mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-01 03:16:31 +02:00
Add a new match_subject_alt_names option to the downstream_mtls settings group. This setting can be used to further constrain the allowed client certificates by requiring that certificates contain a Subject Alternative Name of a particular type, matching a particular regex. When set, populate the corresponding match_typed_subject_alt_names setting within Envoy, and also implement a corresponding check in the authorize service.
464 lines
18 KiB
Go
464 lines
18 KiB
Go
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("missing CRL", func(t *testing.T) {
|
|
// If a CRL is provided for any CA, it must be provided for all CAs.
|
|
valid, err := isValidClientCertificate(testCA, testCRL, ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidIntermediateCert,
|
|
Intermediates: testIntermediateCA,
|
|
}, noConstraints)
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.False(t, valid, "should return false")
|
|
})
|
|
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)
|
|
})
|
|
}
|