Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add constraints on the Common Name for certificate-based authentication #2595

Merged
merged 15 commits into from
Apr 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 73 additions & 17 deletions builtin/credential/cert/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,9 +348,9 @@ func TestBackend_CertWrites(t *testing.T) {
tc := logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "aaa", ca1, "foo", false),
testAccStepCert(t, "bbb", ca2, "foo", false),
testAccStepCert(t, "ccc", ca3, "foo", true),
testAccStepCert(t, "aaa", ca1, "foo", "", false),
testAccStepCert(t, "bbb", ca2, "foo", "", false),
testAccStepCert(t, "ccc", ca3, "foo", "", true),
},
}
tc.Steps = append(tc.Steps, testAccStepListCerts(t, []string{"aaa", "bbb"})...)
Expand All @@ -368,13 +368,17 @@ func TestBackend_basic_CA(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", false),
testAccStepCert(t, "web", ca, "foo", "", false),
testAccStepLogin(t, connState),
testAccStepCertLease(t, "web", ca, "foo"),
testAccStepCertTTL(t, "web", ca, "foo"),
testAccStepLogin(t, connState),
testAccStepCertNoLease(t, "web", ca, "foo"),
testAccStepLoginDefaultLease(t, connState),
testAccStepCert(t, "web", ca, "foo", "*.example.com", false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "*.invalid.com", false),
testAccStepLoginInvalid(t, connState),
},
})
}
Expand Down Expand Up @@ -405,8 +409,29 @@ func TestBackend_Basic_CRLs(t *testing.T) {
})
}

// Test a self-signed client that is trusted
// Test a self-signed client (root CA) that is trusted
func TestBackend_basic_singleCert(t *testing.T) {
connState := testConnState(t, "test-fixtures/root/rootcacert.pem",
"test-fixtures/root/rootcakey.pem", "test-fixtures/root/rootcacert.pem")
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", "", false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", false),
testAccStepLoginInvalid(t, connState),
},
})
}

// Test against a collection of matching and non-matching rules
func TestBackend_mixed_constraints(t *testing.T) {
connState := testConnState(t, "test-fixtures/keys/cert.pem",
"test-fixtures/keys/key.pem", "test-fixtures/root/rootcacert.pem")
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
Expand All @@ -416,13 +441,18 @@ func TestBackend_basic_singleCert(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", false),
testAccStepCert(t, "1unconstrained", ca, "foo", "", false),
testAccStepCert(t, "2matching", ca, "foo", "*.example.com,whatever", false),
testAccStepCert(t, "3invalid", ca, "foo", "invalid", false),
testAccStepLogin(t, connState),
// Assumes CertEntries are processed in alphabetical order (due to store.List), so we only match 2matching if 1unconstrained doesn't match
testAccStepLoginWithName(t, connState, "2matching"),
testAccStepLoginWithNameInvalid(t, connState, "3invalid"),
},
})
}

// Test an untrusted self-signed client
// Test an untrusted client
func TestBackend_untrusted(t *testing.T) {
connState := testConnState(t, "test-fixtures/keys/cert.pem",
"test-fixtures/keys/key.pem", "test-fixtures/root/rootcacert.pem")
Expand Down Expand Up @@ -476,6 +506,10 @@ func testAccStepDeleteCRL(t *testing.T, connState tls.ConnectionState) logicalte
}

func testAccStepLogin(t *testing.T, connState tls.ConnectionState) logicaltest.TestStep {
return testAccStepLoginWithName(t, connState, "")
}

func testAccStepLoginWithName(t *testing.T, connState tls.ConnectionState, certName string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "login",
Expand All @@ -486,9 +520,16 @@ func testAccStepLogin(t *testing.T, connState tls.ConnectionState) logicaltest.T
t.Fatalf("bad lease length: %#v", resp.Auth)
}

if certName != "" && resp.Auth.DisplayName != ("mnt-"+certName) {
t.Fatalf("matched the wrong cert: %#v", resp.Auth.DisplayName)
}

fn := logicaltest.TestCheckAuth([]string{"default", "foo"})
return fn(resp)
},
Data: map[string]interface{}{
"name": certName,
},
}
}

Expand All @@ -510,6 +551,10 @@ func testAccStepLoginDefaultLease(t *testing.T, connState tls.ConnectionState) l
}

func testAccStepLoginInvalid(t *testing.T, connState tls.ConnectionState) logicaltest.TestStep {
return testAccStepLoginWithNameInvalid(t, connState, "")
}

func testAccStepLoginWithNameInvalid(t *testing.T, connState tls.ConnectionState, certName string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "login",
Expand All @@ -521,6 +566,9 @@ func testAccStepLoginInvalid(t *testing.T, connState tls.ConnectionState) logica
}
return nil
},
Data: map[string]interface{}{
"name": certName,
},
ErrorOk: true,
}
}
Expand Down Expand Up @@ -572,16 +620,17 @@ func testAccStepListCerts(
}

func testAccStepCert(
t *testing.T, name string, cert []byte, policies string, expectError bool) logicaltest.TestStep {
t *testing.T, name string, cert []byte, policies string, allowedNames string, expectError bool) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "certs/" + name,
ErrorOk: expectError,
Data: map[string]interface{}{
"certificate": string(cert),
"policies": policies,
"display_name": name,
"lease": 1000,
"certificate": string(cert),
"policies": policies,
"display_name": name,
"allowed_names": allowedNames,
"lease": 1000,
},
Check: func(resp *logical.Response) error {
if resp == nil && expectError {
Expand Down Expand Up @@ -730,18 +779,25 @@ func Test_Renew(t *testing.T) {
t.Fatal(err)
}

resp, err = b.pathLogin(req, nil)
empty_login_fd := &framework.FieldData{
Raw: map[string]interface{}{},
Schema: pathLogin(b).Fields,
}
resp, err = b.pathLogin(req, empty_login_fd)
if err != nil {
t.Fatal(err)
}
if resp.IsError() {
t.Fatalf("got error: %#v", *resp)
}
req.Auth.InternalData = resp.Auth.InternalData
req.Auth.Metadata = resp.Auth.Metadata
req.Auth.LeaseOptions = resp.Auth.LeaseOptions
req.Auth.Policies = resp.Auth.Policies
req.Auth.IssueTime = time.Now()

// Normal renewal
resp, err = b.pathLoginRenew(req, nil)
resp, err = b.pathLoginRenew(req, empty_login_fd)
if err != nil {
t.Fatal(err)
}
Expand All @@ -759,7 +815,7 @@ func Test_Renew(t *testing.T) {
t.Fatal(err)
}

resp, err = b.pathLoginRenew(req, nil)
resp, err = b.pathLoginRenew(req, empty_login_fd)
if err == nil {
t.Fatal("expected error")
}
Expand All @@ -771,7 +827,7 @@ func Test_Renew(t *testing.T) {
t.Fatal(err)
}

resp, err = b.pathLoginRenew(req, nil)
resp, err = b.pathLoginRenew(req, empty_login_fd)
if err != nil {
t.Fatal(err)
}
Expand All @@ -788,7 +844,7 @@ func Test_Renew(t *testing.T) {
t.Fatal(err)
}

resp, err = b.pathLoginRenew(req, nil)
resp, err = b.pathLoginRenew(req, empty_login_fd)
if err != nil {
t.Fatal(err)
}
Expand Down
9 changes: 8 additions & 1 deletion builtin/credential/cert/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type CLIHandler struct{}
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
var data struct {
Mount string `mapstructure:"mount"`
Name string `mapstructure:"name"`
}
if err := mapstructure.WeakDecode(m, &data); err != nil {
return "", err
Expand All @@ -22,8 +23,11 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
data.Mount = "cert"
}

options := map[string]interface{}{
"name": data.Name,
}
path := fmt.Sprintf("auth/%s/login", data.Mount)
secret, err := c.Logical().Write(path, nil)
secret, err := c.Logical().Write(path, options)
if err != nil {
return "", err
}
Expand All @@ -38,10 +42,13 @@ func (h *CLIHandler) Help() string {
help := `
The "cert" credential provider allows you to authenticate with a
client certificate. No other authentication materials are needed.
Optionally, you may specify the specific certificate role to
authenticate against with the "name" parameter.

Example: vault auth -method=cert \
-client-cert=/path/to/cert.pem \
-client-key=/path/to/key.pem
name=cert1

`

Expand Down
27 changes: 18 additions & 9 deletions builtin/credential/cert/path_certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ func pathCerts(b *backend) *framework.Path {
Must be x509 PEM encoded.`,
},

"allowed_names": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `A comma-separated list of names.
At least one must exist in either the Common Name or SANs. Supports globbing.`,
},

"display_name": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The display name to use for clients using this
Expand Down Expand Up @@ -139,6 +145,7 @@ func (b *backend) pathCertWrite(
certificate := d.Get("certificate").(string)
displayName := d.Get("display_name").(string)
policies := policyutil.ParsePolicies(d.Get("policies").(string))
allowedNames := d.Get("allowed_names").([]string)

// Default the display name to the certificate name if not given
if displayName == "" {
Expand All @@ -165,10 +172,11 @@ func (b *backend) pathCertWrite(
}

certEntry := &CertEntry{
Name: name,
Certificate: certificate,
DisplayName: displayName,
Policies: policies,
Name: name,
Certificate: certificate,
DisplayName: displayName,
Policies: policies,
AllowedNames: allowedNames,
}

// Parse the lease duration or default to backend/system default
Expand Down Expand Up @@ -196,11 +204,12 @@ func (b *backend) pathCertWrite(
}

type CertEntry struct {
Name string
Certificate string
DisplayName string
Policies []string
TTL time.Duration
Name string
Certificate string
DisplayName string
Policies []string
TTL time.Duration
AllowedNames []string
}

const pathCertHelpSyn = `
Expand Down
Loading