mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-30 02:46:30 +02:00
Set the Envoy option only_verify_leaf_cert_crl, to avoid a bug where CRLs cannot be used in combination with an intermediate CA trust root. Update the client certificate validation logic in the authorize service to match this behavior.
454 lines
18 KiB
Go
454 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("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)
|
|
})
|
|
}
|