package evaluator import ( "regexp" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/pomerium/pomerium/config" ) // These certificates can be regenerated by running: // // go run ./gen-test-certs.go // // (Copy and paste the output here.) const ( testCA = ` -----BEGIN CERTIFICATE----- MIIBaDCCAQ6gAwIBAgICEAAwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzMwODA3MTgwMzIxWjAaMRgw FgYDVQQDEw9UcnVzdGVkIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC AAR2/RkzmSK6paoeTKFx1Bd52ZCg29ulJlMxFdSZT8FlmmaK9mN6KWwO+NHYObiW y3AQuoSTrZXlrlRW5ANvMI+io0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ BAUwAwEB/zAdBgNVHQ4EFgQUJGFVU2UOvOVgaY9YcCUiunGpiCQwCgYIKoZIzj0E AwIDSAAwRQIhAMU5/NjpitOSbUobtjeOriPH8JRo9qy1iFyeVNAcdVvgAiAewq2A PhgzWTw5F9PJg++9i+xGQTqHs3ZirG27cCjvhQ== -----END CERTIFICATE----- ` testValidCert = ` -----BEGIN CERTIFICATE----- MIIBYjCCAQigAwIBAgICEAEwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzMwODA3MTgwMzIxWjAeMRww GgYDVQQDExN0cnVzdGVkIGNsaWVudCBjZXJ0MFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEcWa1Bz6mpsLnM1VD8gtzELjzjEp9Dopp/xWScFO9qtay5SBOeX+Ftr0O 8+/RkoKHzGgZ80gr6xQyUJL3MCwVZKM4MDYwEwYDVR0lBAwwCgYIKwYBBQUHAwIw HwYDVR0jBBgwFoAUJGFVU2UOvOVgaY9YcCUiunGpiCQwCgYIKoZIzj0EAwIDSAAw RQIgXM1ogmy0vcz4lYzji5X3In1n2GLOFNTgucFPkM0GtqgCIQCsXPs/0OjSFyDR FBqAm1NqDJcxq685fS9t3VfHwapcVA== -----END CERTIFICATE----- ` testUntrustedCert = ` -----BEGIN CERTIFICATE----- MIIBZjCCAQygAwIBAgICEAEwCgYIKoZIzj0EAwIwHDEaMBgGA1UEAxMRVW50cnVz dGVkIFJvb3QgQ0EwIBgPMDAwMTAxMDEwMDAwMDBaFw0zMzA4MDcxODAzMjFaMCAx HjAcBgNVBAMTFXVudHJ1c3RlZCBjbGllbnQgY2VydDBZMBMGByqGSM49AgEGCCqG SM49AwEHA0IABJxEIKqLhhMEm5XZXkT+p+hlC2TFyaW0HIZqoE9navJrAcUB8L2M mVQ+/wLaCznJHLeSLn46uGH5p1hoGFqOrdajODA2MBMGA1UdJQQMMAoGCCsGAQUF BwMCMB8GA1UdIwQYMBaAFIp2rlIiSnr33ea3cGyLsX4LEYwWMAoGCCqGSM49BAMC A0gAMEUCIDtJIZJDcqIYaDXhZFs0nd0nHER8IGP9n4BBFMWewAb2AiEAlQyavOxw iTQQxt0rXB4Ox5zWpU9q68+F9BGBkQKTsBs= -----END CERTIFICATE----- ` testRevokedCert = ` -----BEGIN CERTIFICATE----- MIIBYjCCAQigAwIBAgICEAIwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzMwODA3MTgwMzIxWjAeMRww GgYDVQQDExNyZXZva2VkIGNsaWVudCBjZXJ0MFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEcnoO4EM72C7xL31RE9e6m9YJYyF6E4JloASECd8mdiXPlMXIjq8MZHB5 28mFAVQNE7erAtBftID1SbuY4IpXxqM4MDYwEwYDVR0lBAwwCgYIKwYBBQUHAwIw HwYDVR0jBBgwFoAUJGFVU2UOvOVgaY9YcCUiunGpiCQwCgYIKoZIzj0EAwIDSAAw RQIgUUETSO064YIu+VKnyRb0yBnNTjXLy3TvGuYgZI8VX0YCIQDd0gyNEC5YLvRN njxfnLoimp+TzTVzvsCokUbNSNRKJA== -----END CERTIFICATE----- ` testCRL = ` -----BEGIN X509 CRL----- MIHeMIGFAgEBMAoGCCqGSM49BAMCMBoxGDAWBgNVBAMTD1RydXN0ZWQgUm9vdCBD QRgPMDAwMTAxMDEwMDAwMDBaMBUwEwICEAIXDTIzMDgxMDE4MDMyMVqgMDAuMB8G A1UdIwQYMBaAFCRhVVNlDrzlYGmPWHAlIrpxqYgkMAsGA1UdFAQEAgIgADAKBggq hkjOPQQDAgNIADBFAiEAumtTtjiQt1VsbsEnyr+xbpK0KmzKvkpxIVgE1M9CND0C IA8zx5clcaGIT5xRnBLZW7RwA37IOmB+7zjAuJQpmKKp -----END X509 CRL----- ` testIntermediateCA = ` -----BEGIN CERTIFICATE----- MIIBkTCCATegAwIBAgICEAMwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzMwODA3MTgwMzIxWjAiMSAw HgYDVQQDExdUcnVzdGVkIEludGVybWVkaWF0ZSBDQTBZMBMGByqGSM49AgEGCCqG SM49AwEHA0IABMY+zxL/2dNORuha3uVVOXZYIkTpa9V8N9UVrM15HOHkrdLlz1qk 4wbePkkoGtNRzoayb0iZqeA4YjOxqyPG8emjYzBhMA4GA1UdDwEB/wQEAwIBBjAP BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQpGNmcLLM3vHiOADYGPDQL8AhkyDAf BgNVHSMEGDAWgBQkYVVTZQ685WBpj1hwJSK6camIJDAKBggqhkjOPQQDAgNIADBF AiEAnR6xrk7OCk91ymtzU+duZXDqDq35w0oO+MM8nqpac4YCIED+6c9dJKvRCc/C nP8PMxRaUsbQet1woE7Fckn5tK4N -----END CERTIFICATE----- ` testValidIntermediateCert = ` -----BEGIN CERTIFICATE----- MIIBdTCCARqgAwIBAgICEAAwCgYIKoZIzj0EAwIwIjEgMB4GA1UEAxMXVHJ1c3Rl ZCBJbnRlcm1lZGlhdGUgQ0EwIBgPMDAwMTAxMDEwMDAwMDBaFw0zMzA4MDcxODAz MjFaMCgxJjAkBgNVBAMTHWNsaWVudCBjZXJ0IGZyb20gaW50ZXJtZWRpYXRlMFkw EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5SVyYjNRuuFXGjEmCcuVtMq7e2bmndPK bRJ7lJ5cc0kZSoNJes5wXOtGRFbx3+admRHq+w1XEBXOe+yRUB8kdKM4MDYwEwYD VR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUKRjZnCyzN7x4jgA2Bjw0C/AI ZMgwCgYIKoZIzj0EAwIDSQAwRgIhAMj0O2wDRLoxGIPUDOmUfYxmxglOecQhSkWO NBtItSxmAiEAy0XCzvpL6XOZU3zxyCjTdJQa2RiC6YnypMaCaETzCaU= -----END CERTIFICATE----- ` testValidCertWithDNSSANs = ` -----BEGIN CERTIFICATE----- MIIBlTCCATugAwIBAgICEAQwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzMwODA3MTgwMzIxWjAYMRYw FAYDVQQDEw1jbGllbnQgY2VydCAzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE LFai39b7TYauNjg4M58f0qY6jTC7xEOhE84wTTcevZvH/t2Y7U0BBNGkvpb14yxh 60vrRKZA9t9G6ZvWKcY/BKNxMG8wEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0j BBgwFoAUJGFVU2UOvOVgaY9YcCUiunGpiCQwNwYDVR0RBDAwLoIVYS5jbGllbnQz LmV4YW1wbGUuY29tghViLmNsaWVudDMuZXhhbXBsZS5jb20wCgYIKoZIzj0EAwID SAAwRQIgBSw8MsKWPPcpGtuVpNJonTEthIOjIGXswxiYG49y2BECIQC5D1DCX/lY KSwF4aapPx4906VujTL+Ehj8L5ImUYcPbA== -----END CERTIFICATE----- ` testValidCertWithEmailSAN = ` -----BEGIN CERTIFICATE----- MIIBezCCASKgAwIBAgICEAUwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzMwODA3MTgwMzIxWjAYMRYw FAYDVQQDEw1jbGllbnQgY2VydCA0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE WvX8BnCrzUSpLrYka8ed+bz6/HoXUvq5nRqysKe0nGYSsXKRjxLdCG8AKsoGIQIv KOQScf/4TJUNIUY4XOsFI6NYMFYwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0j BBgwFoAUJGFVU2UOvOVgaY9YcCUiunGpiCQwHgYDVR0RBBcwFYETY2xpZW50NEBl eGFtcGxlLmNvbTAKBggqhkjOPQQDAgNHADBEAiAMYGTjUBqgnai8UL3B/iQkCkMb xgCC1ZYdZaJ1RBwFfgIgIhjQZ2s6dTaah/LzYJ9ZwMvSA86XQvzTVSuT6s+RJw0= -----END CERTIFICATE----- ` testValidCertWithIPSAN = ` -----BEGIN CERTIFICATE----- MIIBbTCCAROgAwIBAgICEAYwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzMwODA3MTgwMzIxWjAYMRYw FAYDVQQDEw1jbGllbnQgY2VydCA1MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE 6+9BE+aomrR2Mdnx4iFY63t0hsVD6rYaHBW1b9roFQX6Cor4YeUfkEEF4LrGeAyb wcqb6G1ExgNyjEh10Ai1M6NJMEcwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0j BBgwFoAUJGFVU2UOvOVgaY9YcCUiunGpiCQwDwYDVR0RBAgwBocEwKgKCjAKBggq hkjOPQQDAgNIADBFAiEA/mq32YZZAacOH/P/wjvfD1n74DD/GkhW4kfS72Z0oGQC IAQ+L8E78JOLaPWXiL7WFpVrb0hOHkV2m9Qw4GB41mUN -----END CERTIFICATE----- ` testValidCertWithURISAN = ` -----BEGIN CERTIFICATE----- MIIBhTCCASugAwIBAgICEAcwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzMwODA3MTgwMzIxWjAYMRYw FAYDVQQDEw1jbGllbnQgY2VydCA2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE 7mHladapSY5PlVYToL1dHr0tJPGe5XVP8DSZJz+WWyS9tWsuEsK6P5yeZrbWASOX foH7iVIdx3DMyukGsvMX+KNhMF8wEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0j BBgwFoAUJGFVU2UOvOVgaY9YcCUiunGpiCQwJwYDVR0RBCAwHoYcc3BpZmZlOi8v ZXhhbXBsZS5jb20vZm9vL2JhcjAKBggqhkjOPQQDAgNIADBFAiAhzElKeGJzp2zP GOTUEy0f6b2tvMYGDLQxCcp4bc4QuQIhAPwX4Y3Cr7uazQlbwL6D51y9NCcDyj3D Z18vZNxm9ZR1 -----END CERTIFICATE----- ` ) func Test_isValidClientCertificate(t *testing.T) { t.Parallel() var noConstraints ClientCertConstraints t.Run("no ca", func(t *testing.T) { valid, err := isValidClientCertificate( "", "", ClientCertificateInfo{Leaf: "WHATEVER!"}, noConstraints) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") }) t.Run("no cert", func(t *testing.T) { valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{}, noConstraints) assert.NoError(t, err, "should not return an error") assert.False(t, valid, "should return false") }) t.Run("valid cert", func(t *testing.T) { valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCert, }, noConstraints) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") }) t.Run("valid cert with intermediate", func(t *testing.T) { valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidIntermediateCert, Intermediates: testIntermediateCA, }, noConstraints) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") }) t.Run("valid cert missing intermediate", func(t *testing.T) { valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidIntermediateCert, Intermediates: "", }, noConstraints) assert.NoError(t, err, "should not return an error") assert.False(t, valid, "should return false") }) t.Run("intermediate CA as root", func(t *testing.T) { valid, err := isValidClientCertificate(testIntermediateCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidIntermediateCert, }, noConstraints) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") }) t.Run("unsigned cert", func(t *testing.T) { valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testUntrustedCert, }, noConstraints) assert.NoError(t, err, "should not return an error") assert.False(t, valid, "should return false") }) t.Run("not a cert", func(t *testing.T) { valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: "WHATEVER!", }, noConstraints) assert.Error(t, err, "should return an error") assert.False(t, valid, "should return false") }) t.Run("revoked cert", func(t *testing.T) { revokedCertInfo := ClientCertificateInfo{ Presented: true, Leaf: testRevokedCert, } // The "revoked cert" should otherwise be valid (when no CRL is specified). valid, err := isValidClientCertificate(testCA, "", revokedCertInfo, noConstraints) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") valid, err = isValidClientCertificate(testCA, testCRL, revokedCertInfo, noConstraints) assert.NoError(t, err, "should not return an error") assert.False(t, valid, "should return false") // Specifying a CRL containing the revoked cert should not affect other certs. valid, err = isValidClientCertificate(testCA, testCRL, ClientCertificateInfo{ Presented: true, Leaf: testValidCert, }, noConstraints) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") }) t.Run("chain too deep", func(t *testing.T) { valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidIntermediateCert, Intermediates: testIntermediateCA, }, ClientCertConstraints{MaxVerifyDepth: 1}) assert.NoError(t, err, "should not return an error") assert.False(t, valid, "should return false") }) t.Run("any SAN", func(t *testing.T) { matchAnySAN := ClientCertConstraints{SANMatchers: SANMatchers{ config.SANTypeDNS: regexp.MustCompile("^.*$"), config.SANTypeEmail: regexp.MustCompile("^.*$"), config.SANTypeIPAddress: regexp.MustCompile("^.*$"), config.SANTypeURI: regexp.MustCompile("^.*$"), }} valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCert, // no SANs }, matchAnySAN) assert.NoError(t, err, "should not return an error") assert.False(t, valid, "should return false") valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCertWithDNSSANs, }, matchAnySAN) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCertWithEmailSAN, }, matchAnySAN) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCertWithIPSAN, }, matchAnySAN) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCertWithURISAN, }, matchAnySAN) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") }) t.Run("DNS SAN", func(t *testing.T) { valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCertWithDNSSANs, }, ClientCertConstraints{SANMatchers: SANMatchers{ config.SANTypeDNS: regexp.MustCompile(`^a\..*\.example\.com$`), }}) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCertWithDNSSANs, }, ClientCertConstraints{SANMatchers: SANMatchers{ config.SANTypeEmail: regexp.MustCompile(`^a\..*\.example\.com$`), // mismatched type }}) assert.NoError(t, err, "should not return an error") assert.False(t, valid, "should return false") }) t.Run("email SAN", func(t *testing.T) { valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCertWithEmailSAN, }, ClientCertConstraints{SANMatchers: SANMatchers{ config.SANTypeEmail: regexp.MustCompile(`^.*@example\.com$`), }}) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCertWithEmailSAN, }, ClientCertConstraints{SANMatchers: SANMatchers{ config.SANTypeIPAddress: regexp.MustCompile(`^.*@example\.com$`), // mismatched type }}) assert.NoError(t, err, "should not return an error") assert.False(t, valid, "should return false") }) t.Run("IP address SAN", func(t *testing.T) { valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCertWithIPSAN, }, ClientCertConstraints{SANMatchers: SANMatchers{ config.SANTypeIPAddress: regexp.MustCompile(`^192\.168\.10\..*$`), }}) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCertWithIPSAN, }, ClientCertConstraints{SANMatchers: SANMatchers{ config.SANTypeURI: regexp.MustCompile(`^192\.168\.10\..*$`), // mismatched type }}) assert.NoError(t, err, "should not return an error") assert.False(t, valid, "should return false") }) t.Run("URI SAN", func(t *testing.T) { valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCertWithURISAN, }, ClientCertConstraints{SANMatchers: SANMatchers{ config.SANTypeURI: regexp.MustCompile(`^spiffe://example\.com/.*$`), }}) assert.NoError(t, err, "should not return an error") assert.True(t, valid, "should return true") valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{ Presented: true, Leaf: testValidCertWithURISAN, }, ClientCertConstraints{SANMatchers: SANMatchers{ config.SANTypeDNS: regexp.MustCompile(`^spiffe://example\.com/.*$`), // mismatched type }}) assert.NoError(t, err, "should not return an error") assert.False(t, valid, "should return false") }) } func TestClientCertConstraintsFromConfig(t *testing.T) { t.Parallel() t.Run("default constraints", func(t *testing.T) { var s config.DownstreamMTLSSettings c, err := ClientCertConstraintsFromConfig(&s) assert.NoError(t, err) assert.Equal(t, &ClientCertConstraints{MaxVerifyDepth: 1}, c) }) t.Run("no constraints", func(t *testing.T) { s := config.DownstreamMTLSSettings{ MaxVerifyDepth: new(uint32), } c, err := ClientCertConstraintsFromConfig(&s) assert.NoError(t, err) assert.Equal(t, &ClientCertConstraints{}, c) }) t.Run("larger max depth", func(t *testing.T) { depth := uint32(5) s := config.DownstreamMTLSSettings{ MaxVerifyDepth: &depth, } c, err := ClientCertConstraintsFromConfig(&s) assert.NoError(t, err) assert.Equal(t, &ClientCertConstraints{MaxVerifyDepth: 5}, c) }) t.Run("one SAN match", func(t *testing.T) { s := config.DownstreamMTLSSettings{ MatchSubjectAltNames: []config.SANMatcher{ {Type: config.SANTypeDNS, Pattern: `.*\.corp\.example\.com`}, }, } c, err := ClientCertConstraintsFromConfig(&s) assert.NoError(t, err) assert.Equal(t, &ClientCertConstraints{ MaxVerifyDepth: 1, SANMatchers: SANMatchers{ config.SANTypeDNS: regexp.MustCompile(`^(.*\.corp\.example\.com)$`), }, }, c) }) t.Run("multiple SAN matches", func(t *testing.T) { s := config.DownstreamMTLSSettings{ MatchSubjectAltNames: []config.SANMatcher{ {Type: config.SANTypeDNS, Pattern: `.*\.foo\.example\.com`}, {Type: config.SANTypeDNS, Pattern: `.*\.bar\.example\.com`}, }, } c, err := ClientCertConstraintsFromConfig(&s) assert.NoError(t, err) assert.Equal(t, &ClientCertConstraints{ MaxVerifyDepth: 1, SANMatchers: SANMatchers{ config.SANTypeDNS: regexp.MustCompile(`^(.*\.foo\.example\.com)|(.*\.bar\.example\.com)$`), }, }, c) }) t.Run("multiple SAN matches", func(t *testing.T) { s := config.DownstreamMTLSSettings{ MatchSubjectAltNames: []config.SANMatcher{ {Type: config.SANTypeDNS, Pattern: `.*\.foo\.example\.com`}, {Type: config.SANTypeEmail, Pattern: `.*@example\.com`}, }, } c, err := ClientCertConstraintsFromConfig(&s) assert.NoError(t, err) assert.Equal(t, &ClientCertConstraints{ MaxVerifyDepth: 1, SANMatchers: SANMatchers{ config.SANTypeDNS: regexp.MustCompile(`^(.*\.foo\.example\.com)$`), config.SANTypeEmail: regexp.MustCompile(`^(.*@example\.com)$`), }, }, c) }) t.Run("bad SAN match expression", func(t *testing.T) { s := config.DownstreamMTLSSettings{ MatchSubjectAltNames: []config.SANMatcher{ {Type: config.SANTypeDNS, Pattern: "["}, }, } _, err := ClientCertConstraintsFromConfig(&s) require.Error(t, err) }) }