From 2c5843c639145d03e1c5f5430d2fcda5e2575ed6 Mon Sep 17 00:00:00 2001 From: Saurabh Pal Date: Tue, 26 Feb 2019 22:44:54 -0800 Subject: [PATCH 1/5] Validate Subject DN entries during auth --- builtin/credential/cert/backend_test.go | 104 ++++++++++++++++++ builtin/credential/cert/path_certs.go | 10 ++ builtin/credential/cert/path_login.go | 53 ++++++++- .../test-fixtures/root/rootcawsubjoids.cnf | 27 +++++ .../test-fixtures/root/rootcawsubjoids.csr | 31 ++++++ .../root/rootcawsubjoidscert.pem | 27 +++++ .../test-fixtures/root/rootcawsubjoidskey.pem | 51 +++++++++ website/source/api/auth/cert/index.html.md | 7 ++ 8 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawsubjoids.cnf create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawsubjoids.csr create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawsubjoidscert.pem create mode 100644 builtin/credential/cert/test-fixtures/root/rootcawsubjoidskey.pem diff --git a/builtin/credential/cert/backend_test.go b/builtin/credential/cert/backend_test.go index 14875e533067..dbf86e140cd3 100644 --- a/builtin/credential/cert/backend_test.go +++ b/builtin/credential/cert/backend_test.go @@ -1485,6 +1485,7 @@ type allowed struct { uris string // allowed uris in SAN extension of the certificate organizational_units string // allowed OUs in the certificate ext string // required extensions in the certificate + sbjDnOids string // required subject dn entries } func testAccStepCert( @@ -1504,6 +1505,7 @@ func testAccStepCert( "allowed_uri_sans": testData.uris, "allowed_organizational_units": testData.organizational_units, "required_extensions": testData.ext, + "required_subject_oids": testData.sbjDnOids, "lease": 1000, }, Check: func(resp *logical.Response) error { @@ -1805,3 +1807,105 @@ func Test_Renew(t *testing.T) { t.Fatal("expected error") } } + +// Test a self-signed client containing subject DN entries that is trusted by root CA. +func TestBackend_subjectoids_singleCert(t *testing.T) { + connState, err := testConnState( + "test-fixtures/root/rootcawsubjoidscert.pem", + "test-fixtures/root/rootcawsubjoidskey.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{ + CredentialBackend: testFactory(t), + Steps: []logicaltest.TestStep{ + // First set of cases check all available fields in the Subject DN + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "0.9.2342.19200300.100.1.1:TheUID"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "0.9.2342.19200300.100.1.1:TheUID,2.5.4.3:example.com"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:ExampleOrg"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:ExampleOrg,2.5.4.11:ExampleDivision1"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:ExampleOrg,2.5.4.11:ExampleDivision1,2.5.4.11:ExampleDivision2"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:ExampleOrg,2.5.4.11:ExampleDivision1,2.5.4.11:Example*2,2.5.4.11:ExampleDivision3"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:ExampleOrg,2.5.4.11:Example*1,2.5.4.11:ExampleDivision2,2.5.4.11:ExampleDivision3,0.9.2342.19200300.100.1.25:ExampleDC"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:ExampleOrg,2.5.4.11:ExampleDivision1,2.5.4.11:Example*2,2.5.4.11:ExampleDivision3,0.9.2342.19200300.100.1.25:ExampleDC,2.5.4.4:ExampleSN"}, false), + testAccStepLogin(t, connState), + + //This Second set of test cases check for all available fields in the Subject DN for globbed pattern(s) + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "0.9.2342.19200300.100.1.1:*UID"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "0.9.2342.19200300.100.1.1:*UID,2.5.4.3:example*"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example*"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example*,2.5.4.6:US"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example*,2.5.4.6:US,2.5.4.8:CA"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example*,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:*Org"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example*,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:*Org,2.5.4.11:*Division1"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example*,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:*Org,2.5.4.11:*Division1,2.5.4.11:*Division2"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example*,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:*Org,2.5.4.11:*Division1,2.5.4.11:*Division2,2.5.4.11:*Division3"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example*,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:*Org,2.5.4.11:*Division1,2.5.4.11:*Division2,2.5.4.11:*Division3,0.9.2342.19200300.100.1.25:Example*"}, false), + testAccStepLogin(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example*,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:*Org,2.5.4.11:*Division1,2.5.4.11:*Division2,2.5.4.11:*Division3,0.9.2342.19200300.100.1.25:*DC,2.5.4.4:Example*"}, false), + testAccStepLogin(t, connState), + + // This Third set of test cases check for all non-matching entries of OIDs in Subject DN + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "0.9.2342.19200300.100.1.1:NotTheUID"}, false), + testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "0.9.2342.19200300.100.1.1:TheUID,2.5.4.3:NotExample.com"}, false), + testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:NotExample.com"}, false), + testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:JP"}, false), + testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:WA"}, false), + testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:San Francisco,2.5.4.10:ExampleOrg"}, false), + testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:NotExampleOrg,2.5.4.11:ExampleDivision1"}, false), + testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:ExampleOrg,2.5.4.11:NotExampleDivision1,2.5.4.11:ExampleDivision2"}, false), + testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:ExampleOrg,2.5.4.11:ExampleDivision1,2.5.4.11:NotExampleDivision2,2.5.4.11:ExampleDivision3"}, false), + testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:ExampleOrg,2.5.4.11:ExampleDivision1,2.5.4.11:ExampleDivision2,2.5.4.11:NotExampleDivision3,0.9.2342.19200300.100.1.25:ExampleDC"}, false), + testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:ExampleOrg,2.5.4.11:ExampleDivision1,2.5.4.11:ExampleDivision2,2.5.4.11:ExampleDivision3,0.9.2342.19200300.100.1.25:NotExampleDC,2.5.4.4:ExampleSN"}, false), + testAccStepLoginInvalid(t, connState), + + //+ve Tests for condition when both "allowed_common_names" and "required_subject_oids" are provided. + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com", common_names: "example.com"}, false), + testAccStepLogin(t, connState), + + //-ve Tests for condition when both "allowed_common_names" and "required_subject_oids" are provided. + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com", common_names: "Notexample.com"}, false), + testAccStepLoginInvalid(t, connState), + testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:Notexample.com", common_names: "example.com"}, false), + testAccStepLoginInvalid(t, connState), + }, + }) +} diff --git a/builtin/credential/cert/path_certs.go b/builtin/credential/cert/path_certs.go index 53f0254b33f6..8c93bed3dba8 100644 --- a/builtin/credential/cert/path_certs.go +++ b/builtin/credential/cert/path_certs.go @@ -129,6 +129,12 @@ TTL will be set to the value of this parameter.`, Description: `Comma separated string or list of CIDR blocks. If set, specifies the blocks of IP addresses which can perform the login operation.`, }, + "required_subject_oids": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `A comma-separated string or array of subject name entries +formatted as "oid:value". Expects the oid value to be some type of ASN1 encoded string. +All values much match. Supports globbing on "value".`, + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -198,6 +204,7 @@ func (b *backend) pathCertRead(ctx context.Context, req *logical.Request, d *fra "allowed_uri_sans": cert.AllowedURISANs, "allowed_organizational_units": cert.AllowedOrganizationalUnits, "required_extensions": cert.RequiredExtensions, + "required_subject_oids": cert.RequiredSubjectOids, "bound_cidrs": cert.BoundCIDRs, }, }, nil @@ -215,6 +222,7 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr allowedURISANs := d.Get("allowed_uri_sans").([]string) allowedOrganizationalUnits := d.Get("allowed_organizational_units").([]string) requiredExtensions := d.Get("required_extensions").([]string) + requiredSubjectOids := d.Get("required_subject_oids").([]string) var resp logical.Response @@ -302,6 +310,7 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr MaxTTL: maxTTL, Period: period, BoundCIDRs: parsedCIDRs, + RequiredSubjectOids: requiredSubjectOids, } // Store it @@ -336,6 +345,7 @@ type CertEntry struct { AllowedOrganizationalUnits []string RequiredExtensions []string BoundCIDRs []*sockaddr.SockAddrMarshaler + RequiredSubjectOids []string } const pathCertHelpSyn = ` diff --git a/builtin/credential/cert/path_login.go b/builtin/credential/cert/path_login.go index 4ef6cefe1c92..8916bd174c00 100644 --- a/builtin/credential/cert/path_login.go +++ b/builtin/credential/cert/path_login.go @@ -253,7 +253,8 @@ func (b *backend) matchesConstraints(clientCert *x509.Certificate, trustedChain b.matchesEmailSANs(clientCert, config) && b.matchesURISANs(clientCert, config) && b.matchesOrganizationalUnits(clientCert, config) && - b.matchesCertificateExtensions(clientCert, config) + b.matchesCertificateExtensions(clientCert, config) && + b.matchesSubjectOids(clientCert, config) } // matchesNames verifies that the certificate matches at least one configured @@ -411,6 +412,56 @@ func (b *backend) matchesCertificateExtensions(clientCert *x509.Certificate, con return true } +// Matches the Subject dn entries with te required_subject_oids from the the configuration. +// All entries in the required config should match. +func (b *backend) matchesSubjectOids(clientCert *x509.Certificate, config *ParsedCert) bool { + // If no required extensions, nothing to check here + if len(config.Entry.RequiredSubjectOids) == 0 { + return true + } + // Fail fast if we have required Subject OID's but do not have Subject entries in the Cert + if len(clientCert.Subject.Names) == 0 { + return false + } + + // Build the 'Subject Names' OID map from the certificate, this will be + // matched in the next step with the required OID's from config. + // Note that this is a Map of slice, So eg. if the subject has multiple + // entries for OU say MyOU1, MyOU2 and MyOU3, this generated Map will have an entry + // 2.5.4.11:[MyOU1 MyOU1 MyOU2] + subjectOidMap := make(map[string][]string, len(clientCert.Subject.Names)) + for _, n := range clientCert.Subject.Names { + subjectOidMap[n.Type.String()] = append(subjectOidMap[n.Type.String()], n.Value.(string)) + } + // Check if all the required OID's from the config are present in the + // certificate Subject Names i.e. the subjectOidMap we created in previous step + for _, requiredOid := range config.Entry.RequiredSubjectOids { + // expected format for a required OID is OID:Value so we split it accordingly + reqOid := strings.SplitN(requiredOid, ":", 2) + + clientSubjOidValue, clientSubjOidValueOk := subjectOidMap[reqOid[0]] + // The match fails if the OID itself is not present or is missing a value + if !clientSubjOidValueOk || len(clientSubjOidValue) == 0 { + return false + } + // If the OID matches, we compare the required OID value with each entry of the + // slice value from the matched map entry + isRequiredOidInCertEntry := false + for _, clientSubjOid := range clientSubjOidValue { + if glob.Glob(reqOid[1], clientSubjOid) { + isRequiredOidInCertEntry = true + break + } + } + + // If none of the slice entries match, the overall match fails for a required OID. + if !isRequiredOidInCertEntry { + return false + } + } + return true +} + // loadTrustedCerts is used to load all the trusted certificates from the backend func (b *backend) loadTrustedCerts(ctx context.Context, storage logical.Storage, certName string) (pool *x509.CertPool, trusted []*ParsedCert, trustedNonCAs []*ParsedCert) { pool = x509.NewCertPool() diff --git a/builtin/credential/cert/test-fixtures/root/rootcawsubjoids.cnf b/builtin/credential/cert/test-fixtures/root/rootcawsubjoids.cnf new file mode 100644 index 000000000000..de173393209a --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawsubjoids.cnf @@ -0,0 +1,27 @@ +[ req ] +default_bits = 2048 +encrypt_key = no +prompt = no +default_md = sha256 +req_extensions = req_v3 +distinguished_name = dn + +[ dn ] +UID = TheUID +CN = example.com +C = US +ST = CA +L = Sunnyvale +O = ExampleOrg +0.OU = ExampleDivision1 +1.OU = ExampleDivision2 +2.OU = ExampleDivision3 +DC = ExampleDC +SN = ExampleSN + +[ 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/rootcawsubjoids.csr b/builtin/credential/cert/test-fixtures/root/rootcawsubjoids.csr new file mode 100644 index 000000000000..f37a987b7047 --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawsubjoids.csr @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIFZjCCA04CAQAwgfExFjAUBgoJkiaJk/IsZAEBDAZUaGVVSUQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcM +CVN1bm55dmFsZTETMBEGA1UECgwKRXhhbXBsZU9yZzEZMBcGA1UECwwQRXhhbXBs +ZURpdmlzaW9uMTEZMBcGA1UECwwQRXhhbXBsZURpdmlzaW9uMjEZMBcGA1UECwwQ +RXhhbXBsZURpdmlzaW9uMzEZMBcGCgmSJomT8ixkARkWCUV4YW1wbGVEQzESMBAG +A1UEBAwJRXhhbXBsZVNOMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +peUb51cXh3StDo6U/EsfNIorj9XyhiDojI9VojoMObdhJqbwnPWCNWxU/DXgdoE5 +LzJvfqUOq1s6Hi28KgwQCJj9dakvll64k4rdMT1iV2aUqGTk3GYHhFSaBFurV9po +KDNCtNwhsQ5bF30nFhUPpzt18KVov7shsbMA3iEyKqsvr4e2STB3Lbmx0u9K66zn +Am5hgwFK68Jg2YmkUvGIyBCUFqe+X+xffv2kr6kofJdghR2K4Ptf74RRxis4RFiZ +JGzSNQW3TD2yzMacoBA3zqcob6i0V0cQLNh1vx6FAB9L0qHgH0jsHXbKsKfeI9Wl +kqqrv2HO7huf7qROjbTejGoYFnQbST3yBHEabgCIrAdjxTISuzn/rEpClBhh6yo9 +dIXkNbWzfWGbo3LowxVQMRWVB/n/yZ+u3k5O8UQuLerpOhaNAqu113nkISXYE+XA +r7K/3sUBV7OZCaS8suSInifjwCVaL2hHZ0Bm65aTohtqCa0o+ZI2Wurs4Qivfuse +WtOvcqzdX0ZPBidKgHEfksIwGvz71GMAnYMw7l5kHOnVowxv3c3Um6oynhAuzxpQ +co69kTiT+fgtI1KlQSL0uiQAxtE0nqDFnsjinyMORE66pBCFxBCwmdI3f0veml73 +GboxNrBBgTxZpesOI99+FKQ1UOF8GHqLjLGm4Ao000ECAwEAAaAvMC0GCSqGSIb3 +DQEJDjEgMB4wHAYDVR0RBBUwE4cEfwAAAYILZXhhbXBsZS5jb20wDQYJKoZIhvcN +AQELBQADggIBAAaMV264TQnYWnjfNzHaFVWHzOXRVh9tXwcpsYgA6sFTqXFlLd5C +bxxCDjOQRr8wkYRksiqGJHrxthjLH9M4ot+SV9Yjq02PmTrIQQj10yGyywGnh8BZ +xDy6bdP3tqEN3i1mPn/spvEhaNej/XQ9pFC6MrufrjcRjvO7pJilFMSOWAaB09e3 +dXreXeOtgX6Ee+OxHLt9jxBa/Lrj7+F/2/vtk4oJxc5IRBEXTmPEdBruidbI0KAC +WF5UxbiuwV4iq1X8SkT/PB1p7N2gA14asgBm4nPex59T2Gu+DYHsr7Tso6TSx+GO +NiHZH/h76IOU4LVFS0r4YTN6Goy8jvKvD34UQ1mqxMxeq9ueGl95Ywz5foSO3Ufh +uk1OarZINF6GbqyP3J0jtIXzGukSmiU1J28nExvm/UpIbDWM4DGnNcKfQOhQUKZq +b79IUxaAD1a9AFAxIP4And0R/G2izrEz9GfnUYEzzrZcMR6v84nVU7eLnw0/+jcT +9Bl4L2cNXXHqXhJFhaQMNslGK21zsTptnuIeGoKwMsv+Z6gmCQw5oRhvLGUGpNyD +G/niwZcQLAynvijFbgV1oOIsOp7jJAl/CnIKX9SWXyNla8DixRyb6sT2cGCl4mlz +LgAV95DPIw1sMCSVflBmBKk0DItzamCJDJR9sNgmOYTIc2KZtYQZAwcH +-----END CERTIFICATE REQUEST----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawsubjoidscert.pem b/builtin/credential/cert/test-fixtures/root/rootcawsubjoidscert.pem new file mode 100644 index 000000000000..b242021a118c --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawsubjoidscert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqzCCA5OgAwIBAgIJANl6FnIQbEiEMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV +BAMTC2V4YW1wbGUuY29tMB4XDTE4MDcyOTAwMzkxMFoXDTI4MDcyNjAwMzkxMFow +gfExFjAUBgoJkiaJk/IsZAEBDAZUaGVVSUQxFDASBgNVBAMMC2V4YW1wbGUuY29t +MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCVN1bm55dmFsZTET +MBEGA1UECgwKRXhhbXBsZU9yZzEZMBcGA1UECwwQRXhhbXBsZURpdmlzaW9uMTEZ +MBcGA1UECwwQRXhhbXBsZURpdmlzaW9uMjEZMBcGA1UECwwQRXhhbXBsZURpdmlz +aW9uMzEZMBcGCgmSJomT8ixkARkWCUV4YW1wbGVEQzESMBAGA1UEBAwJRXhhbXBs +ZVNOMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApeUb51cXh3StDo6U +/EsfNIorj9XyhiDojI9VojoMObdhJqbwnPWCNWxU/DXgdoE5LzJvfqUOq1s6Hi28 +KgwQCJj9dakvll64k4rdMT1iV2aUqGTk3GYHhFSaBFurV9poKDNCtNwhsQ5bF30n +FhUPpzt18KVov7shsbMA3iEyKqsvr4e2STB3Lbmx0u9K66znAm5hgwFK68Jg2Ymk +UvGIyBCUFqe+X+xffv2kr6kofJdghR2K4Ptf74RRxis4RFiZJGzSNQW3TD2yzMac +oBA3zqcob6i0V0cQLNh1vx6FAB9L0qHgH0jsHXbKsKfeI9Wlkqqrv2HO7huf7qRO +jbTejGoYFnQbST3yBHEabgCIrAdjxTISuzn/rEpClBhh6yo9dIXkNbWzfWGbo3Lo +wxVQMRWVB/n/yZ+u3k5O8UQuLerpOhaNAqu113nkISXYE+XAr7K/3sUBV7OZCaS8 +suSInifjwCVaL2hHZ0Bm65aTohtqCa0o+ZI2Wurs4QivfuseWtOvcqzdX0ZPBidK +gHEfksIwGvz71GMAnYMw7l5kHOnVowxv3c3Um6oynhAuzxpQco69kTiT+fgtI1Kl +QSL0uiQAxtE0nqDFnsjinyMORE66pBCFxBCwmdI3f0veml73GboxNrBBgTxZpesO +I99+FKQ1UOF8GHqLjLGm4Ao000ECAwEAAaMgMB4wHAYDVR0RBBUwE4cEfwAAAYIL +ZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAGOh0YwKhHvlb2sOU3ukjX7J +xrK6oAAGNXpBp6jvEGr7Q7OJLrS+/4WLh+SDXv6hpkyTpNcInJHZTNLZ4wiDjVM1 +odD/JFbA46WyKvuxJZ7+5P+RZRLYyni/PIjCaX8GPNypI6v8Vpkdi19gaSsU7uOi +8Ivfk8nQgFVrWzKhvd04mzQplsnteB+lyn6fr59uwX9KNrbTJ7cF7Q0Jtv+pY9ld +nOMgVNOUHMXmR0a6kG0hQcpx/nZkoBelaBD0swgukZPjR2NW+FA5XdV/HXwE+cfm +zNmirBvVjFSb45Pvaf17/andknVI8Z81kN+OuTkdHwGYAcn+J98CTGIXJV1fKLY= +-----END CERTIFICATE----- diff --git a/builtin/credential/cert/test-fixtures/root/rootcawsubjoidskey.pem b/builtin/credential/cert/test-fixtures/root/rootcawsubjoidskey.pem new file mode 100644 index 000000000000..37877c703251 --- /dev/null +++ b/builtin/credential/cert/test-fixtures/root/rootcawsubjoidskey.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/website/source/api/auth/cert/index.html.md b/website/source/api/auth/cert/index.html.md index f7205f65c0fe..0b52955f3288 100644 --- a/website/source/api/auth/cert/index.html.md +++ b/website/source/api/auth/cert/index.html.md @@ -67,6 +67,13 @@ Sets a CA cert and associated parameters in a role name. string or array of `oid:value`. Expects the extension value to be some type of ASN1 encoded string. All conditions _must_ be met. Supports globbing on `value`. +- `required_subject_oids` `(string: "" or array: [])` - Requires Subject DN entries + to exist and match the pattern. Value is a comma separated + string or array of `oid:value`. All entries should match. Supports globbing on + `value`. + Example: A 'required_subject_oids' value "0.9.2342.19200300.100.1.1:TheUID,2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:ExampleOrg,2.5.4.11:ExampleDivision1,2.5.4.11:Example*2,2.5.4.11:ExampleDivision3,0.9.2342.19200300.100.1.25:ExampleDC,2.5.4.4:ExampleSN" will require a certificate + to contain "Subject: UID=TheUID, CN=example.com, C=US, ST=CA, L=Sunnyvale, O=ExampleOrg, OU=ExampleDivision1, OU=ExampleDivision2, OU=ExampleDivision3, DC=ExampleDC, SN=ExampleSN" + for the check to succeed. - `policies` `(string: "")` - A comma-separated list of policies to set on tokens issued when authenticating against this CA certificate. - `display_name` `(string: "")` - The `display_name` to set on tokens issued From bce560fb15025d49d3475ee9bca912b6bb15b695 Mon Sep 17 00:00:00 2001 From: Saurabh Pal Date: Wed, 26 Apr 2023 18:07:29 -0700 Subject: [PATCH 2/5] fix conflicts --- builtin/credential/cert/backend_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/credential/cert/backend_test.go b/builtin/credential/cert/backend_test.go index 4ac863e241c9..23fde8b41f3f 100644 --- a/builtin/credential/cert/backend_test.go +++ b/builtin/credential/cert/backend_test.go @@ -2343,7 +2343,7 @@ func TestBackend_CertUpgrade(t *testing.T) { } } -// Test a self-signed client containing subject DN entries that is trusted by root CA. +// TestBackend_subjectoids_singleCert tests a self-signed client cert containing subject DN entries that is trusted by root CA. func TestBackend_subjectoids_singleCert(t *testing.T) { connState, err := testConnState( "test-fixtures/root/rootcawsubjoidscert.pem", From 4b4d0a68b5fc5d292b5f9459e5071aaf195eb5ed Mon Sep 17 00:00:00 2001 From: Saurabh Pal Date: Wed, 26 Apr 2023 22:24:46 -0700 Subject: [PATCH 3/5] Add changelog --- changelog/5453.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/5453.txt diff --git a/changelog/5453.txt b/changelog/5453.txt new file mode 100644 index 000000000000..31ddf24be788 --- /dev/null +++ b/changelog/5453.txt @@ -0,0 +1,3 @@ +```release-note:feature +auth/cert: Verify individual Subject DN entries of a presented client certificate during authentication. +``` \ No newline at end of file From 43cba91b4b24f3c1ccef3e3bf61a927a201aed7e Mon Sep 17 00:00:00 2001 From: Saurabh Pal Date: Wed, 26 Apr 2023 22:38:43 -0700 Subject: [PATCH 4/5] Formatting changes --- builtin/credential/cert/backend_test.go | 2 +- builtin/credential/cert/path_certs.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/credential/cert/backend_test.go b/builtin/credential/cert/backend_test.go index 23fde8b41f3f..94f5c2ef911e 100644 --- a/builtin/credential/cert/backend_test.go +++ b/builtin/credential/cert/backend_test.go @@ -2384,7 +2384,7 @@ func TestBackend_subjectoids_singleCert(t *testing.T) { testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "2.5.4.3:example.com,2.5.4.6:US,2.5.4.8:CA,2.5.4.7:Sunnyvale,2.5.4.10:ExampleOrg,2.5.4.11:ExampleDivision1,2.5.4.11:Example*2,2.5.4.11:ExampleDivision3,0.9.2342.19200300.100.1.25:ExampleDC,2.5.4.4:ExampleSN"}, false), testAccStepLogin(t, connState), - //This Second set of test cases check for all available fields in the Subject DN for globbed pattern(s) + // This Second set of test cases check for all available fields in the Subject DN for globbed pattern(s) testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "0.9.2342.19200300.100.1.1:*UID"}, false), testAccStepLogin(t, connState), testAccStepCert(t, "web", ca, "foo", allowed{sbjDnOids: "0.9.2342.19200300.100.1.1:*UID,2.5.4.3:example*"}, false), diff --git a/builtin/credential/cert/path_certs.go b/builtin/credential/cert/path_certs.go index 3674d6cb15f1..df797cf3158b 100644 --- a/builtin/credential/cert/path_certs.go +++ b/builtin/credential/cert/path_certs.go @@ -218,7 +218,7 @@ certificate.`, Description: tokenutil.DeprecationText("token_bound_cidrs"), Deprecated: true, }, - "required_subject_oids": &framework.FieldSchema{ + "required_subject_oids": { Type: framework.TypeCommaStringSlice, Description: `A comma-separated string or array of subject name entries formatted as "oid:value". Expects the oid value to be some type of ASN1 encoded string. From ab2f6be8af8670c6799dd110d5b3981261ebd69e Mon Sep 17 00:00:00 2001 From: Saurabh Pal Date: Sun, 30 Apr 2023 00:03:27 -0700 Subject: [PATCH 5/5] fix conflicts --- builtin/credential/cert/path_certs.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builtin/credential/cert/path_certs.go b/builtin/credential/cert/path_certs.go index df797cf3158b..3e23c25db250 100644 --- a/builtin/credential/cert/path_certs.go +++ b/builtin/credential/cert/path_certs.go @@ -399,6 +399,9 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr if allowedMetadataExtensionsRaw, ok := d.GetOk("allowed_metadata_extensions"); ok { cert.AllowedMetadataExtensions = allowedMetadataExtensionsRaw.([]string) } + if requiredSubjectOidsRaw, ok := d.GetOk("required_subject_oids"); ok { + cert.RequiredSubjectOids = requiredSubjectOidsRaw.([]string) + } // Get tokenutil fields if err := cert.ParseTokenFields(req, d); err != nil {