From 0fd4a21cd74ef97271fe5b9497aa7e6bfa1cf247 Mon Sep 17 00:00:00 2001 From: Nic Jackson Date: Sat, 31 Mar 2018 14:24:44 +0100 Subject: [PATCH 1/6] Added capability for allowed_names in cert auth to be validated against URI SAN --- builtin/credential/cert/backend_test.go | 6 +++++ builtin/credential/cert/path_login.go | 6 +++++ .../cert/test-fixtures/root/rootcawext.cnf | 1 + .../cert/test-fixtures/root/rootcawext.csr | 24 +++++++++--------- .../test-fixtures/root/rootcawextcert.pem | 25 ++++++++++--------- 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/builtin/credential/cert/backend_test.go b/builtin/credential/cert/backend_test.go index ec1c83139415..4081323bbb7a 100644 --- a/builtin/credential/cert/backend_test.go +++ b/builtin/credential/cert/backend_test.go @@ -959,6 +959,10 @@ func TestBackend_extensions_singleCert(t *testing.T) { testAccStepLogin(t, connState), testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:*,2.1.1.2:A UTF8*", false), testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", "spiffe://example.com/*", "", false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", "spiffe://example.com/host", "", false), + testAccStepLogin(t, connState), testAccStepCert(t, "web", ca, "foo", "", "1.2.3.45:*", false), testAccStepLoginInvalid(t, connState), testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:The Wrong Value", false), @@ -989,6 +993,8 @@ func TestBackend_extensions_singleCert(t *testing.T) { testAccStepLoginInvalid(t, connState), testAccStepCert(t, "web", ca, "foo", "invalid", "2.1.1.1:*,2.1.1.2:The Wrong Value", false), testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", "spiffe://example.com/invalid", "", false), + testAccStepLoginInvalid(t, connState), }, }) } diff --git a/builtin/credential/cert/path_login.go b/builtin/credential/cert/path_login.go index 65e06b987b69..81527cbfe1bc 100644 --- a/builtin/credential/cert/path_login.go +++ b/builtin/credential/cert/path_login.go @@ -268,6 +268,12 @@ func (b *backend) matchesNames(clientCert *x509.Certificate, config *ParsedCert) return true } } + + for _, name := range clientCert.URIs { + if glob.Glob(allowedName, name.String()) { + return true + } + } } return false } diff --git a/builtin/credential/cert/test-fixtures/root/rootcawext.cnf b/builtin/credential/cert/test-fixtures/root/rootcawext.cnf index 524efd2e4013..193cbfd12b3a 100644 --- a/builtin/credential/cert/test-fixtures/root/rootcawext.cnf +++ b/builtin/credential/cert/test-fixtures/root/rootcawext.cnf @@ -19,3 +19,4 @@ subjectAltName = @alt_names [ alt_names ] DNS.1 = example.com IP.1 = 127.0.0.1 +URI.1 = spiffe://example.com/host diff --git a/builtin/credential/cert/test-fixtures/root/rootcawext.csr b/builtin/credential/cert/test-fixtures/root/rootcawext.csr index 55e22eedeb0a..f44aa26bad94 100644 --- a/builtin/credential/cert/test-fixtures/root/rootcawext.csr +++ b/builtin/credential/cert/test-fixtures/root/rootcawext.csr @@ -1,19 +1,19 @@ -----BEGIN CERTIFICATE REQUEST----- -MIIDAzCCAesCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3 +MIIDHjCCAgYCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQDM2PrLyK/wVQIcnK362ZylDrIVMjFQzps/0AxM ke+8MNPMArBlSAhnZus6qb0nN0nJrDLkHQgYqnSvK9N7VUv/xFblEcOLBlciLhyN Wkm92+q/M/xOvUVmnYkN3XgTI5QNxF7ZWDFHmwCNV27RraQZou0hG7yvyoILLMQE 3MnMCNM1nZ9JIuBMcRsZLGqQ1XNaQljboRVIUjimzkcfYyTruhLosTIbwForp78J MzHHqVjtLJXPqUnRMS7KhGMj1f2mIswQzCv6F2PWEzNBbP4Gb67znKikKDs0RgyL -RyfizFNFJSC58XntK8jwHK1D8W3UepFf4K8xNFnhPoKWtWfJAgMBAAGggacwgaQG -CSqGSIb3DQEJDjGBljCBkzAcBgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATAf -BgNRAQEEGAwWQSBVVEY4U3RyaW5nIEV4dGVuc2lvbjAZBgNRAQIEEgwQQSBVVEY4 -IEV4dGVuc2lvbjAZBgNRAQMEEhYQQW4gSUE1IEV4dGVuc2lvbjAcBgNRAQQEFRoT -QSBWaXNpYmxlIEV4dGVuc2lvbjANBgkqhkiG9w0BAQsFAAOCAQEAtYjewBcqAXxk -tDY0lpZid6ZvfngdDlDZX0vrs3zNppKNe5Sl+jsoDOexqTA7HQA/y1ru117sAEeB -yiqMeZ7oPk8b3w+BZUpab7p2qPMhZypKl93y/jGXGscc3jRbUBnym9S91PSq6wUd -f2aigSqFc9+ywFVdx5PnnZUfcrUQ2a+AweYEkGOzXX2Ga+Ige8grDMCzRgCoP5cW -kM5ghwZp5wYIBGrKBU9iDcBlmnNhYaGWf+dD00JtVDPNn2bJnCsJHIO0nklZgnrS -fli8VQ1nYPkONdkiRYLt6//6at1iNDoDgsVCChtlVkLpxFIKcDFUHlffZsc1kMFI -HTX579k8hA== +RyfizFNFJSC58XntK8jwHK1D8W3UepFf4K8xNFnhPoKWtWfJAgMBAAGggcIwgb8G +CSqGSIb3DQEJDjGBsTCBrjA3BgNVHREEMDAuggtleGFtcGxlLmNvbYcEfwAAAYYZ +c3BpZmZlOi8vZXhhbXBsZS5jb20vaG9zdDAfBgNRAQEEGAwWQSBVVEY4U3RyaW5n +IEV4dGVuc2lvbjAZBgNRAQIEEgwQQSBVVEY4IEV4dGVuc2lvbjAZBgNRAQMEEhYQ +QW4gSUE1IEV4dGVuc2lvbjAcBgNRAQQEFRoTQSBWaXNpYmxlIEV4dGVuc2lvbjAN +BgkqhkiG9w0BAQsFAAOCAQEACk8FEoQfho3e0QpgbAOBoS+/2I0jpjYBDQCsU1x/ +OHR1OfOmDKQ0jpVBhjA5KLB7B+Svmn3reAbFx8vSo03moMakaRDEYzGxLB7QNoYW +s31b7OwDaMO60RPjEJa57E5SV5jjAJAsTQWCsBekvHuWSXCAat3urQOAksWEOFIa +rC3ZyLEHgXOoEu20Br7SM6uClUBl3pU1IxyKCM95XGelajcgl137XQR+bxsURAJf +ZPto9QMem7C2JNxhNwYLKNqOJD+ZcSit2tFWa0lkUfkU66XVEx5W6zmuppgDkg2J +BgcPVVCIQYpSWTUKCHRLEcSeTL2UZWgIowZWd2Kuf9JxUw== -----END CERTIFICATE REQUEST----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawextcert.pem b/builtin/credential/cert/test-fixtures/root/rootcawextcert.pem index 2c8591735f5a..365d1442a55a 100644 --- a/builtin/credential/cert/test-fixtures/root/rootcawextcert.pem +++ b/builtin/credential/cert/test-fixtures/root/rootcawextcert.pem @@ -1,20 +1,21 @@ -----BEGIN CERTIFICATE----- -MIIDRjCCAi6gAwIBAgIJAJIiPq+77hejMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV -BAMTC2V4YW1wbGUuY29tMB4XDTE3MTEyOTE5MTgwM1oXDTI3MTEyNzE5MTgwM1ow +MIIDYTCCAkmgAwIBAgIJAJIiPq+77hesMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV +BAMTC2V4YW1wbGUuY29tMB4XDTE4MDMzMTEyNTMxOVoXDTE5MDMzMTEyNTMxOVow FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDM2PrLyK/wVQIcnK362ZylDrIVMjFQzps/0AxMke+8MNPMArBlSAhn Zus6qb0nN0nJrDLkHQgYqnSvK9N7VUv/xFblEcOLBlciLhyNWkm92+q/M/xOvUVm nYkN3XgTI5QNxF7ZWDFHmwCNV27RraQZou0hG7yvyoILLMQE3MnMCNM1nZ9JIuBM cRsZLGqQ1XNaQljboRVIUjimzkcfYyTruhLosTIbwForp78JMzHHqVjtLJXPqUnR MS7KhGMj1f2mIswQzCv6F2PWEzNBbP4Gb67znKikKDs0RgyLRyfizFNFJSC58Xnt -K8jwHK1D8W3UepFf4K8xNFnhPoKWtWfJAgMBAAGjgZYwgZMwHAYDVR0RBBUwE4IL -ZXhhbXBsZS5jb22HBH8AAAEwHwYDUQEBBBgMFkEgVVRGOFN0cmluZyBFeHRlbnNp -b24wGQYDUQECBBIMEEEgVVRGOCBFeHRlbnNpb24wGQYDUQEDBBIWEEFuIElBNSBF -eHRlbnNpb24wHAYDUQEEBBUaE0EgVmlzaWJsZSBFeHRlbnNpb24wDQYJKoZIhvcN -AQELBQADggEBAGU/iA6saupEaGn/veVNCknFGDL7pst5D6eX/y9atXlBOdJe7ZJJ -XQRkeHJldA0khVpzH7Ryfi+/25WDuNz+XTZqmb4ppeV8g9amtqBwxziQ9UUwYrza -eDBqdXBaYp/iHUEHoceX4F44xuo80BIqwF0lD9TFNUFoILnF26ajhKX0xkGaiKTH -6SbjBfHoQVMzOHokVRWregmgNycV+MAI9Ne9XkIZvdOYeNlcS9drZeJI3szkiaxB -WWaWaAr5UU2Z0yUCZnAIDMRcIiUbSEjIDz504sSuCzTctMOxWZu0r/0UrXRzwZZi -HAaKm3MUmBh733ChP4rTB58nr5DEr5rJ9P8= +K8jwHK1D8W3UepFf4K8xNFnhPoKWtWfJAgMBAAGjgbEwga4wNwYDVR0RBDAwLoIL +ZXhhbXBsZS5jb22HBH8AAAGGGXNwaWZmZTovL2V4YW1wbGUuY29tL2hvc3QwHwYD +UQEBBBgMFkEgVVRGOFN0cmluZyBFeHRlbnNpb24wGQYDUQECBBIMEEEgVVRGOCBF +eHRlbnNpb24wGQYDUQEDBBIWEEFuIElBNSBFeHRlbnNpb24wHAYDUQEEBBUaE0Eg +VmlzaWJsZSBFeHRlbnNpb24wDQYJKoZIhvcNAQEFBQADggEBAMIW2sK/rz+Ip8p5 +618MK5Er6BPDtsp0YMrb1mSG6uyDlmpi0zxjXp7ulSCvZ4ABClP94MaE1sJsBTBN +9mOWmKTyu5EjOVAsJ76XdS+/pkzItG7r+lPpAmnzb6sxdFDbhAhFJ6MkD6sL0WgY +b8YWH6MPpehw0CODbHGNWQGstITqY7fC8q6m+MoiGxFIh72Cgm4e1wxdaPoZ8Hmn +waRmJK8a2RIe4ZSaSwbbOTcfy4loYvudDtJQNUpsnzJS9yjt2RvO++fsKa0lMEni +RqslPF6ZqqhgV4dLoteAQlfE6q77VVgxIt3QKveV+Roq34GFtO8Xu7ok9wQ9oy4f +ILb53wI= -----END CERTIFICATE----- From ba2d9cf1256d9575ea321c82f282c9f8586e7e5f Mon Sep 17 00:00:00 2001 From: Nic Jackson Date: Sat, 31 Mar 2018 17:42:44 +0100 Subject: [PATCH 2/6] Update to move SAN uris to separate parameter --- builtin/credential/cert/backend_test.go | 98 ++++++++++++------- builtin/credential/cert/path_certs.go | 27 +++-- builtin/credential/cert/path_login.go | 17 +++- .../cert/test-fixtures/root/rootcacert.srl | 2 +- .../cert/test-fixtures/root/rootcawext.cnf | 1 - .../cert/test-fixtures/root/rootcawext.csr | 24 ++--- .../test-fixtures/root/rootcawextcert.pem | 25 +++-- .../cert/test-fixtures/root/rootcawuri.cnf | 18 ++++ .../cert/test-fixtures/root/rootcawuri.csr | 17 ++++ .../test-fixtures/root/rootcawuricert.pem | 18 ++++ .../cert/test-fixtures/root/rootcawurikey.pem | 28 ++++++ website/source/api/auth/cert/index.html.md | 5 + 12 files changed, 209 insertions(+), 71 deletions(-) create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawuri.cnf create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawuri.csr create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawuricert.pem create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawurikey.pem diff --git a/builtin/credential/cert/backend_test.go b/builtin/credential/cert/backend_test.go index 4081323bbb7a..ae766ebb80f7 100644 --- a/builtin/credential/cert/backend_test.go +++ b/builtin/credential/cert/backend_test.go @@ -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", "", "", "", false), + testAccStepCert(t, "bbb", ca2, "foo", "", "", "", false), + testAccStepCert(t, "ccc", ca3, "foo", "", "", "", true), }, } tc.Steps = append(tc.Steps, testAccStepListCerts(t, []string{"aaa", "bbb"})...) @@ -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", "", "", "", false), testAccStepLogin(t, connState), testAccStepCertLease(t, "web", ca, "foo"), testAccStepCertTTL(t, "web", ca, "foo"), @@ -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", "*.example.com", "", "", false), testAccStepLogin(t, connState), - testAccStepCert(t, "web", ca, "foo", "*.invalid.com", "", false), + testAccStepCert(t, "web", ca, "foo", "*.invalid.com", "", "", false), testAccStepLoginInvalid(t, connState), }, }) @@ -926,13 +926,13 @@ 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", "", "", "", false), testAccStepLogin(t, connState), - testAccStepCert(t, "web", ca, "foo", "example.com", "", false), + testAccStepCert(t, "web", ca, "foo", "example.com", "", "", false), testAccStepLogin(t, connState), - testAccStepCert(t, "web", ca, "foo", "invalid", "", false), + testAccStepCert(t, "web", ca, "foo", "invalid", "", "", false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "", "1.2.3.4:invalid", false), + testAccStepCert(t, "web", ca, "foo", "", "", "1.2.3.4:invalid", false), testAccStepLoginInvalid(t, connState), }, }) @@ -955,45 +955,70 @@ 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", "", "", "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", "", "", "2.1.1.1:*,2.1.1.2:A UTF8*", false), testAccStepLogin(t, connState), - testAccStepCert(t, "web", ca, "foo", "spiffe://example.com/*", "", false), - testAccStepLogin(t, connState), - testAccStepCert(t, "web", ca, "foo", "spiffe://example.com/host", "", false), - testAccStepLogin(t, connState), - testAccStepCert(t, "web", ca, "foo", "", "1.2.3.45:*", false), + testAccStepCert(t, "web", ca, "foo", "", "", "1.2.3.45:*", false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:The Wrong Value", false), + testAccStepCert(t, "web", ca, "foo", "", "", "2.1.1.1: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", "", "", "2.1.1.1:*,2.1.1.2:The Wrong Value", false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:", false), + testAccStepCert(t, "web", ca, "foo", "", "", "2.1.1.1:", false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:,2.1.1.2:*", false), + testAccStepCert(t, "web", ca, "foo", "", "", "2.1.1.1:,2.1.1.2:*", false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "example.com", "2.1.1.1:A UTF8String Extension", false), + testAccStepCert(t, "web", ca, "foo", "example.com", "", "2.1.1.1:A UTF8String Extension", 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", "example.com", "", "2.1.1.1:*,2.1.1.2:A UTF8*", false), testAccStepLogin(t, connState), - testAccStepCert(t, "web", ca, "foo", "example.com", "1.2.3.45:*", false), + testAccStepCert(t, "web", ca, "foo", "example.com", "", "1.2.3.45:*", false), + testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", "example.com", "", "2.1.1.1:The Wrong Value", false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "example.com", "2.1.1.1:The Wrong Value", false), + testAccStepCert(t, "web", ca, "foo", "example.com", "", "2.1.1.1:*,2.1.1.2:The Wrong Value", 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", "invalid", "", "2.1.1.1:A UTF8String Extension", false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "invalid", "2.1.1.1:A UTF8String Extension", false), + testAccStepCert(t, "web", ca, "foo", "invalid", "", "2.1.1.1:*,2.1.1.2:A UTF8*", 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", "invalid", "", "1.2.3.45:*", false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "invalid", "1.2.3.45:*", false), + testAccStepCert(t, "web", ca, "foo", "invalid", "", "2.1.1.1:The Wrong Value", false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "invalid", "2.1.1.1:The Wrong Value", false), + testAccStepCert(t, "web", ca, "foo", "invalid", "", "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_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", "", "spiffe://example.com/*", "", false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", "", "spiffe://example.com/host", "", false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", "", "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", "", "abc", "", false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "spiffe://example.com/invalid", "", false), + testAccStepCert(t, "web", ca, "foo", "", "http://www.google.com", "", false), testAccStepLoginInvalid(t, connState), }, }) @@ -1013,9 +1038,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", "", "", "", 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"), @@ -1195,7 +1220,7 @@ func testAccStepListCerts( } 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, allowedNames, allowedURIs, requiredExtensions string, expectError bool) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: "certs/" + name, @@ -1205,6 +1230,7 @@ func testAccStepCert( "policies": policies, "display_name": name, "allowed_names": allowedNames, + "allowed_uris": allowedURIs, "required_extensions": requiredExtensions, "lease": 1000, }, diff --git a/builtin/credential/cert/path_certs.go b/builtin/credential/cert/path_certs.go index 511b240c489f..7dc660a9eee4 100644 --- a/builtin/credential/cert/path_certs.go +++ b/builtin/credential/cert/path_certs.go @@ -46,6 +46,12 @@ Must be x509 PEM encoded.`, At least one must exist in either the Common Name or SANs. Supports globbing.`, }, + "allowed_uris": &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{ Type: framework.TypeCommaStringSlice, Description: `A comma-separated string or array of extensions @@ -75,12 +81,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 @@ -144,13 +152,15 @@ 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_uris": cert.AllowedURIs, + "required_extensions": cert.RequiredExtensions, }, }, nil } @@ -161,6 +171,7 @@ 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) + allowedURIs := d.Get("allowed_uris").([]string) requiredExtensions := d.Get("required_extensions").([]string) var resp logical.Response @@ -234,6 +245,7 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr DisplayName: displayName, Policies: policies, AllowedNames: allowedNames, + AllowedURIs: allowedURIs, RequiredExtensions: requiredExtensions, TTL: ttl, MaxTTL: maxTTL, @@ -265,6 +277,7 @@ type CertEntry struct { MaxTTL time.Duration Period time.Duration AllowedNames []string + AllowedURIs []string RequiredExtensions []string } diff --git a/builtin/credential/cert/path_login.go b/builtin/credential/cert/path_login.go index 81527cbfe1bc..b633848f40f3 100644 --- a/builtin/credential/cert/path_login.go +++ b/builtin/credential/cert/path_login.go @@ -241,6 +241,7 @@ func (b *backend) verifyCredentials(ctx context.Context, req *logical.Request, d func (b *backend) matchesConstraints(clientCert *x509.Certificate, trustedChain []*x509.Certificate, config *ParsedCert) bool { return !b.checkForChainInCRLs(trustedChain) && b.matchesNames(clientCert, config) && + b.matchesURIs(clientCert, config) && b.matchesCertificateExtensions(clientCert, config) } @@ -269,12 +270,26 @@ func (b *backend) matchesNames(clientCert *x509.Certificate, config *ParsedCert) } } + } + return false +} + +// matchesURIs verifies that the certificate matches at least one configured +// allowed uri +func (b *backend) matchesURIs(clientCert *x509.Certificate, config *ParsedCert) bool { + // Default behavior (no names) is to allow all names + if len(config.Entry.AllowedURIs) == 0 { + return true + } + // At least one pattern must match at least one name if any patterns are specified + for _, allowedURI := range config.Entry.AllowedURIs { for _, name := range clientCert.URIs { - if glob.Glob(allowedName, name.String()) { + if glob.Glob(allowedURI, name.String()) { return true } } } + return false } diff --git a/builtin/credential/cert/test-fixtures/root/rootcacert.srl b/builtin/credential/cert/test-fixtures/root/rootcacert.srl index 219a6be4b13b..282f3267bb3e 100644 --- a/builtin/credential/cert/test-fixtures/root/rootcacert.srl +++ b/builtin/credential/cert/test-fixtures/root/rootcacert.srl @@ -1 +1 @@ -92223EAFBBEE17A3 +92223EAFBBEE17A4 diff --git a/builtin/credential/cert/test-fixtures/root/rootcawext.cnf b/builtin/credential/cert/test-fixtures/root/rootcawext.cnf index 193cbfd12b3a..524efd2e4013 100644 --- a/builtin/credential/cert/test-fixtures/root/rootcawext.cnf +++ b/builtin/credential/cert/test-fixtures/root/rootcawext.cnf @@ -19,4 +19,3 @@ subjectAltName = @alt_names [ alt_names ] DNS.1 = example.com IP.1 = 127.0.0.1 -URI.1 = spiffe://example.com/host diff --git a/builtin/credential/cert/test-fixtures/root/rootcawext.csr b/builtin/credential/cert/test-fixtures/root/rootcawext.csr index f44aa26bad94..55e22eedeb0a 100644 --- a/builtin/credential/cert/test-fixtures/root/rootcawext.csr +++ b/builtin/credential/cert/test-fixtures/root/rootcawext.csr @@ -1,19 +1,19 @@ -----BEGIN CERTIFICATE REQUEST----- -MIIDHjCCAgYCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3 +MIIDAzCCAesCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQDM2PrLyK/wVQIcnK362ZylDrIVMjFQzps/0AxM ke+8MNPMArBlSAhnZus6qb0nN0nJrDLkHQgYqnSvK9N7VUv/xFblEcOLBlciLhyN Wkm92+q/M/xOvUVmnYkN3XgTI5QNxF7ZWDFHmwCNV27RraQZou0hG7yvyoILLMQE 3MnMCNM1nZ9JIuBMcRsZLGqQ1XNaQljboRVIUjimzkcfYyTruhLosTIbwForp78J MzHHqVjtLJXPqUnRMS7KhGMj1f2mIswQzCv6F2PWEzNBbP4Gb67znKikKDs0RgyL -RyfizFNFJSC58XntK8jwHK1D8W3UepFf4K8xNFnhPoKWtWfJAgMBAAGggcIwgb8G -CSqGSIb3DQEJDjGBsTCBrjA3BgNVHREEMDAuggtleGFtcGxlLmNvbYcEfwAAAYYZ -c3BpZmZlOi8vZXhhbXBsZS5jb20vaG9zdDAfBgNRAQEEGAwWQSBVVEY4U3RyaW5n -IEV4dGVuc2lvbjAZBgNRAQIEEgwQQSBVVEY4IEV4dGVuc2lvbjAZBgNRAQMEEhYQ -QW4gSUE1IEV4dGVuc2lvbjAcBgNRAQQEFRoTQSBWaXNpYmxlIEV4dGVuc2lvbjAN -BgkqhkiG9w0BAQsFAAOCAQEACk8FEoQfho3e0QpgbAOBoS+/2I0jpjYBDQCsU1x/ -OHR1OfOmDKQ0jpVBhjA5KLB7B+Svmn3reAbFx8vSo03moMakaRDEYzGxLB7QNoYW -s31b7OwDaMO60RPjEJa57E5SV5jjAJAsTQWCsBekvHuWSXCAat3urQOAksWEOFIa -rC3ZyLEHgXOoEu20Br7SM6uClUBl3pU1IxyKCM95XGelajcgl137XQR+bxsURAJf -ZPto9QMem7C2JNxhNwYLKNqOJD+ZcSit2tFWa0lkUfkU66XVEx5W6zmuppgDkg2J -BgcPVVCIQYpSWTUKCHRLEcSeTL2UZWgIowZWd2Kuf9JxUw== +RyfizFNFJSC58XntK8jwHK1D8W3UepFf4K8xNFnhPoKWtWfJAgMBAAGggacwgaQG +CSqGSIb3DQEJDjGBljCBkzAcBgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATAf +BgNRAQEEGAwWQSBVVEY4U3RyaW5nIEV4dGVuc2lvbjAZBgNRAQIEEgwQQSBVVEY4 +IEV4dGVuc2lvbjAZBgNRAQMEEhYQQW4gSUE1IEV4dGVuc2lvbjAcBgNRAQQEFRoT +QSBWaXNpYmxlIEV4dGVuc2lvbjANBgkqhkiG9w0BAQsFAAOCAQEAtYjewBcqAXxk +tDY0lpZid6ZvfngdDlDZX0vrs3zNppKNe5Sl+jsoDOexqTA7HQA/y1ru117sAEeB +yiqMeZ7oPk8b3w+BZUpab7p2qPMhZypKl93y/jGXGscc3jRbUBnym9S91PSq6wUd +f2aigSqFc9+ywFVdx5PnnZUfcrUQ2a+AweYEkGOzXX2Ga+Ige8grDMCzRgCoP5cW +kM5ghwZp5wYIBGrKBU9iDcBlmnNhYaGWf+dD00JtVDPNn2bJnCsJHIO0nklZgnrS +fli8VQ1nYPkONdkiRYLt6//6at1iNDoDgsVCChtlVkLpxFIKcDFUHlffZsc1kMFI +HTX579k8hA== -----END CERTIFICATE REQUEST----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawextcert.pem b/builtin/credential/cert/test-fixtures/root/rootcawextcert.pem index 365d1442a55a..2c8591735f5a 100644 --- a/builtin/credential/cert/test-fixtures/root/rootcawextcert.pem +++ b/builtin/credential/cert/test-fixtures/root/rootcawextcert.pem @@ -1,21 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIDYTCCAkmgAwIBAgIJAJIiPq+77hesMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV -BAMTC2V4YW1wbGUuY29tMB4XDTE4MDMzMTEyNTMxOVoXDTE5MDMzMTEyNTMxOVow +MIIDRjCCAi6gAwIBAgIJAJIiPq+77hejMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV +BAMTC2V4YW1wbGUuY29tMB4XDTE3MTEyOTE5MTgwM1oXDTI3MTEyNzE5MTgwM1ow FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDM2PrLyK/wVQIcnK362ZylDrIVMjFQzps/0AxMke+8MNPMArBlSAhn Zus6qb0nN0nJrDLkHQgYqnSvK9N7VUv/xFblEcOLBlciLhyNWkm92+q/M/xOvUVm nYkN3XgTI5QNxF7ZWDFHmwCNV27RraQZou0hG7yvyoILLMQE3MnMCNM1nZ9JIuBM cRsZLGqQ1XNaQljboRVIUjimzkcfYyTruhLosTIbwForp78JMzHHqVjtLJXPqUnR MS7KhGMj1f2mIswQzCv6F2PWEzNBbP4Gb67znKikKDs0RgyLRyfizFNFJSC58Xnt -K8jwHK1D8W3UepFf4K8xNFnhPoKWtWfJAgMBAAGjgbEwga4wNwYDVR0RBDAwLoIL -ZXhhbXBsZS5jb22HBH8AAAGGGXNwaWZmZTovL2V4YW1wbGUuY29tL2hvc3QwHwYD -UQEBBBgMFkEgVVRGOFN0cmluZyBFeHRlbnNpb24wGQYDUQECBBIMEEEgVVRGOCBF -eHRlbnNpb24wGQYDUQEDBBIWEEFuIElBNSBFeHRlbnNpb24wHAYDUQEEBBUaE0Eg -VmlzaWJsZSBFeHRlbnNpb24wDQYJKoZIhvcNAQEFBQADggEBAMIW2sK/rz+Ip8p5 -618MK5Er6BPDtsp0YMrb1mSG6uyDlmpi0zxjXp7ulSCvZ4ABClP94MaE1sJsBTBN -9mOWmKTyu5EjOVAsJ76XdS+/pkzItG7r+lPpAmnzb6sxdFDbhAhFJ6MkD6sL0WgY -b8YWH6MPpehw0CODbHGNWQGstITqY7fC8q6m+MoiGxFIh72Cgm4e1wxdaPoZ8Hmn -waRmJK8a2RIe4ZSaSwbbOTcfy4loYvudDtJQNUpsnzJS9yjt2RvO++fsKa0lMEni -RqslPF6ZqqhgV4dLoteAQlfE6q77VVgxIt3QKveV+Roq34GFtO8Xu7ok9wQ9oy4f -ILb53wI= +K8jwHK1D8W3UepFf4K8xNFnhPoKWtWfJAgMBAAGjgZYwgZMwHAYDVR0RBBUwE4IL +ZXhhbXBsZS5jb22HBH8AAAEwHwYDUQEBBBgMFkEgVVRGOFN0cmluZyBFeHRlbnNp +b24wGQYDUQECBBIMEEEgVVRGOCBFeHRlbnNpb24wGQYDUQEDBBIWEEFuIElBNSBF +eHRlbnNpb24wHAYDUQEEBBUaE0EgVmlzaWJsZSBFeHRlbnNpb24wDQYJKoZIhvcN +AQELBQADggEBAGU/iA6saupEaGn/veVNCknFGDL7pst5D6eX/y9atXlBOdJe7ZJJ +XQRkeHJldA0khVpzH7Ryfi+/25WDuNz+XTZqmb4ppeV8g9amtqBwxziQ9UUwYrza +eDBqdXBaYp/iHUEHoceX4F44xuo80BIqwF0lD9TFNUFoILnF26ajhKX0xkGaiKTH +6SbjBfHoQVMzOHokVRWregmgNycV+MAI9Ne9XkIZvdOYeNlcS9drZeJI3szkiaxB +WWaWaAr5UU2Z0yUCZnAIDMRcIiUbSEjIDz504sSuCzTctMOxWZu0r/0UrXRzwZZi +HAaKm3MUmBh733ChP4rTB58nr5DEr5rJ9P8= -----END CERTIFICATE----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawuri.cnf b/builtin/credential/cert/test-fixtures/root/rootcawuri.cnf new file mode 100644 index 000000000000..bb15540ca7e4 --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawuri.cnf @@ -0,0 +1,18 @@ +[ req ] +default_bits = 2048 +encrypt_key = no +prompt = no +default_md = sha256 +req_extensions = req_v3 +distinguished_name = dn + +[ dn ] +CN = example.com + +[ req_v3 ] +subjectAltName = @alt_names + +[ alt_names ] +DNS.1 = example.com +IP.1 = 127.0.0.1 +URI.1 = spiffe://example.com/host diff --git a/builtin/credential/cert/test-fixtures/root/rootcawuri.csr b/builtin/credential/cert/test-fixtures/root/rootcawuri.csr new file mode 100644 index 000000000000..0ababe1ce027 --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawuri.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICpTCCAY0CAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDEtoz6THzA8RFNJ+wu40Pa30Inyprv3xRGYA71 +0T3yLrWUA0xaS8i7HHXDaEVmtHi7I+dFRqGwCgtDLY3sXN1C1t/U6V6xhhQ1hRW7 +PJhbGfsfi8uBx83amWiSMlmEBYPryQzPS+8mmRErBi6EdmgbdGWV5IcovMddDxE1 +Npc1vwmTxDUOe6mRSa8UkaR9nwFl8LTz9clIkGlOJLHWD2oX15PVr7SKYco+MrIh +HLKkYMgATFJ05EKLyRxO/lQWD6ibUYJuGhFeNyjk34swl3uoWQBGndxcs2BQP4OL +EfnsoXVDrHwjZ1FWSu/Bf6TfKvwo5It1IZLnm+cCTqxCnaLRAgMBAAGgSjBIBgkq +hkiG9w0BCQ4xOzA5MDcGA1UdEQQwMC6CC2V4YW1wbGUuY29thwR/AAABhhlzcGlm +ZmU6Ly9leGFtcGxlLmNvbS9ob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQBw2y7bPrLk +B7DrZRvO/s8yj/Mi2iS/q3KEACEUxHTXH9GrqnQJ1n00WjaEu5JgXW8F08738nj/ +QhO5IM9ZMBtFyt9/GguZzGWnGUGUvtfM/ps/qzF6lAnjxYnFfqJeDWhg4SQsW6ZW +eFZ3S1kx0iQjy+Y7oWZNObbgDhszdJa6swN1WJBB8BZuiDJYXMBzfWdR6aZStJ0Z +lUHyaQbILXRc+meuDY7KeILJhldlE8oU/NENO1w1WXcsseXg8790pPYg+uR/uXg0 +0iWPtqgjO+55eAvkZ5nY0N/kABV1oaCB8bVs6/2HPqquPX6c+xkcUI/HY8SJgWzk +AHCG7VIB4W94 +-----END CERTIFICATE REQUEST----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawuricert.pem b/builtin/credential/cert/test-fixtures/root/rootcawuricert.pem new file mode 100644 index 000000000000..171f4de004ea --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawuricert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC6jCCAdKgAwIBAgIJAJIiPq+77hekMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV +BAMTC2V4YW1wbGUuY29tMB4XDTE4MDMzMTE2MTE0NVoXDTE5MDMzMTE2MTE0NVow +FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDEtoz6THzA8RFNJ+wu40Pa30Inyprv3xRGYA710T3yLrWUA0xaS8i7 +HHXDaEVmtHi7I+dFRqGwCgtDLY3sXN1C1t/U6V6xhhQ1hRW7PJhbGfsfi8uBx83a +mWiSMlmEBYPryQzPS+8mmRErBi6EdmgbdGWV5IcovMddDxE1Npc1vwmTxDUOe6mR +Sa8UkaR9nwFl8LTz9clIkGlOJLHWD2oX15PVr7SKYco+MrIhHLKkYMgATFJ05EKL +yRxO/lQWD6ibUYJuGhFeNyjk34swl3uoWQBGndxcs2BQP4OLEfnsoXVDrHwjZ1FW +Su/Bf6TfKvwo5It1IZLnm+cCTqxCnaLRAgMBAAGjOzA5MDcGA1UdEQQwMC6CC2V4 +YW1wbGUuY29thwR/AAABhhlzcGlmZmU6Ly9leGFtcGxlLmNvbS9ob3N0MA0GCSqG +SIb3DQEBBQUAA4IBAQDhR59hSpL4k4wbK3bA17YoNwFBsDpDcoU2iB9NDUTj+j+T +Rgumt+VHtgxuGRDFPQ+0D2hmJJHNCHKulgeDKVLtY/c5dCEsk8epLQwoCd/pQsNR +Lj102g83rCrU0pfTFjAUoecmHBFt7GDxVyWDsJgGItMatPQuWyZXTzO8JdhCfpMP +m7z65VYZjIPgevpSR5NVJDU8u2jRCkRQBFqOXotJS6EObu4P8aly4YhwiMf1B0L8 +60XHbBksOQSZOky37uFhaab78bAu5nd2kN1K4qSObTJshCZAwRYk0XdCjDrMcZRJ +Fp+yygib+K8e7o71Co0zUdSU0yxOKGsWvjz1BUVl +-----END CERTIFICATE----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawurikey.pem b/builtin/credential/cert/test-fixtures/root/rootcawurikey.pem new file mode 100644 index 000000000000..81ac978291d7 --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawurikey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDEtoz6THzA8RFN +J+wu40Pa30Inyprv3xRGYA710T3yLrWUA0xaS8i7HHXDaEVmtHi7I+dFRqGwCgtD +LY3sXN1C1t/U6V6xhhQ1hRW7PJhbGfsfi8uBx83amWiSMlmEBYPryQzPS+8mmREr +Bi6EdmgbdGWV5IcovMddDxE1Npc1vwmTxDUOe6mRSa8UkaR9nwFl8LTz9clIkGlO +JLHWD2oX15PVr7SKYco+MrIhHLKkYMgATFJ05EKLyRxO/lQWD6ibUYJuGhFeNyjk +34swl3uoWQBGndxcs2BQP4OLEfnsoXVDrHwjZ1FWSu/Bf6TfKvwo5It1IZLnm+cC +TqxCnaLRAgMBAAECggEAYLdYbR/6HmroFMVSLGN000H9ps7IirNlpoxIDrhH+rDY +eeN9QNAN62E8zUyRAsQsr+YhKUBm8sSdcPQO2W13JAu9lVMAScwgV4gNfTd3uSL3 +AzWaYz63iYjvjyHOPUjw6Za6A5nUBWgwtrSdXmdRHF6IK8Bma7MVWj20OjOS+MsM +ScXk+yMTzpQYZ+AhP6rgcccn6djtk+Mqrpa7yW5cTDkQ0+/MF0KR7tYUbakRSimI +Ph6e+zFt4infOWP5fDr0oSpMXA2chh0INTtxbltnJzvaaPF8LSzyihWTZszABc84 +Ckgrvmt5DViYbmfKHk0csS/xF/wdygfkkJHML8l/IQKBgQD9CMaDgfpM78uH8Kgm +Ja/ANu4Te5zO/n5E96PHdvCN+m7pCMgYuXuKgXDADgD1O6MItzDnEpkubluffARf +1eJyw9ner0tTAs8bZgtKdLQvaghq5Afk1+m8XDTskJsVLVGrozvJLuabPqnZrkRH +AxLdZjiAh6z2csFVYTQnMQSfhQKBgQDHBMjapcDx9y/jUq/yoFvwkdT3THQO9JgK +XC5NOHGVhyT3695wpqi/ANA4b8P9MmAzcUkT8a3jcqV87OIQmK3Y1oGvjHQCKS60 +OYE9TadpxwW2uzxS5T7YegXf5L3uHinoWHlLklN+Q9pvJStw4QrDzhd8rtcZA+FN +KBmjzYdJ3QKBgQDYutl97qi7mXEVgPYlpoYA94u4OFq5mZYB8LLhuGiW03iINbNe +KhE9M12lwtjjNC+S2YYThgSaln/3/LuqcoLBlitY54B3G6LVbvQg1BE5w3JuS97P +Dnjvk3LpZXrQCr83altdGMUBGA1XnEJzKJjR9ipTPOLTPLuIK/gF0aCKGQKBgQCm +ZFitfZGge4M9Mt/KIcpciwCcNf5+ln8bglBv3XYRhykgYsLaOmyxLLPpy3/4DAsk +V1263//7PtofZUnoiE4pEcbhh7NiLx5OLhngsDD9Hhmn2kkoIWR2xyZsN6mYEP4G +tRnMVi2aTo6tCE2WlYBTjtZSNze9QWI4CQPO0MKAvQKBgQCzpJAJXl04zQv9S5uW +pH3xShmd0Zjv9tNyOVNqWUeg47IFzNC2w/6FqYkhd9C4DCAibzPx7WkVjYAR+ivY +NQv1usVhV3maJX5rw+C4Zck8kAmiqMbLacUVdy/5E2Mbk7xqjAvu+qrMFdSk/2GR +raR1xOEvE0cKWIwr8c8wIva4wA== +-----END PRIVATE KEY----- diff --git a/website/source/api/auth/cert/index.html.md b/website/source/api/auth/cert/index.html.md index 9d94aef3dd23..adf8efcfda4c 100644 --- a/website/source/api/auth/cert/index.html.md +++ b/website/source/api/auth/cert/index.html.md @@ -34,6 +34,11 @@ Sets a CA cert and associated parameters in a role name. (https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is a comma-separated list of patterns. Authentication requires at least one Name matching at least one pattern. If not set, defaults to allowing all names. +- `allowed_uris` `(string: "")` - Constrain the Alternative Names in + the client certificate with a [globbed pattern] + (https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is + a comma-separated list of URI patterns. Authentication requires at least one URI + matching at least one pattern. If not set, defaults to allowing all URIs. - `required_extensions` `(string: "" or array:[])` - Require specific Custom Extension OIDs to exist and match the pattern. Value is a comma separated string or array of `oid:value`. Expects the extension value to be some type From 7ce52e3f46c6a12f98b5bdab44050b029899f825 Mon Sep 17 00:00:00 2001 From: Nic Jackson Date: Thu, 26 Apr 2018 07:40:43 +0100 Subject: [PATCH 3/6] Added breakout for cert validation parameters, refactored tests Updated documentation --- builtin/credential/cert/backend_test.go | 187 +++++++++++++----- builtin/credential/cert/path_certs.go | 52 ++++- builtin/credential/cert/path_login.go | 103 +++++++++- .../cert/test-fixtures/root/rootcacert.srl | 2 +- .../cert/test-fixtures/root/rootcawdns.cnf | 17 ++ .../cert/test-fixtures/root/rootcawdns.csr | 27 +++ .../test-fixtures/root/rootcawdnscert.pem | 23 +++ .../cert/test-fixtures/root/rootcawdnskey.pem | 52 +++++ .../cert/test-fixtures/root/rootcawemail.cnf | 17 ++ .../cert/test-fixtures/root/rootcawemail.csr | 27 +++ .../test-fixtures/root/rootcawemailcert.pem | 23 +++ .../test-fixtures/root/rootcawemailkey.pem | 52 +++++ .../cert/test-fixtures/root/rootcawext.cnf | 5 - website/source/api/auth/cert/index.html.md | 17 ++ 14 files changed, 532 insertions(+), 72 deletions(-) create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawdns.cnf create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawdns.csr create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawdnscert.pem create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawdnskey.pem create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawemail.cnf create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawemail.csr create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawemailcert.pem create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawemailkey.pem diff --git a/builtin/credential/cert/backend_test.go b/builtin/credential/cert/backend_test.go index ae766ebb80f7..c783a70f7000 100644 --- a/builtin/credential/cert/backend_test.go +++ b/builtin/credential/cert/backend_test.go @@ -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"})...) @@ -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"), @@ -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), }, }) @@ -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", @@ -955,46 +980,104 @@ 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", "", "", "2.1.1.1:The Wrong Value", false), + 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:*,2.1.1.2: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:", 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:,2.1.1.2:*", 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", "example.com", "", "2.1.1.1:A UTF8String Extension", 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", "example.com", "", "2.1.1.1:*,2.1.1.2:A UTF8*", false), + 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", "example.com", "", "1.2.3.45:*", false), + testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "1.2.3.45:*"}, false), + testAccStepLoginInvalid(t, connState), + 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", "example.com", "", "2.1.1.1:The Wrong Value", false), + 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", "example.com", "", "2.1.1.1:*,2.1.1.2:The Wrong Value", false), + 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", allowed{dns: "notincert.com"}, false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "invalid", "", "2.1.1.1:A UTF8String Extension", false), + testAccStepCert(t, "web", ca, "foo", allowed{dns: "abc"}, 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{dns: "*.example.com"}, false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "invalid", "", "1.2.3.45:*", 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: "invalid@notincert.com"}, false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "invalid", "", "2.1.1.1:The Wrong Value", false), + testAccStepCert(t, "web", ca, "foo", allowed{emails: "abc"}, 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{emails: "*.example.com"}, false), testAccStepLoginInvalid(t, connState), }, }) } // Test a self-signed client with URI alt names (root CA) that is trusted -func TestBackend_URI_singleCert(t *testing.T) { +func TestBackend_uri_singleCert(t *testing.T) { connState, err := testConnState( "test-fixtures/root/rootcawuricert.pem", "test-fixtures/root/rootcawurikey.pem", @@ -1010,15 +1093,15 @@ func TestBackend_URI_singleCert(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ Backend: testFactory(t), Steps: []logicaltest.TestStep{ - testAccStepCert(t, "web", ca, "foo", "", "spiffe://example.com/*", "", false), + testAccStepCert(t, "web", ca, "foo", allowed{uris: "spiffe://example.com/*"}, false), testAccStepLogin(t, connState), - testAccStepCert(t, "web", ca, "foo", "", "spiffe://example.com/host", "", false), + testAccStepCert(t, "web", ca, "foo", allowed{uris: "spiffe://example.com/host"}, false), testAccStepLogin(t, connState), - testAccStepCert(t, "web", ca, "foo", "", "spiffe://example.com/invalid", "", false), + testAccStepCert(t, "web", ca, "foo", allowed{uris: "spiffe://example.com/invalid"}, false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "", "abc", "", false), + testAccStepCert(t, "web", ca, "foo", allowed{uris: "abc"}, false), testAccStepLoginInvalid(t, connState), - testAccStepCert(t, "web", ca, "foo", "", "http://www.google.com", "", false), + testAccStepCert(t, "web", ca, "foo", allowed{uris: "http://www.google.com"}, false), testAccStepLoginInvalid(t, connState), }, }) @@ -1038,9 +1121,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"), @@ -1219,20 +1302,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 certificate + emails string // allowed email names in the certificate + uris string // allowed uris in SAN section of the certificate + ext string // required extensions in the certificate +} + func testAccStepCert( - t *testing.T, name string, cert []byte, policies, allowedNames, allowedURIs, 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, - "allowed_uris": allowedURIs, - "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": testData.dns, + "allowed_emails": testData.emails, + "allowed_uris": testData.uris, + "required_extensions": testData.ext, + "lease": 1000, }, Check: func(resp *logical.Response) error { if resp == nil && expectError { diff --git a/builtin/credential/cert/path_certs.go b/builtin/credential/cert/path_certs.go index 7dc660a9eee4..6b75e71bcffd 100644 --- a/builtin/credential/cert/path_certs.go +++ b/builtin/credential/cert/path_certs.go @@ -43,7 +43,27 @@ 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, +allowed_email, allowed_uris.`, + }, + + "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": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `A comma-separated list of DNS names. +At least one must exist in the SANs. Supports globbing.`, + }, + + "allowed_emails": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `A comma-separated list of Email Addresses. +At least one must exist in the SANs. Supports globbing.`, }, "allowed_uris": &framework.FieldSchema{ @@ -152,15 +172,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, - "allowed_uris": cert.AllowedURIs, - "required_extensions": cert.RequiredExtensions, + "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": cert.AllowedDNS, + "allowed_emails": cert.AllowedEmails, + "allowed_uris": cert.AllowedURIs, + "required_extensions": cert.RequiredExtensions, }, }, nil } @@ -171,6 +194,9 @@ 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) + allowedDNS := d.Get("allowed_dns").([]string) + allowedEmails := d.Get("allowed_emails").([]string) allowedURIs := d.Get("allowed_uris").([]string) requiredExtensions := d.Get("required_extensions").([]string) @@ -245,6 +271,9 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr DisplayName: displayName, Policies: policies, AllowedNames: allowedNames, + AllowedCommonNames: allowedCommonNames, + AllowedDNS: allowedDNS, + AllowedEmails: allowedEmails, AllowedURIs: allowedURIs, RequiredExtensions: requiredExtensions, TTL: ttl, @@ -277,6 +306,9 @@ type CertEntry struct { MaxTTL time.Duration Period time.Duration AllowedNames []string + AllowedCommonNames []string + AllowedDNS []string + AllowedEmails []string AllowedURIs []string RequiredExtensions []string } diff --git a/builtin/credential/cert/path_login.go b/builtin/credential/cert/path_login.go index b633848f40f3..2e14c86abd60 100644 --- a/builtin/credential/cert/path_login.go +++ b/builtin/credential/cert/path_login.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "strings" + "time" "github.com/hashicorp/vault/helper/certutil" "github.com/hashicorp/vault/helper/policyutil" @@ -71,6 +72,11 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, data *fra return nil, nil } + ttl := matched.Entry.TTL + if ttl == 0 { + ttl = b.System().DefaultLeaseTTL() + } + clientCerts := req.Connection.ConnState.PeerCertificates if len(clientCerts) == 0 { return logical.ErrorResponse("no client certificate found"), nil @@ -95,8 +101,7 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, data *fra }, LeaseOptions: logical.LeaseOptions{ Renewable: true, - TTL: matched.Entry.TTL, - MaxTTL: matched.Entry.MaxTTL, + TTL: ttl, }, Alias: &logical.Alias{ Name: clientCerts[0].SerialNumber.String(), @@ -104,6 +109,20 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, data *fra }, } + if matched.Entry.MaxTTL > time.Duration(0) { + // Cap maxTTL to the sysview's max TTL + maxTTL := matched.Entry.MaxTTL + if maxTTL > b.System().MaxLeaseTTL() { + maxTTL = b.System().MaxLeaseTTL() + } + + // Cap TTL to MaxTTL + if resp.Auth.TTL > maxTTL { + resp.AddWarning(fmt.Sprintf("Effective TTL of '%s' exceeded the effective max_ttl of '%s'; TTL value is capped accordingly", (resp.Auth.TTL / time.Second), (maxTTL / time.Second))) + resp.Auth.TTL = maxTTL + } + } + // Generate a response return resp, nil } @@ -156,11 +175,17 @@ func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *f return nil, fmt.Errorf("policies have changed, not renewing") } - resp := &logical.Response{Auth: req.Auth} - resp.Auth.TTL = cert.TTL - resp.Auth.MaxTTL = cert.MaxTTL - resp.Auth.Period = cert.Period - return resp, nil + // If a period is provided, set that as part of resp.Auth.Period and return a + // response immediately. Let expiration manager handle renewal from there on. + if cert.Period > time.Duration(0) { + resp := &logical.Response{ + Auth: req.Auth, + } + resp.Auth.Period = cert.Period + return resp, nil + } + + return framework.LeaseExtend(cert.TTL, cert.MaxTTL, b.System())(ctx, req, d) } func (b *backend) verifyCredentials(ctx context.Context, req *logical.Request, d *framework.FieldData) (*ParsedCert, *logical.Response, error) { @@ -241,6 +266,9 @@ func (b *backend) verifyCredentials(ctx context.Context, req *logical.Request, d func (b *backend) matchesConstraints(clientCert *x509.Certificate, trustedChain []*x509.Certificate, config *ParsedCert) bool { return !b.checkForChainInCRLs(trustedChain) && b.matchesNames(clientCert, config) && + b.matchesCommonName(clientCert, config) && + b.matchesDNSName(clientCert, config) && + b.matchesEmail(clientCert, config) && b.matchesURIs(clientCert, config) && b.matchesCertificateExtensions(clientCert, config) } @@ -274,6 +302,61 @@ func (b *backend) matchesNames(clientCert *x509.Certificate, config *ParsedCert) return false } +// matchesCommonName verifies that the certificate matches at least one configured +// allowed common name +func (b *backend) matchesCommonName(clientCert *x509.Certificate, config *ParsedCert) bool { + // Default behavior (no names) is to allow all names + if len(config.Entry.AllowedCommonNames) == 0 { + return true + } + // At least one pattern must match at least one name if any patterns are specified + for _, allowedCommonName := range config.Entry.AllowedCommonNames { + if glob.Glob(allowedCommonName, clientCert.Subject.CommonName) { + return true + } + } + + return false +} + +// matchesDNSName verifies that the certificate matches at least one configured +// allowed dns name +func (b *backend) matchesDNSName(clientCert *x509.Certificate, config *ParsedCert) bool { + // Default behavior (no names) is to allow all names + if len(config.Entry.AllowedDNS) == 0 { + return true + } + // At least one pattern must match at least one name if any patterns are specified + for _, allowedDNS := range config.Entry.AllowedDNS { + for _, name := range clientCert.DNSNames { + if glob.Glob(allowedDNS, name) { + return true + } + } + } + + return false +} + +// matchesDNSName verifies that the certificate matches at least one configured +// allowed dns name +func (b *backend) matchesEmail(clientCert *x509.Certificate, config *ParsedCert) bool { + // Default behavior (no names) is to allow all names + if len(config.Entry.AllowedEmails) == 0 { + return true + } + // At least one pattern must match at least one name if any patterns are specified + for _, allowedEmail := range config.Entry.AllowedEmails { + for _, email := range clientCert.EmailAddresses { + if glob.Glob(allowedEmail, email) { + return true + } + } + } + + return false +} + // matchesURIs verifies that the certificate matches at least one configured // allowed uri func (b *backend) matchesURIs(clientCert *x509.Certificate, config *ParsedCert) bool { @@ -333,7 +416,7 @@ func (b *backend) loadTrustedCerts(ctx context.Context, storage logical.Storage, trustedNonCAs = make([]*ParsedCert, 0) names, err := storage.List(ctx, "cert/") if err != nil { - b.Logger().Error("failed to list trusted certs", "error", err) + b.Logger().Error("cert: failed to list trusted certs", "error", err) return } for _, name := range names { @@ -343,12 +426,12 @@ func (b *backend) loadTrustedCerts(ctx context.Context, storage logical.Storage, } entry, err := b.Cert(ctx, storage, strings.TrimPrefix(name, "cert/")) if err != nil { - b.Logger().Error("failed to load trusted cert", "name", name, "error", err) + b.Logger().Error("cert: failed to load trusted cert", "name", name, "error", err) continue } parsed := parsePEM([]byte(entry.Certificate)) if len(parsed) == 0 { - b.Logger().Error("failed to parse certificate", "name", name) + b.Logger().Error("cert: failed to parse certificate", "name", name) continue } if !parsed[0].IsCA { diff --git a/builtin/credential/cert/test-fixtures/root/rootcacert.srl b/builtin/credential/cert/test-fixtures/root/rootcacert.srl index 282f3267bb3e..1c85d6318e03 100644 --- a/builtin/credential/cert/test-fixtures/root/rootcacert.srl +++ b/builtin/credential/cert/test-fixtures/root/rootcacert.srl @@ -1 +1 @@ -92223EAFBBEE17A4 +92223EAFBBEE17AF diff --git a/builtin/credential/cert/test-fixtures/root/rootcawdns.cnf b/builtin/credential/cert/test-fixtures/root/rootcawdns.cnf new file mode 100644 index 000000000000..3c576a95c50e --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawdns.cnf @@ -0,0 +1,17 @@ +[ req ] +default_bits = 2048 +encrypt_key = no +prompt = no +default_md = sha256 +req_extensions = req_v3 +distinguished_name = dn + +[ dn ] +CN = example.com + +[ req_v3 ] +subjectAltName = @alt_names + +[ alt_names ] +IP.1 = 127.0.0.1 +DNS.1 = example.com diff --git a/builtin/credential/cert/test-fixtures/root/rootcawdns.csr b/builtin/credential/cert/test-fixtures/root/rootcawdns.csr new file mode 100644 index 000000000000..b56d3231404d --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawdns.csr @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEijCCAnICAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQDUJ6s97BFxR295bCjpwQ85Vo8DnBFa/awNH107 +QFn/zw0ZDdJMLtEBc/bw7pTYw5ulKbiZDFrmzPEY+QZlo+t1TeWgPRJg0CbYNukS +aNv0vKXjDXYwbrCyOvZucy8hte6IKjZfH+kAsgbbUxfD75BCKsxMxbVHkg0W9Ma2 +pnZj/kpvQE5lkMj5mDvtWdfCRsVg4zL6jhRHkPZ6fOkF3mrfTbQu3oyOcbKLEE/G +t3QRKw3uv0vMDmhg62ZPvD1k70UMjUV2MVqEPZuWY7/bbW8OsfzMyBOGY9LLp7QS +krxWYRj6SPUR4f1bZq7pRbqOfS0okq/XDLf1k6Na5cT6iNdyjEVdSJl7vR7kSreX +8hkwK46Oup8v/vJLu/cRDCpAas0gJJkJDPt5114V0/Xww7EFxs5GijXP8i5RLlgK +/nRscbK+fgjQOnQ5cp0pcP8HAriy2vil7E0fQvMvt5QTyINEYgiYaCIT9WGRC8Xo +WcoUGI2vyrGy6RU6A3/TKeBLtikaSPjFKa1dFTAHfrUkTBpfqc+sbiJ334Bvucg5 +WyS8oAC5Vf++iMnETSdzx1k0/QARVLD38PO8wPaPU1M2XaSA+RHTB9SGFc4VTauT +B167NLlmgJHYuhp+KM1RTy1TEoDlJh2qKj21BLcR1GJ0KgDze6Vpf9xdRTdqMpo2 +h20wdQIDAQABoC8wLQYJKoZIhvcNAQkOMSAwHjAcBgNVHREEFTAThwR/AAABggtl +eGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAgEAGtds+2IUVKzw9bi130mBbb9K +CrXw2NXSp+LJwneIEy0bjAaNr7zCGQsj7q57qFwjc7vLTtGRheP5myrAOq00lp8J +1sGZSETS/y4yeITLZSYWVq2dtF/hY9I+X3uOoibdsQgzYqhBcUr4oTDapf1ZEs0i +wA2J5IcasfaBpWFc9wRN79BBACLGyCbX6VwUISrGe3Hgzkeqtg97cU62ecQsgXiZ +LdQgERvC0wSfAmI4lGulXi1oYYSRxXQ8pEKEAMeJrJVQfvhdbS/o4Bdf3Yj6ibtD +tFSdKLcdRCfMQBEHNpSh665LfBbwU55Fh89tBdGmf6uqsimUY6AxNncnLsc1Kq6F +oINXix3GsBNmCahDeHdGOlNjw0Lpl0m6bnu6LXSDwwuNWAEdDfEmxR+5T/GkGxcG +TTWPwEkpnCe4VmGl9Y10uPSvqneNsdNWjDVK4BeW4VSf9Lp1Zeme1dYFvpyzow+r +4ogpvMPf5vy5I/0HCEf1KlaPyhs8ZGK6YBGaeEDYSaysAWJfYm8eiqwUuKYj/FUe +G3KkaFpOGsQHFNRtG8GukV3r2AK97HFHKNfygZ2xvk5isXz2ZsNX1/J0+GGjalJl +cWBBEiXFM94XJHE9rACsL2UKn8cWCh9lHNLlePOkQuoNY9CUd63xx4Hg97XWP3+U +DhpG7CADsKcPJfbMgrk= +-----END CERTIFICATE REQUEST----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawdnscert.pem b/builtin/credential/cert/test-fixtures/root/rootcawdnscert.pem new file mode 100644 index 000000000000..2ce633e3a452 --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawdnscert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIJAJIiPq+77herMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV +BAMTC2V4YW1wbGUuY29tMB4XDTE4MDQyNjEyMDEzMFoXDTE5MDQyNjEyMDEzMFow +FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDUJ6s97BFxR295bCjpwQ85Vo8DnBFa/awNH107QFn/zw0ZDdJMLtEB +c/bw7pTYw5ulKbiZDFrmzPEY+QZlo+t1TeWgPRJg0CbYNukSaNv0vKXjDXYwbrCy +OvZucy8hte6IKjZfH+kAsgbbUxfD75BCKsxMxbVHkg0W9Ma2pnZj/kpvQE5lkMj5 +mDvtWdfCRsVg4zL6jhRHkPZ6fOkF3mrfTbQu3oyOcbKLEE/Gt3QRKw3uv0vMDmhg +62ZPvD1k70UMjUV2MVqEPZuWY7/bbW8OsfzMyBOGY9LLp7QSkrxWYRj6SPUR4f1b +Zq7pRbqOfS0okq/XDLf1k6Na5cT6iNdyjEVdSJl7vR7kSreX8hkwK46Oup8v/vJL +u/cRDCpAas0gJJkJDPt5114V0/Xww7EFxs5GijXP8i5RLlgK/nRscbK+fgjQOnQ5 +cp0pcP8HAriy2vil7E0fQvMvt5QTyINEYgiYaCIT9WGRC8XoWcoUGI2vyrGy6RU6 +A3/TKeBLtikaSPjFKa1dFTAHfrUkTBpfqc+sbiJ334Bvucg5WyS8oAC5Vf++iMnE +TSdzx1k0/QARVLD38PO8wPaPU1M2XaSA+RHTB9SGFc4VTauTB167NLlmgJHYuhp+ +KM1RTy1TEoDlJh2qKj21BLcR1GJ0KgDze6Vpf9xdRTdqMpo2h20wdQIDAQABoyAw +HjAcBgNVHREEFTAThwR/AAABggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQUFAAOC +AQEA2JswcCYtHvOm2QmSEVeFcCeVNkzr35FXATamJv0oMMjjUFix78MW03EW6vJa +E52e3pBvRdy+k2fuq/RtHIUKzB6jNbv0Vds26Dq+pmGeoaQZOW94/Wht7f9pZgBi +IRPBg9oACtyNAuDsCOdetOyvyoU29sjUOUoQZbEXF+FK4lRJrEmZUJHbp/BOD58V +mQRtjTMjQlZZropqBQmooMRYU0qgWHaIjyoQpu2MgEj3+/1b1IX6SCfRuit0auh/ +YI3/cCtyAG/DpZ6zfyXuyY+iN+l8B6t0nXyV3g8JgBWYPGJv1hgVIgnnqlwuL517 +mEAT5RnHCNJQNuzS1dwfuBrX3w== +-----END CERTIFICATE----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawdnskey.pem b/builtin/credential/cert/test-fixtures/root/rootcawdnskey.pem new file mode 100644 index 000000000000..15db567e0378 --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawdnskey.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDUJ6s97BFxR295 +bCjpwQ85Vo8DnBFa/awNH107QFn/zw0ZDdJMLtEBc/bw7pTYw5ulKbiZDFrmzPEY ++QZlo+t1TeWgPRJg0CbYNukSaNv0vKXjDXYwbrCyOvZucy8hte6IKjZfH+kAsgbb +UxfD75BCKsxMxbVHkg0W9Ma2pnZj/kpvQE5lkMj5mDvtWdfCRsVg4zL6jhRHkPZ6 +fOkF3mrfTbQu3oyOcbKLEE/Gt3QRKw3uv0vMDmhg62ZPvD1k70UMjUV2MVqEPZuW +Y7/bbW8OsfzMyBOGY9LLp7QSkrxWYRj6SPUR4f1bZq7pRbqOfS0okq/XDLf1k6Na +5cT6iNdyjEVdSJl7vR7kSreX8hkwK46Oup8v/vJLu/cRDCpAas0gJJkJDPt5114V +0/Xww7EFxs5GijXP8i5RLlgK/nRscbK+fgjQOnQ5cp0pcP8HAriy2vil7E0fQvMv +t5QTyINEYgiYaCIT9WGRC8XoWcoUGI2vyrGy6RU6A3/TKeBLtikaSPjFKa1dFTAH +frUkTBpfqc+sbiJ334Bvucg5WyS8oAC5Vf++iMnETSdzx1k0/QARVLD38PO8wPaP +U1M2XaSA+RHTB9SGFc4VTauTB167NLlmgJHYuhp+KM1RTy1TEoDlJh2qKj21BLcR +1GJ0KgDze6Vpf9xdRTdqMpo2h20wdQIDAQABAoICAQDJxszUQQC554I7TsZ+xBJx +q0Sr3zSWgOuxM2Jdpy+x38AKUx3vPRulsSBtN8yzeR9Ab7TVQ231U3f/E2GlK8kW +sTazN0KSd4ZqX5c+3iJM21s+3p/JIo3FhdS5aa2q9zjdoqBByry135wr3xScUu22 +MLRMVEG8x0jRy45vS1UQd1teAiBN8u1ijgp5DNjrOpohMxVaPeVFx7bU+pY58bdd +mK7FYP73v2VbY/EsA3FNntBKgQBbHFzjyR9uuI7/v53BeV9WMUxwt5OR7l8cGDHn +HRtdvPDtAWYMMf1PKOYdlY3HBbqn/nMUCk5TKPFs8dsQWqsI8lzIIVndauj0i0+0 +M/lVMXu4x48o5FfLa4HjkpcDxAU6QDHA9thaDkasZebixVH/p1ZJkLORl5jDLYkU +Av+B3i1efITwNYgosZNjPpw0PyYh9PV9JvB87d5wFpgISfZyRXpBVGeJbt6gg++8 +8/5A/GzSpGy0FhLcP3vuVTcX2VOexjqeaoi4U3cHrbWv/wNj5a4BNk5EJT8fVeSb ++Emqydl9u3n2E315GPC8kwxdE3r3hGrWdZQn9byGvqzwDaLWXQLQWvQN4GOpGTrP +Yxj2Oi8s1MJHkppj4eo52O4J7cBlAJn3RFmlCKGOoWJZMdPktp/gWeT+xIGSaa21 +qB+l/ZFEWLPMxdTBMGFmYQKCAQEA8DgozaZBXr7mb1H25EbW9YmtLc61JMapQrZb +ObygiGR6RZsxCXEvGdhvvmwpO8rA9wAOWqI8NV5GU8EvuRuwvGoX4HqbXkB6ZcyC +6RuZzki2lrKVGUaLc1v6MyhX4IzrqTYWDgQvwd9lMcUGR7r007KPE5ft4v3/TuxQ +qPKxQE7NO2xnTloUchd5g0/d975GZi0g6XDecFOuj43Pi0c/wRcFH6zfVirdcm+M +yP9CsJ/LUgtV1voLqyhfybwHvzpxJ0l25Fw+P85I4czosBp+FaFAwogxZEDnY8Fr +Hqcwdc7vwGDjTbtflDsUdppt2h8nD8bBZGysG8+P8HAt3i5D+QKCAQEA4heKueRQ +Y8nTZlmRSRtB6usRAeymQBJjO+yWwu/06Efg8VW5QRwtP0sx+syrLaQDy8MT07II +XQZmq55xATWbHCxULiceIY2KG5LHCovVotYAll8ov58exJva19C7/41uVrkl3H9j +xFLX0Bn3zMFKBOxKhygP2xqqEJdb1JJt27c2CbXvXOzqIZ4RCaNQdBdrlEiXQihR +JCGMUBfrYIwALQFzYuPGULhg77YcAi5owCPnfK+dDOOvMmW8BwPnRUc14WFIVV+m +dbY22WonLNPP055W5755Xl9RHKW1bcmIH6E4QZpMrlnd1UzPBQq1PJtcO3uRc5T6 +CMQSUmwMGSQ3XQKCAQBiuVHborY+8AnYOkFTc+GoK5rmtosvwA2UA0neoqz/IPw3 +Wx5+GOwYnSDfi6gukJdZa8Z6bS59aG9SwJSSaNTrulZxxTHRPIKRD8nFb7h4VN3l +dSNdreZl1KkxGSV0fbXkZvwNap8N+HeoSqbYF/fCgSHYFZqIrYadsvU7WfKK0Vf7 +UgPq6Y55jTg9RTeeN67LE0Txa5efZmTZTpi7Tt7exk0uxWdMDHXSMBIWEQIhgKqY +31u57C2bfA5R5FrytlwGn2SjWV2j7214jzQaG+kxjoIE8OALqbjvAHC7uk5qPE/A +KpGAQr93Ngik7baz7BWroC2eziK1k0o+sHvJUg5RAoIBABF+ftZ5axr9j+T4gzxj +5orV24AJnqeQhKsrWFMHHC0o+qfR2T7HflzKZbihQ5GJgl2u34be3LTN/P3Eibvt +OO5KI81aa4NvH0OY7NvNDB/IbU01WcLR/iB6asmONi3E9MezFdHk7YRQYLCSgdEP +F7ofyniAyhFLE+OqwolFN0jr+TtxH29SSZ+GSo0zXNNOyJ01rLaKxhSEoAXGhAj5 +bD4PQa1iMIMocR+7OJmWm7ZaUNwd/onzyCefJZhpXejHZMzmqSEqAIhVLBNQmm1m +iks2kkTmQR/jQjR0QgCXunewEtlIpixLedW6Vr5uIK3q240it5N48IvjGAPWpmz/ +l2UCggEBALRlARlBdYcPhWbcyhq9suq2PJqHVvNdP9vVTZpPgmWgxmj9mPQ731Q/ +UpRlBIk6U0qAxBXP9kzPqSivWhY8Jto80Jdr+/80PkdANDkpnXoDlxPk095sD2uN +Jv5FffFgMZH9MGpPTEuZ571/YtVi+1qFt0i3oazpF/g8gU23f2oxaX4xzsltVl8J +rWXYzmYE0i5Qiy81+zZ9dZlnmlKhcYpD6m2t/0hRAoNaoxOUV7WFcIzYIxpKvzYL +QTDL/Se2Ooc0xLQvM1oZ9/1NE2hpGQ/ipASEPlx9KO5ktYW7+LwdcSCMXtx84I/D +VQpWjPdILMpiVrB/9NsENTNv2DUvc+o= +-----END PRIVATE KEY----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawemail.cnf b/builtin/credential/cert/test-fixtures/root/rootcawemail.cnf new file mode 100644 index 000000000000..f679fb987900 --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawemail.cnf @@ -0,0 +1,17 @@ +[ req ] +default_bits = 2048 +encrypt_key = no +prompt = no +default_md = sha256 +distinguished_name = dn +req_extensions = req_v3 + +[ req_v3 ] +subjectAltName = @alt_names + +[ dn ] +CN = example.com + +[ alt_names ] +IP.1 = 127.0.0.1 +email = valid@example.com diff --git a/builtin/credential/cert/test-fixtures/root/rootcawemail.csr b/builtin/credential/cert/test-fixtures/root/rootcawemail.csr new file mode 100644 index 000000000000..44b191495f46 --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawemail.csr @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEkDCCAngCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQDO7stcdJQwUtVeJriwpAswDAirO827peSlgdxs +fW8X9M2hE9ihvESEILb7TRMRiFlDyQYg1BxKMrJ0DZmixFi8RvUCZbH6TFOUMsk+ +w1FhpzjuqAqxNQ51s7u30sfruJg7XN3YJLEPelom62wvzhvLXJFLQZlQCDrMx+PC +ofWs4IA7jR8JaXZjIGdkEU0GgRPy8zKPUe3dUBHi2UR4eKT4cRCn4IwCrFx4BQjV +AxNKNDGpe+fTVOzII/UX+FppDdGZZ4g0y3E1mQUEKkff4dKCK7vhlGJR9D+5v/V0 +/stwP72aXczijuVtnXXyli+oj24NaijoqQluNCD3MvV/INovLL2Tyk54H3/GvpU1 ++sJbpE2+UPh+Rh8DNkT6RPRguymJO8MSsdLt/qvVD8BlZ7I9V3XZlDKosCRTUyxf +jjFpa+VzB3nt7uFtIXZ9HNGhQIpOULvkFGizWV+tS8PpGdTFVzDjyWg0HUKWn8g8 +IiWR9S40h6mHjVuTuxA9tlO69PuTjGK7MlAvFTDaPC8seau1LUiqtQ+prnSLI0h1 +6GfI9W2G7BKKVPloErODhLcsOcwRcmaJVW+yBda3te8+cMBIvtQYKAYSCtg8qXws +xyfPLo4GChbGGRbRCuM3mB1lG1qHEivJ0vynsgolp0t8jaXSFVBVgYj+C6Vd9/hl +ieUcOwIDAQABoDUwMwYJKoZIhvcNAQkOMSYwJDAiBgNVHREEGzAZhwR/AAABgRF2 +YWxpZEBleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAgEAe1u3wYMMKaZ3d5Wz +jKH971CF3sl+KYl+oV0ekD8dbYe+szERgajn6Y5IYVxxi5o9UgeAWqnyHCiqsW8T +MQdUwMa67Ym/pHKSVoBlGePAKHqock0+iSsVBMcPpU9RkxdSW2aVtdb0DGfyB952 +t3dSb0LaITu30fe8p7lxrL0DKESbwd4N2XQE1F5Vf+1OodvpJinn4Wqzn45hqRf0 +imxrCgVjT5VtR+NRzKCK3Msmh+cJGpR3zgXwGKqgHLWzhvSoQwRWYE3apMK5xLk7 +N1sWVxEKK5+L/CDaMNGQFx5lPiCN3bUudCq4uSZcPLO5AuDpSeLKnknBrWA6HcbB +WvnwtKmHeVe2qogPViKGuwE16rnPlp9hysPl2ckmtqEsXRagIAh5fMI3OoRbZmVV +jfJm21U4YkUWuMKet3EU1StT6T8T6O7QEFA4w4s5+m3dsjDZ9iTuK9/dCs1xnIke +4uJYmh3YrNl8IjMffJuWxA+/de3JO1UljC2EAFxa5KAc24+qyeWwky4tMv72gTOp +6q3k2wnsrK5B1errRV37OLgxtoh1I3Rgp+0B77SOK/PpD/JJazJG5O9bBJOvHJc0 +STW9Td2CzgC2lKGfvkX6UYgVy/9HDq7/EKXP/G2f3kRik2NPUhGcnAH9nyL9SvpP ++T4CZ+FumDj5DulARk6arSq+uy4= +-----END CERTIFICATE REQUEST----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawemailcert.pem b/builtin/credential/cert/test-fixtures/root/rootcawemailcert.pem new file mode 100644 index 000000000000..f774a71827b8 --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawemailcert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID1TCCAr2gAwIBAgIJAJIiPq+77hevMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV +BAMTC2V4YW1wbGUuY29tMB4XDTE4MDQyNjEyMjE1MloXDTE5MDQyNjEyMjE1Mlow +FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDO7stcdJQwUtVeJriwpAswDAirO827peSlgdxsfW8X9M2hE9ihvESE +ILb7TRMRiFlDyQYg1BxKMrJ0DZmixFi8RvUCZbH6TFOUMsk+w1FhpzjuqAqxNQ51 +s7u30sfruJg7XN3YJLEPelom62wvzhvLXJFLQZlQCDrMx+PCofWs4IA7jR8JaXZj +IGdkEU0GgRPy8zKPUe3dUBHi2UR4eKT4cRCn4IwCrFx4BQjVAxNKNDGpe+fTVOzI +I/UX+FppDdGZZ4g0y3E1mQUEKkff4dKCK7vhlGJR9D+5v/V0/stwP72aXczijuVt +nXXyli+oj24NaijoqQluNCD3MvV/INovLL2Tyk54H3/GvpU1+sJbpE2+UPh+Rh8D +NkT6RPRguymJO8MSsdLt/qvVD8BlZ7I9V3XZlDKosCRTUyxfjjFpa+VzB3nt7uFt +IXZ9HNGhQIpOULvkFGizWV+tS8PpGdTFVzDjyWg0HUKWn8g8IiWR9S40h6mHjVuT +uxA9tlO69PuTjGK7MlAvFTDaPC8seau1LUiqtQ+prnSLI0h16GfI9W2G7BKKVPlo +ErODhLcsOcwRcmaJVW+yBda3te8+cMBIvtQYKAYSCtg8qXwsxyfPLo4GChbGGRbR +CuM3mB1lG1qHEivJ0vynsgolp0t8jaXSFVBVgYj+C6Vd9/hlieUcOwIDAQABoyYw +JDAiBgNVHREEGzAZhwR/AAABgRF2YWxpZEBleGFtcGxlLmNvbTANBgkqhkiG9w0B +AQUFAAOCAQEAp2T99t93hxPyCDaqfTF0lsdzIgxZ5GkSzYTYQ2pekLfMDUUy4WFQ +AppdnSJSpm6b+xWO2DkO8UAgOdSEORf/Qpfm+UpHaEYZlQiWQ0zNmIQgBoh6indU +bEZKeL6aAOfIshPNfmqjFt+DpEClrQvCHJggG/rB77Ujj6hPY2+8h4JjbjeX7Pe9 +oUEx9LpZ5Qpo6PK5vB537PP7Q2qp2PIr29DLz1VeLCbqUnV+j7qT0T3hhqurnpTA +QUiRZI0etgeP/B5lw/S4AWijq+R6RasdPAS4UNHsYK+PSGiqdhW/bJvSx5UBXQbk +DuY2A4kdv60H5Aw45/F6enH2Fg1kg7PlQA== +-----END CERTIFICATE----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawemailkey.pem b/builtin/credential/cert/test-fixtures/root/rootcawemailkey.pem new file mode 100644 index 000000000000..13b1657829c9 --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawemailkey.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDO7stcdJQwUtVe +JriwpAswDAirO827peSlgdxsfW8X9M2hE9ihvESEILb7TRMRiFlDyQYg1BxKMrJ0 +DZmixFi8RvUCZbH6TFOUMsk+w1FhpzjuqAqxNQ51s7u30sfruJg7XN3YJLEPelom +62wvzhvLXJFLQZlQCDrMx+PCofWs4IA7jR8JaXZjIGdkEU0GgRPy8zKPUe3dUBHi +2UR4eKT4cRCn4IwCrFx4BQjVAxNKNDGpe+fTVOzII/UX+FppDdGZZ4g0y3E1mQUE +Kkff4dKCK7vhlGJR9D+5v/V0/stwP72aXczijuVtnXXyli+oj24NaijoqQluNCD3 +MvV/INovLL2Tyk54H3/GvpU1+sJbpE2+UPh+Rh8DNkT6RPRguymJO8MSsdLt/qvV +D8BlZ7I9V3XZlDKosCRTUyxfjjFpa+VzB3nt7uFtIXZ9HNGhQIpOULvkFGizWV+t +S8PpGdTFVzDjyWg0HUKWn8g8IiWR9S40h6mHjVuTuxA9tlO69PuTjGK7MlAvFTDa +PC8seau1LUiqtQ+prnSLI0h16GfI9W2G7BKKVPloErODhLcsOcwRcmaJVW+yBda3 +te8+cMBIvtQYKAYSCtg8qXwsxyfPLo4GChbGGRbRCuM3mB1lG1qHEivJ0vynsgol +p0t8jaXSFVBVgYj+C6Vd9/hlieUcOwIDAQABAoICAQDFitqh6TxqITlFBwv6vK9d +b696371XrFdo1F57RwcdxHnkklCUnWh/BcgIgJx6eUJV3nq2LibPgjQva6hF5NCc +89QDNNfBjMmgyRaqjsSKx5sm4U5Lus2R+UFzi4mEcpUI3m99XhGVKAUV8Fo4DLcl +3LlrMTVNXH3dbdj0va4NGcfwkZiWYJI+sPliYs24LtK/dADJJro/MqfQef7OTsWV +0kHHMSoXhzlC7fNvfd8VUFw0Ym99pC3iJclc155feWyk2FwDok7xjqFmR4KTrD1M +PLm/7+ooOFX5WdHVnULSZlb3HSJxCV7l1JJ7QXo/nKS/s59X875n8OWjdoc7lD4T +Xw/K9CzJCyhJ/HDhTAea1+MNTig4a6wjdSim6vasig/Gkot6jjS2lhnZae8ZhYxP +GUx4JcPthHppgHt8s6Jb2PHuqNVRmVB0x41c5mmXOnJcSqOX0XhbSbeS1TUV8BiC +HMaa+agt7RpQOb5uxpb+Hath/88tsjDXI0ZHNAG43ndkHxSQQ9P/q/m5uaLwuJyo +Yb06yUy/g7ceXpJFjGsjO+33DmamvligqOswgg+oazMFo8S9ZUJw6sSXhM/XiHla +JOj+Vatfj0ViVcaGlO2kWughuCT5thn92bgC9V2VnJhbaSzSaQlRphlbuSYJEYj0 +S1uIbwPzTrcBQuekwY50YQKCAQEA/vve5K/nAnw4KLSrKwwCp9trYSm8C5czv2jV +tn6vQtckQMrw/hubX7TcTTgTuGboGdHMwZMFJBKpx6AlRHCR5IBw8fR1z+58c+2V +VJgllc23eKwCcBMKoe6LmsiUXOWmc7MuHc+qQS+9OemO93nNafsSwFCkucBFQs/3 +Yx7J3zNvMOuy+dq3jrxO0xl2jBF0pcmJF/czrvbMCD7tvDntgqvpAnybgrwm2cu3 +Q5F6i+E5w6VDhCprQL/aK95iT7cPmfdGxsUCdfNzDGIJFHZp2Hrar1TsOP6ESsDl +Q/Oz9oO1vMy7MymJjWFoVELBlCBxDEgubyM1f8cE1tQ6UAqFSwKCAQEAz8HnKWPe +NWZtqdAzSmY+3ZxSe1BbukOo4XtCV8LfRHGazKpXMTqsO9l7ynK7ifXv3b3GHTr+ +ck2Af/vyiVx6f7Ty2dmBotFQDzg0HfKD2skAPyH8cHpA8TUeL3yMOR3XQU5/pOnG +tn84n7KWpAyZXh8gzMnmzWjMlb9pUlkKcATUj0gb8iSa9PV0zBwMKYKY0ngznJT2 +CgE1vhy59rpuUVMrQ8i5iW9jbqYVrqID+ta2DWgcLsEXft7jKfupnRHF0Dvc650p ++Lkxv0YgKjUg5sYc2QJbIiBxXaW0cTRrw/KfOe4kvdG5RMF60Six+W1DIW2l+qi3 +irnDRvRm1N6e0QKCAQEA86d5MaxJIl3TSEqEeikK7J3GuV0pHSZKQ7EI70+VaFiv +gt6qdReqXEU2cu+QIJjtV6bcc2lq8zKGXITSt9ieAO0fgIWqgpyQ/jJcjS6qU8D1 +fnFYDwKTGXQaoTjkVPT6HvtsqP4E4i+dMZbWj/MrcAeEvpMRJZLuXE7gRi5ol0nO +CcBhEVKILvQQmrZtSqFvhvDTeTw2fg3FoGeJw2DTbheaHE84RzBGK774C7Abm0kI +asUkhEoInSH3eA4UgbobRXQ+hLhDhrSxDncr2ArjUALtr7eF11yWy9wR+OIK6Rio +9JXqmJQrphcbm9ECq+poPGVJQdgySjzCigrZAh1biwKCAQBiBnFVXCOaOov/lZa9 +weRjl8BrIo1FI2tpiEjTM8U4fAm4C588QRzG2GTKLrxB6eKVU1dIr28i62J4AJ59 +JT8/RldXZoL+GZiWtcQRZT3FWxVctGJxh51gsdleOnvG70eDLtCXNR5nOTu0TgU5 +viAXAsTtG05lGM9+0GOXUR/VntHUEQfuhkr+zVmgfJNYeqA0njZr6PT134BGBTPR +MEGg6Yb+YpT4PbBCouaUESmjju8zAC5b+Qtm9y9jvbRXwez9xWEFYpBNJMROJX5D +q/GsMUmnMq9hOMGEmAy9ZSh7udxa7vwy++NYh5m1Wmgu8di8ywmHbVe8gs2aivKB ++dAhAoIBAQC7nSuRSmRGeKJCAqikMFPVdFdLaTnEw4i/fcyYsPa+o2hTWXyLll8K +lwSnBxe+BCvdeQ8cWzg3rPaIBVzUjDecZjdwjcHnhlHvgHjEvFX339rvpD7J2HIb +DaVqvtPniCFdNK4Jyvd3JMtNq34SIHFAcmB9358JuKsOwCmk8CpMAqKPVsKj7m6H +ETISh/K8aI2vZxVZ4WN4FsQTCqmtQDXFSGpZF5EZSpMJIB3ZZLt2jyyDW2DaZ+1T +yuVl9jU56fTtacQROQY7cvrwznX0lFpmniwl0Aj0wln/svFAqKo1+RujqApw5iYn +ssH1dH2tESx6RpMMyLYihjHVDC/ULUVu +-----END PRIVATE KEY----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawext.cnf b/builtin/credential/cert/test-fixtures/root/rootcawext.cnf index 524efd2e4013..77e8258e1048 100644 --- a/builtin/credential/cert/test-fixtures/root/rootcawext.cnf +++ b/builtin/credential/cert/test-fixtures/root/rootcawext.cnf @@ -10,12 +10,7 @@ distinguished_name = dn CN = example.com [ req_v3 ] -subjectAltName = @alt_names 2.1.1.1=ASN1:UTF8String:A UTF8String Extension 2.1.1.2=ASN1:UTF8:A UTF8 Extension 2.1.1.3=ASN1:IA5:An IA5 Extension 2.1.1.4=ASN1:VISIBLE:A Visible Extension - -[ alt_names ] -DNS.1 = example.com -IP.1 = 127.0.0.1 diff --git a/website/source/api/auth/cert/index.html.md b/website/source/api/auth/cert/index.html.md index adf8efcfda4c..c4bf5e3d66b5 100644 --- a/website/source/api/auth/cert/index.html.md +++ b/website/source/api/auth/cert/index.html.md @@ -34,6 +34,23 @@ Sets a CA cert and associated parameters in a role name. (https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is a comma-separated list of patterns. Authentication requires at least one Name matching at least one pattern. If not set, defaults to allowing all names. + Note: This parameter is deprecated please use allowed_uris, allowed_common_names, + allowed_dns, allowed_emails, allowed_uris, required_extensions +- `allowed_common_names` `(string: "")` - Constrain the Common Names in + the client certificate with a [globbed pattern] + (https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is + a comma-separated list of patterns. Authentication requires at least one Name + matching at least one pattern. If not set, defaults to allowing all names. +- `allowed_dns` `(string: "")` - Constrain the Alternative Names in + the client certificate with a [globbed pattern] + (https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is + a comma-separated list of patterns. Authentication requires at least one DNS + matching at least one pattern. If not set, defaults to allowing all dns. +- `allowed_email` `(string: "")` - Constrain the Alternative Names in + the client certificate with a [globbed pattern] + (https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is + a comma-separated list of patterns. Authentication requires at least one Email + matching at least one pattern. If not set, defaults to allowing all emails. - `allowed_uris` `(string: "")` - Constrain the Alternative Names in the client certificate with a [globbed pattern] (https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is From 8a4e004cdce16d5cc55d17e9d724e11e4485dc76 Mon Sep 17 00:00:00 2001 From: Nic Jackson Date: Fri, 4 May 2018 10:38:21 +0100 Subject: [PATCH 4/6] Updated cert extension parameters to use common naming convention --- builtin/credential/cert/backend_test.go | 16 ++++++---- builtin/credential/cert/path_certs.go | 34 ++++++++++---------- builtin/credential/cert/path_login.go | 36 +++++++++++----------- website/source/api/auth/cert/index.html.md | 11 ++++--- 4 files changed, 51 insertions(+), 46 deletions(-) diff --git a/builtin/credential/cert/backend_test.go b/builtin/credential/cert/backend_test.go index c783a70f7000..4e4edb730c97 100644 --- a/builtin/credential/cert/backend_test.go +++ b/builtin/credential/cert/backend_test.go @@ -1037,6 +1037,8 @@ func TestBackend_dns_singleCert(t *testing.T) { Steps: []logicaltest.TestStep{ testAccStepCert(t, "web", ca, "foo", allowed{dns: "example.com"}, false), testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{dns: "*ample.com"}, false), + testAccStepLogin(t, connState), testAccStepCert(t, "web", ca, "foo", allowed{dns: "notincert.com"}, false), testAccStepLoginInvalid(t, connState), testAccStepCert(t, "web", ca, "foo", allowed{dns: "abc"}, false), @@ -1066,6 +1068,8 @@ func TestBackend_email_singleCert(t *testing.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", allowed{emails: "abc"}, false), @@ -1305,9 +1309,9 @@ 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 certificate - emails string // allowed email names in the certificate - uris string // allowed uris in SAN section of 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 } @@ -1323,9 +1327,9 @@ func testAccStepCert( "display_name": name, "allowed_names": testData.names, "allowed_common_names": testData.common_names, - "allowed_dns": testData.dns, - "allowed_emails": testData.emails, - "allowed_uris": testData.uris, + "allowed_dns_sans": testData.dns, + "allowed_email_sans": testData.emails, + "allowed_uri_sans": testData.uris, "required_extensions": testData.ext, "lease": 1000, }, diff --git a/builtin/credential/cert/path_certs.go b/builtin/credential/cert/path_certs.go index 6b75e71bcffd..676e574c847d 100644 --- a/builtin/credential/cert/path_certs.go +++ b/builtin/credential/cert/path_certs.go @@ -44,8 +44,8 @@ Must be x509 PEM encoded.`, Type: framework.TypeCommaStringSlice, Description: `A comma-separated list of names. 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, -allowed_email, allowed_uris.`, +This parameter is deprecated, please use allowed_common_names, allowed_dns_sans, +allowed_email_sans, allowed_uri_sans.`, }, "allowed_common_names": &framework.FieldSchema{ @@ -54,19 +54,19 @@ allowed_email, allowed_uris.`, At least one must exist in the Common Name. Supports globbing.`, }, - "allowed_dns": &framework.FieldSchema{ + "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_emails": &framework.FieldSchema{ + "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_uris": &framework.FieldSchema{ + "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.`, @@ -180,9 +180,9 @@ func (b *backend) pathCertRead(ctx context.Context, req *logical.Request, d *fra "period": cert.Period / time.Second, "allowed_names": cert.AllowedNames, "allowed_common_names": cert.AllowedCommonNames, - "allowed_dns": cert.AllowedDNS, - "allowed_emails": cert.AllowedEmails, - "allowed_uris": cert.AllowedURIs, + "allowed_dns_sans": cert.AllowedDNSSans, + "allowed_email_sans": cert.AllowedEmailSans, + "allowed_uri_sans": cert.AllowedURISans, "required_extensions": cert.RequiredExtensions, }, }, nil @@ -195,9 +195,9 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr policies := policyutil.ParsePolicies(d.Get("policies")) allowedNames := d.Get("allowed_names").([]string) allowedCommonNames := d.Get("allowed_common_names").([]string) - allowedDNS := d.Get("allowed_dns").([]string) - allowedEmails := d.Get("allowed_emails").([]string) - allowedURIs := d.Get("allowed_uris").([]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 @@ -272,9 +272,9 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr Policies: policies, AllowedNames: allowedNames, AllowedCommonNames: allowedCommonNames, - AllowedDNS: allowedDNS, - AllowedEmails: allowedEmails, - AllowedURIs: allowedURIs, + AllowedDNSSans: allowedDNSSans, + AllowedEmailSans: allowedEmailSans, + AllowedURISans: allowedURISans, RequiredExtensions: requiredExtensions, TTL: ttl, MaxTTL: maxTTL, @@ -307,9 +307,9 @@ type CertEntry struct { Period time.Duration AllowedNames []string AllowedCommonNames []string - AllowedDNS []string - AllowedEmails []string - AllowedURIs []string + AllowedDNSSans []string + AllowedEmailSans []string + AllowedURISans []string RequiredExtensions []string } diff --git a/builtin/credential/cert/path_login.go b/builtin/credential/cert/path_login.go index 2e14c86abd60..9c42fba7cc71 100644 --- a/builtin/credential/cert/path_login.go +++ b/builtin/credential/cert/path_login.go @@ -267,9 +267,9 @@ func (b *backend) matchesConstraints(clientCert *x509.Certificate, trustedChain return !b.checkForChainInCRLs(trustedChain) && b.matchesNames(clientCert, config) && b.matchesCommonName(clientCert, config) && - b.matchesDNSName(clientCert, config) && - b.matchesEmail(clientCert, config) && - b.matchesURIs(clientCert, config) && + b.matchesDNSSans(clientCert, config) && + b.matchesEmailSans(clientCert, config) && + b.matchesURISans(clientCert, config) && b.matchesCertificateExtensions(clientCert, config) } @@ -319,15 +319,15 @@ func (b *backend) matchesCommonName(clientCert *x509.Certificate, config *Parsed return false } -// matchesDNSName verifies that the certificate matches at least one configured -// allowed dns name -func (b *backend) matchesDNSName(clientCert *x509.Certificate, config *ParsedCert) bool { +// matchesDNSSans verifies that the certificate matches at least one configured +// allowed dns entry in the subject alternate name extension +func (b *backend) matchesDNSSans(clientCert *x509.Certificate, config *ParsedCert) bool { // Default behavior (no names) is to allow all names - if len(config.Entry.AllowedDNS) == 0 { + if len(config.Entry.AllowedDNSSans) == 0 { return true } // At least one pattern must match at least one name if any patterns are specified - for _, allowedDNS := range config.Entry.AllowedDNS { + for _, allowedDNS := range config.Entry.AllowedDNSSans { for _, name := range clientCert.DNSNames { if glob.Glob(allowedDNS, name) { return true @@ -338,15 +338,15 @@ func (b *backend) matchesDNSName(clientCert *x509.Certificate, config *ParsedCer return false } -// matchesDNSName verifies that the certificate matches at least one configured -// allowed dns name -func (b *backend) matchesEmail(clientCert *x509.Certificate, config *ParsedCert) bool { +// matchesEmailSans verifies that the certificate matches at least one configured +// allowed email in the subject alternate name extension +func (b *backend) matchesEmailSans(clientCert *x509.Certificate, config *ParsedCert) bool { // Default behavior (no names) is to allow all names - if len(config.Entry.AllowedEmails) == 0 { + if len(config.Entry.AllowedEmailSans) == 0 { return true } // At least one pattern must match at least one name if any patterns are specified - for _, allowedEmail := range config.Entry.AllowedEmails { + for _, allowedEmail := range config.Entry.AllowedEmailSans { for _, email := range clientCert.EmailAddresses { if glob.Glob(allowedEmail, email) { return true @@ -357,15 +357,15 @@ func (b *backend) matchesEmail(clientCert *x509.Certificate, config *ParsedCert) return false } -// matchesURIs verifies that the certificate matches at least one configured -// allowed uri -func (b *backend) matchesURIs(clientCert *x509.Certificate, config *ParsedCert) bool { +// matchesURISans verifies that the certificate matches at least one configured +// allowed uri in the subject alternate name extension +func (b *backend) matchesURISans(clientCert *x509.Certificate, config *ParsedCert) bool { // Default behavior (no names) is to allow all names - if len(config.Entry.AllowedURIs) == 0 { + if len(config.Entry.AllowedURISans) == 0 { return true } // At least one pattern must match at least one name if any patterns are specified - for _, allowedURI := range config.Entry.AllowedURIs { + for _, allowedURI := range config.Entry.AllowedURISans { for _, name := range clientCert.URIs { if glob.Glob(allowedURI, name.String()) { return true diff --git a/website/source/api/auth/cert/index.html.md b/website/source/api/auth/cert/index.html.md index c4bf5e3d66b5..be75e5aaeda0 100644 --- a/website/source/api/auth/cert/index.html.md +++ b/website/source/api/auth/cert/index.html.md @@ -34,24 +34,25 @@ Sets a CA cert and associated parameters in a role name. (https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is a comma-separated list of patterns. Authentication requires at least one Name matching at least one pattern. If not set, defaults to allowing all names. - Note: This parameter is deprecated please use allowed_uris, allowed_common_names, - allowed_dns, allowed_emails, allowed_uris, required_extensions + Note: This parameter is deprecated please use individual parameters + allowed_common_names, allowed_dns_sans, allowed_email_sans, allowed_uri_sans, + required_extensions - `allowed_common_names` `(string: "")` - Constrain the Common Names in the client certificate with a [globbed pattern] (https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is a comma-separated list of patterns. Authentication requires at least one Name matching at least one pattern. If not set, defaults to allowing all names. -- `allowed_dns` `(string: "")` - Constrain the Alternative Names in +- `allowed_dns_sans` `(string: "")` - Constrain the Alternative Names in the client certificate with a [globbed pattern] (https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is a comma-separated list of patterns. Authentication requires at least one DNS matching at least one pattern. If not set, defaults to allowing all dns. -- `allowed_email` `(string: "")` - Constrain the Alternative Names in +- `allowed_email_sans` `(string: "")` - Constrain the Alternative Names in the client certificate with a [globbed pattern] (https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is a comma-separated list of patterns. Authentication requires at least one Email matching at least one pattern. If not set, defaults to allowing all emails. -- `allowed_uris` `(string: "")` - Constrain the Alternative Names in +- `allowed_uri_sans` `(string: "")` - Constrain the Alternative Names in the client certificate with a [globbed pattern] (https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is a comma-separated list of URI patterns. Authentication requires at least one URI From be4f94b31940f558ca371251a3daad5b0982a091 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 22 May 2018 13:39:41 -0400 Subject: [PATCH 5/6] Remove some changes due to old branch --- builtin/credential/cert/path_login.go | 29 ++++++--------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/builtin/credential/cert/path_login.go b/builtin/credential/cert/path_login.go index dec6387712d6..8ec24f76c1a3 100644 --- a/builtin/credential/cert/path_login.go +++ b/builtin/credential/cert/path_login.go @@ -73,11 +73,6 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, data *fra return nil, nil } - ttl := matched.Entry.TTL - if ttl == 0 { - ttl = b.System().DefaultLeaseTTL() - } - if err := b.checkCIDR(matched.Entry, req); err != nil { return nil, err } @@ -107,7 +102,9 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, data *fra }, LeaseOptions: logical.LeaseOptions{ Renewable: true, - TTL: ttl, + TTL: matched.Entry.TTL, + MaxTTL: matched.Entry.MaxTTL, + Period: matched.Entry.Period, }, Alias: &logical.Alias{ Name: clientCerts[0].Subject.CommonName, @@ -116,20 +113,6 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, data *fra }, } - if matched.Entry.MaxTTL > time.Duration(0) { - // Cap maxTTL to the sysview's max TTL - maxTTL := matched.Entry.MaxTTL - if maxTTL > b.System().MaxLeaseTTL() { - maxTTL = b.System().MaxLeaseTTL() - } - - // Cap TTL to MaxTTL - if resp.Auth.TTL > maxTTL { - resp.AddWarning(fmt.Sprintf("Effective TTL of '%s' exceeded the effective max_ttl of '%s'; TTL value is capped accordingly", (resp.Auth.TTL / time.Second), (maxTTL / time.Second))) - resp.Auth.TTL = maxTTL - } - } - // Generate a response return resp, nil } @@ -422,7 +405,7 @@ func (b *backend) loadTrustedCerts(ctx context.Context, storage logical.Storage, trustedNonCAs = make([]*ParsedCert, 0) names, err := storage.List(ctx, "cert/") if err != nil { - b.Logger().Error("cert: failed to list trusted certs", "error", err) + b.Logger().Error("failed to list trusted certs", "error", err) return } for _, name := range names { @@ -432,12 +415,12 @@ func (b *backend) loadTrustedCerts(ctx context.Context, storage logical.Storage, } entry, err := b.Cert(ctx, storage, strings.TrimPrefix(name, "cert/")) if err != nil { - b.Logger().Error("cert: failed to load trusted cert", "name", name, "error", err) + b.Logger().Error("failed to load trusted cert", "name", name, "error", err) continue } parsed := parsePEM([]byte(entry.Certificate)) if len(parsed) == 0 { - b.Logger().Error("cert: failed to parse certificate", "name", name) + b.Logger().Error("failed to parse certificate", "name", name) continue } if !parsed[0].IsCA { From 217ae5a7673f4f3df0aa889b31bfee0ea5145427 Mon Sep 17 00:00:00 2001 From: Nic Jackson Date: Fri, 25 May 2018 14:17:26 +0100 Subject: [PATCH 6/6] Updated referenes from Sans to SANs --- builtin/credential/cert/path_certs.go | 24 ++++++++++----------- builtin/credential/cert/path_login.go | 31 +++++++++++++-------------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/builtin/credential/cert/path_certs.go b/builtin/credential/cert/path_certs.go index 0050746dc012..384cd47fd65a 100644 --- a/builtin/credential/cert/path_certs.go +++ b/builtin/credential/cert/path_certs.go @@ -187,9 +187,9 @@ func (b *backend) pathCertRead(ctx context.Context, req *logical.Request, d *fra "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, + "allowed_dns_sans": cert.AllowedDNSSANs, + "allowed_email_sans": cert.AllowedEmailSANs, + "allowed_uri_sans": cert.AllowedURISANs, "required_extensions": cert.RequiredExtensions, }, }, nil @@ -202,9 +202,9 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr 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) + 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 @@ -284,9 +284,9 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr Policies: policies, AllowedNames: allowedNames, AllowedCommonNames: allowedCommonNames, - AllowedDNSSans: allowedDNSSans, - AllowedEmailSans: allowedEmailSans, - AllowedURISans: allowedURISans, + AllowedDNSSANs: allowedDNSSANs, + AllowedEmailSANs: allowedEmailSANs, + AllowedURISANs: allowedURISANs, RequiredExtensions: requiredExtensions, TTL: ttl, MaxTTL: maxTTL, @@ -320,9 +320,9 @@ type CertEntry struct { Period time.Duration AllowedNames []string AllowedCommonNames []string - AllowedDNSSans []string - AllowedEmailSans []string - AllowedURISans []string + AllowedDNSSANs []string + AllowedEmailSANs []string + AllowedURISANs []string RequiredExtensions []string BoundCIDRs []*sockaddr.SockAddrMarshaler } diff --git a/builtin/credential/cert/path_login.go b/builtin/credential/cert/path_login.go index 8ec24f76c1a3..a5a3d00d9207 100644 --- a/builtin/credential/cert/path_login.go +++ b/builtin/credential/cert/path_login.go @@ -11,7 +11,6 @@ import ( "errors" "fmt" "strings" - "time" "github.com/hashicorp/vault/helper/certutil" "github.com/hashicorp/vault/helper/policyutil" @@ -256,9 +255,9 @@ func (b *backend) matchesConstraints(clientCert *x509.Certificate, trustedChain return !b.checkForChainInCRLs(trustedChain) && b.matchesNames(clientCert, config) && b.matchesCommonName(clientCert, config) && - b.matchesDNSSans(clientCert, config) && - b.matchesEmailSans(clientCert, config) && - b.matchesURISans(clientCert, config) && + b.matchesDNSSANs(clientCert, config) && + b.matchesEmailSANs(clientCert, config) && + b.matchesURISANs(clientCert, config) && b.matchesCertificateExtensions(clientCert, config) } @@ -308,15 +307,15 @@ func (b *backend) matchesCommonName(clientCert *x509.Certificate, config *Parsed return false } -// matchesDNSSans verifies that the certificate matches at least one configured +// matchesDNSSANs verifies that the certificate matches at least one configured // allowed dns entry in the subject alternate name extension -func (b *backend) matchesDNSSans(clientCert *x509.Certificate, config *ParsedCert) bool { +func (b *backend) matchesDNSSANs(clientCert *x509.Certificate, config *ParsedCert) bool { // Default behavior (no names) is to allow all names - if len(config.Entry.AllowedDNSSans) == 0 { + if len(config.Entry.AllowedDNSSANs) == 0 { return true } // At least one pattern must match at least one name if any patterns are specified - for _, allowedDNS := range config.Entry.AllowedDNSSans { + for _, allowedDNS := range config.Entry.AllowedDNSSANs { for _, name := range clientCert.DNSNames { if glob.Glob(allowedDNS, name) { return true @@ -327,15 +326,15 @@ func (b *backend) matchesDNSSans(clientCert *x509.Certificate, config *ParsedCer return false } -// matchesEmailSans verifies that the certificate matches at least one configured +// matchesEmailSANs verifies that the certificate matches at least one configured // allowed email in the subject alternate name extension -func (b *backend) matchesEmailSans(clientCert *x509.Certificate, config *ParsedCert) bool { +func (b *backend) matchesEmailSANs(clientCert *x509.Certificate, config *ParsedCert) bool { // Default behavior (no names) is to allow all names - if len(config.Entry.AllowedEmailSans) == 0 { + if len(config.Entry.AllowedEmailSANs) == 0 { return true } // At least one pattern must match at least one name if any patterns are specified - for _, allowedEmail := range config.Entry.AllowedEmailSans { + for _, allowedEmail := range config.Entry.AllowedEmailSANs { for _, email := range clientCert.EmailAddresses { if glob.Glob(allowedEmail, email) { return true @@ -346,15 +345,15 @@ func (b *backend) matchesEmailSans(clientCert *x509.Certificate, config *ParsedC return false } -// matchesURISans verifies that the certificate matches at least one configured +// matchesURISANs verifies that the certificate matches at least one configured // allowed uri in the subject alternate name extension -func (b *backend) matchesURISans(clientCert *x509.Certificate, config *ParsedCert) bool { +func (b *backend) matchesURISANs(clientCert *x509.Certificate, config *ParsedCert) bool { // Default behavior (no names) is to allow all names - if len(config.Entry.AllowedURISans) == 0 { + if len(config.Entry.AllowedURISANs) == 0 { return true } // At least one pattern must match at least one name if any patterns are specified - for _, allowedURI := range config.Entry.AllowedURISans { + for _, allowedURI := range config.Entry.AllowedURISANs { for _, name := range clientCert.URIs { if glob.Glob(allowedURI, name.String()) { return true