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

Breakout parameters for x.509 certificate login #4463

Merged
merged 7 commits into from
May 25, 2018
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
209 changes: 170 additions & 39 deletions builtin/credential/cert/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -843,9 +843,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", allowed{}, false),
testAccStepCert(t, "bbb", ca2, "foo", allowed{}, false),
testAccStepCert(t, "ccc", ca3, "foo", allowed{}, true),
},
}
tc.Steps = append(tc.Steps, testAccStepListCerts(t, []string{"aaa", "bbb"})...)
Expand All @@ -866,7 +866,7 @@ 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", allowed{}, false),
testAccStepLogin(t, connState),
testAccStepCertLease(t, "web", ca, "foo"),
testAccStepCertTTL(t, "web", ca, "foo"),
Expand All @@ -875,9 +875,9 @@ func TestBackend_basic_CA(t *testing.T) {
testAccStepLogin(t, connState),
testAccStepCertNoLease(t, "web", ca, "foo"),
testAccStepLoginDefaultLease(t, connState),
testAccStepCert(t, "web", ca, "foo", "*.example.com", "", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "*.example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "*.invalid.com", "", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "*.invalid.com"}, false),
testAccStepLoginInvalid(t, connState),
},
})
Expand Down Expand Up @@ -926,20 +926,45 @@ 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, "web", ca, "foo", allowed{}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "1.2.3.4:invalid", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "1.2.3.4:invalid"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}

// Test a self-signed client with custom extensions (root CA) that is trusted
func TestBackend_extensions_singleCert(t *testing.T) {
func TestBackend_common_name_singleCert(t *testing.T) {
connState, err := testConnState("test-fixtures/root/rootcacert.pem",
"test-fixtures/root/rootcakey.pem", "test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
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", allowed{}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{common_names: "example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{common_names: "invalid"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "1.2.3.4:invalid"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}

// Test a self-signed client with custom ext (root CA) that is trusted
func TestBackend_ext_singleCert(t *testing.T) {
connState, err := testConnState(
"test-fixtures/root/rootcawextcert.pem",
"test-fixtures/root/rootcawextkey.pem",
Expand All @@ -955,39 +980,132 @@ func TestBackend_extensions_singleCert(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:A UTF8String Extension", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:A UTF8String Extension"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:*,2.1.1.2:A UTF8*", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:*,2.1.1.2:A UTF8*"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "1.2.3.45:*", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "1.2.3.45:*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:The Wrong Value", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:*,2.1.1.2:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:*,2.1.1.2:The Wrong Value", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:,2.1.1.2:*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:,2.1.1.2:*", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.1.1.1:A UTF8String Extension"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.1.1.1:*,2.1.1.2:A UTF8*"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "1.2.3.45:*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "2.1.1.1:A UTF8String Extension", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.1.1.1:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.1.1.1:*,2.1.1.2:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:A UTF8String Extension"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:*,2.1.1.2:A UTF8*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "1.2.3.45:*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:*,2.1.1.2:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}

// Test a self-signed client with URI alt names (root CA) that is trusted
func TestBackend_dns_singleCert(t *testing.T) {
connState, err := testConnState(
"test-fixtures/root/rootcawdnscert.pem",
"test-fixtures/root/rootcawdnskey.pem",
"test-fixtures/root/rootcacert.pem",
)
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
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", allowed{dns: "example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "2.1.1.1:*,2.1.1.2:A UTF8*", false),
testAccStepCert(t, "web", ca, "foo", allowed{dns: "*ample.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "1.2.3.45:*", false),
testAccStepCert(t, "web", ca, "foo", allowed{dns: "notincert.com"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "2.1.1.1:The Wrong Value", false),
testAccStepCert(t, "web", ca, "foo", allowed{dns: "abc"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "2.1.1.1:*,2.1.1.2:The Wrong Value", false),
testAccStepCert(t, "web", ca, "foo", allowed{dns: "*.example.com"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "2.1.1.1:A UTF8String Extension", false),
},
})
}

// Test a self-signed client with URI alt names (root CA) that is trusted
func TestBackend_email_singleCert(t *testing.T) {
connState, err := testConnState(
"test-fixtures/root/rootcawemailcert.pem",
"test-fixtures/root/rootcawemailkey.pem",
"test-fixtures/root/rootcacert.pem",
)
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
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", allowed{emails: "valid@example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{emails: "*@example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{emails: "invalid@notincert.com"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "2.1.1.1:*,2.1.1.2:A UTF8*", false),
testAccStepCert(t, "web", ca, "foo", allowed{emails: "abc"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "1.2.3.45:*", false),
testAccStepCert(t, "web", ca, "foo", allowed{emails: "*.example.com"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "2.1.1.1:The Wrong Value", false),
},
})
}

// Test a self-signed client with URI alt names (root CA) that is trusted
func TestBackend_uri_singleCert(t *testing.T) {
connState, err := testConnState(
"test-fixtures/root/rootcawuricert.pem",
"test-fixtures/root/rootcawurikey.pem",
"test-fixtures/root/rootcacert.pem",
)
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
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", allowed{uris: "spiffe://example.com/*"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{uris: "spiffe://example.com/host"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{uris: "spiffe://example.com/invalid"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "2.1.1.1:*,2.1.1.2:The Wrong Value", false),
testAccStepCert(t, "web", ca, "foo", allowed{uris: "abc"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{uris: "http://www.google.com"}, false),
testAccStepLoginInvalid(t, connState),
},
})
Expand All @@ -1007,9 +1125,9 @@ func TestBackend_mixed_constraints(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "1unconstrained", ca, "foo", "", "", false),
testAccStepCert(t, "2matching", ca, "foo", "*.example.com,whatever", "", false),
testAccStepCert(t, "3invalid", ca, "foo", "invalid", "", false),
testAccStepCert(t, "1unconstrained", ca, "foo", allowed{}, false),
testAccStepCert(t, "2matching", ca, "foo", allowed{names: "*.example.com,whatever"}, false),
testAccStepCert(t, "3invalid", ca, "foo", allowed{names: "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"),
Expand Down Expand Up @@ -1314,19 +1432,32 @@ func testAccStepListCerts(
}
}

type allowed struct {
names string // allowed names in the certificate, looks at common, name, dns, email [depricated]
common_names string // allowed common names in the certificate
dns string // allowed dns names in the SAN extension of the certificate
emails string // allowed email names in SAN extension of the certificate
uris string // allowed uris in SAN extension of the certificate
ext string // required extensions in the certificate
}

func testAccStepCert(
t *testing.T, name string, cert []byte, policies string, allowedNames string, requiredExtensions string, expectError bool) logicaltest.TestStep {
t *testing.T, name string, cert []byte, policies string, testData allowed, 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,
"allowed_names": allowedNames,
"required_extensions": requiredExtensions,
"lease": 1000,
"certificate": string(cert),
"policies": policies,
"display_name": name,
"allowed_names": testData.names,
"allowed_common_names": testData.common_names,
"allowed_dns_sans": testData.dns,
"allowed_email_sans": testData.emails,
"allowed_uri_sans": testData.uris,
"required_extensions": testData.ext,
"lease": 1000,
},
Check: func(resp *logical.Response) error {
if resp == nil && expectError {
Expand Down
61 changes: 53 additions & 8 deletions builtin/credential/cert/path_certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,33 @@ 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.`,
At least one must exist in either the Common Name or SANs. Supports globbing.
This parameter is deprecated, please use allowed_common_names, allowed_dns_sans,
allowed_email_sans, allowed_uri_sans.`,
},

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

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

"allowed_email_sans": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `A comma-separated list of Email Addresses.
At least one must exist in the SANs. Supports globbing.`,
},

"allowed_uri_sans": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `A comma-separated list of URIs.
At least one must exist in the SANs. Supports globbing.`,
},

"required_extensions": &framework.FieldSchema{
Expand Down Expand Up @@ -77,12 +103,14 @@ seconds. Defaults to system/backend default TTL.`,
Description: `TTL for tokens issued by this backend.
Defaults to system/backend default TTL time.`,
},

"max_ttl": &framework.FieldSchema{
Type: framework.TypeDurationSecond,
Description: `Duration in either an integer number of seconds (3600) or
an integer time unit (60m) after which the
issued token can no longer be renewed.`,
},

"period": &framework.FieldSchema{
Type: framework.TypeDurationSecond,
Description: `If set, indicates that the token generated using this role
Expand Down Expand Up @@ -151,13 +179,18 @@ func (b *backend) pathCertRead(ctx context.Context, req *logical.Request, d *fra

return &logical.Response{
Data: map[string]interface{}{
"certificate": cert.Certificate,
"display_name": cert.DisplayName,
"policies": cert.Policies,
"ttl": cert.TTL / time.Second,
"max_ttl": cert.MaxTTL / time.Second,
"period": cert.Period / time.Second,
"allowed_names": cert.AllowedNames,
"certificate": cert.Certificate,
"display_name": cert.DisplayName,
"policies": cert.Policies,
"ttl": cert.TTL / time.Second,
"max_ttl": cert.MaxTTL / time.Second,
"period": cert.Period / time.Second,
"allowed_names": cert.AllowedNames,
"allowed_common_names": cert.AllowedCommonNames,
"allowed_dns_sans": cert.AllowedDNSSANs,
"allowed_email_sans": cert.AllowedEmailSANs,
"allowed_uri_sans": cert.AllowedURISANs,
"required_extensions": cert.RequiredExtensions,
},
}, nil
}
Expand All @@ -168,6 +201,10 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr
displayName := d.Get("display_name").(string)
policies := policyutil.ParsePolicies(d.Get("policies"))
allowedNames := d.Get("allowed_names").([]string)
allowedCommonNames := d.Get("allowed_common_names").([]string)
allowedDNSSANs := d.Get("allowed_dns_sans").([]string)
allowedEmailSANs := d.Get("allowed_email_sans").([]string)
allowedURISANs := d.Get("allowed_uri_sans").([]string)
requiredExtensions := d.Get("required_extensions").([]string)

var resp logical.Response
Expand Down Expand Up @@ -246,6 +283,10 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr
DisplayName: displayName,
Policies: policies,
AllowedNames: allowedNames,
AllowedCommonNames: allowedCommonNames,
AllowedDNSSANs: allowedDNSSANs,
AllowedEmailSANs: allowedEmailSANs,
AllowedURISANs: allowedURISANs,
RequiredExtensions: requiredExtensions,
TTL: ttl,
MaxTTL: maxTTL,
Expand Down Expand Up @@ -278,6 +319,10 @@ type CertEntry struct {
MaxTTL time.Duration
Period time.Duration
AllowedNames []string
AllowedCommonNames []string
AllowedDNSSANs []string
AllowedEmailSANs []string
AllowedURISANs []string
RequiredExtensions []string
BoundCIDRs []*sockaddr.SockAddrMarshaler
}
Expand Down
Loading