diff --git a/builtin/credential/cert/backend_test.go b/builtin/credential/cert/backend_test.go index 47d7ae05d544..94f5c2ef911e 100644 --- a/builtin/credential/cert/backend_test.go +++ b/builtin/credential/cert/backend_test.go @@ -1930,6 +1930,7 @@ type allowed struct { organizational_units string // allowed OUs in the certificate ext string // required extensions in the certificate metadata_ext string // allowed metadata extensions to add to identity alias + sbjDnOids string // required subject dn entries } func testAccStepCert(t *testing.T, name string, cert []byte, policies string, testData allowed, expectError bool) logicaltest.TestStep { @@ -1950,6 +1951,7 @@ func testAccStepCertWithExtraParams(t *testing.T, name string, cert []byte, poli "required_extensions": testData.ext, "allowed_metadata_extensions": testData.metadata_ext, "lease": 1000, + "required_subject_oids": testData.sbjDnOids, } for k, v := range extraParams { data[k] = v @@ -2340,3 +2342,105 @@ func TestBackend_CertUpgrade(t *testing.T) { t.Fatal(diff) } } + +// 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", + "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 03a3e5586210..3e23c25db250 100644 --- a/builtin/credential/cert/path_certs.go +++ b/builtin/credential/cert/path_certs.go @@ -218,6 +218,12 @@ certificate.`, Description: tokenutil.DeprecationText("token_bound_cidrs"), Deprecated: true, }, + "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. +All values much match. Supports globbing on "value".`, + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -308,6 +314,7 @@ func (b *backend) pathCertRead(ctx context.Context, req *logical.Request, d *fra "ocsp_servers_override": cert.OcspServersOverride, "ocsp_fail_open": cert.OcspFailOpen, "ocsp_query_all_servers": cert.OcspQueryAllServers, + "required_subject_oids": cert.RequiredSubjectOids, } cert.PopulateTokenData(data) @@ -392,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 { @@ -509,12 +519,12 @@ type CertEntry struct { RequiredExtensions []string AllowedMetadataExtensions []string BoundCIDRs []*sockaddr.SockAddrMarshaler - - OcspCaCertificates string - OcspEnabled bool - OcspServersOverride []string - OcspFailOpen bool - OcspQueryAllServers bool + RequiredSubjectOids []string + OcspCaCertificates string + OcspEnabled bool + OcspServersOverride []string + OcspFailOpen bool + OcspQueryAllServers bool } const pathCertHelpSyn = ` diff --git a/builtin/credential/cert/path_login.go b/builtin/credential/cert/path_login.go index d59c5b4a9195..2e357cdbf1bd 100644 --- a/builtin/credential/cert/path_login.go +++ b/builtin/credential/cert/path_login.go @@ -357,7 +357,8 @@ func (b *backend) matchesConstraints(ctx context.Context, clientCert *x509.Certi b.matchesEmailSANs(clientCert, config) && b.matchesURISANs(clientCert, config) && b.matchesOrganizationalUnits(clientCert, config) && - b.matchesCertificateExtensions(clientCert, config) + b.matchesCertificateExtensions(clientCert, config) && + b.matchesSubjectOids(clientCert, config) if config.Entry.OcspEnabled { ocspGood, err := b.checkForCertInOCSP(ctx, clientCert, trustedChain, conf) if err != nil { @@ -523,6 +524,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 +} + // certificateExtensionsMetadata returns the metadata from configured // metadata extensions func (b *backend) certificateExtensionsMetadata(clientCert *x509.Certificate, config *ParsedCert) map[string]string { 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/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 diff --git a/website/content/api-docs/auth/cert.mdx b/website/content/api-docs/auth/cert.mdx index 82a72596cc3e..e7c84874e87d 100644 --- a/website/content/api-docs/auth/cert.mdx +++ b/website/content/api-docs/auth/cert.mdx @@ -61,6 +61,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. - `allowed_metadata_extensions` `(array:[])` - A comma separated string or array of oid extensions. Upon successful authentication, these extensions will be added as metadata if they are present in the certificate. The