355 lines
11 KiB
Go
355 lines
11 KiB
Go
package tls
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/traefik/traefik/v3/pkg/types"
|
|
)
|
|
|
|
// LocalhostCert is a PEM-encoded TLS cert with SAN IPs
|
|
// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
|
|
// generated from src/crypto/tls:
|
|
// go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
|
var (
|
|
localhostCert = types.FileOrContent(`-----BEGIN CERTIFICATE-----
|
|
MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS
|
|
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
|
|
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
|
MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r
|
|
bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U
|
|
aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P
|
|
YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk
|
|
POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu
|
|
h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE
|
|
AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
|
|
DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv
|
|
bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI
|
|
5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv
|
|
cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2
|
|
+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B
|
|
grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK
|
|
5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/
|
|
WkBKOclmOV2xlTVuPw==
|
|
-----END CERTIFICATE-----`)
|
|
|
|
// LocalhostKey is the private key for localhostCert.
|
|
localhostKey = types.FileOrContent(`-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDoZtrm0dXV0Aqi
|
|
4Bpc7f95sNRTiu/AJSD8I1onY9PnEsPg3VVxvytsVJbYdcqr4w99V3AgpH/UNzMS
|
|
gAZ/8lZBNbsSDOVesJ3euVqMRfYPvd9pYl6QPRRpSDPm+2tNdn3QFAvta9EgJ3sW
|
|
URnoU85w+W6aLI2bNSq3AaE771p3VbkGolpEjo9h+i42TBHo1rhPNKPkGupR8/QX
|
|
AOLMpInRdeaHyDwb2a3DE5I3dG7VAVzrVfJ6W6Q84YoFX+rpEE2SVM17SAjy6xQy
|
|
VjKgLvK2mk0xbtfa+h0B6VK7bmODHZqeP18NVm6HsBcXn7iclLgAC3SfWU1jucZK
|
|
x1lqzw9tAgMBAAECggEABWzxS1Y2wckblnXY57Z+sl6YdmLV+gxj2r8Qib7g4ZIk
|
|
lIlWR1OJNfw7kU4eryib4fc6nOh6O4AWZyYqAK6tqNQSS/eVG0LQTLTTEldHyVJL
|
|
dvBe+MsUQOj4nTndZW+QvFzbcm2D8lY5n2nBSxU5ypVoKZ1EqQzytFcLZpTN7d89
|
|
EPj0qDyrV4NZlWAwL1AygCwnlwhMQjXEalVF1ylXwU3QzyZ/6MgvF6d3SSUlh+sq
|
|
XefuyigXw484cQQgbzopv6niMOmGP3of+yV4JQqUSb3IDmmT68XjGd2Dkxl4iPki
|
|
6ZwXf3CCi+c+i/zVEcufgZ3SLf8D99kUGE7v7fZ6AQKBgQD1ZX3RAla9hIhxCf+O
|
|
3D+I1j2LMrdjAh0ZKKqwMR4JnHX3mjQI6LwqIctPWTU8wYFECSh9klEclSdCa64s
|
|
uI/GNpcqPXejd0cAAdqHEEeG5sHMDt0oFSurL4lyud0GtZvwlzLuwEweuDtvT9cJ
|
|
Wfvl86uyO36IW8JdvUprYDctrQKBgQDycZ697qutBieZlGkHpnYWUAeImVA878sJ
|
|
w44NuXHvMxBPz+lbJGAg8Cn8fcxNAPqHIraK+kx3po8cZGQywKHUWsxi23ozHoxo
|
|
+bGqeQb9U661TnfdDspIXia+xilZt3mm5BPzOUuRqlh4Y9SOBpSWRmEhyw76w4ZP
|
|
OPxjWYAgwQKBgA/FehSYxeJgRjSdo+MWnK66tjHgDJE8bYpUZsP0JC4R9DL5oiaA
|
|
brd2fI6Y+SbyeNBallObt8LSgzdtnEAbjIH8uDJqyOmknNePRvAvR6mP4xyuR+Bv
|
|
m+Lgp0DMWTw5J9CKpydZDItc49T/mJ5tPhdFVd+am0NAQnmr1MCZ6nHxAoGABS3Y
|
|
LkaC9FdFUUqSU8+Chkd/YbOkuyiENdkvl6t2e52jo5DVc1T7mLiIrRQi4SI8N9bN
|
|
/3oJWCT+uaSLX2ouCtNFunblzWHBrhxnZzTeqVq4SLc8aESAnbslKL4i8/+vYZlN
|
|
s8xtiNcSvL+lMsOBORSXzpj/4Ot8WwTkn1qyGgECgYBKNTypzAHeLE6yVadFp3nQ
|
|
Ckq9yzvP/ib05rvgbvrne00YeOxqJ9gtTrzgh7koqJyX1L4NwdkEza4ilDWpucn0
|
|
xiUZS4SoaJq6ZvcBYS62Yr1t8n09iG47YL8ibgtmH3L+svaotvpVxVK+d7BLevA/
|
|
ZboOWVe3icTy64BT3OQhmg==
|
|
-----END RSA PRIVATE KEY-----`)
|
|
)
|
|
|
|
func TestTLSInStore(t *testing.T) {
|
|
dynamicConfigs := []*CertAndStores{{
|
|
Certificate: Certificate{
|
|
CertFile: localhostCert,
|
|
KeyFile: localhostKey,
|
|
},
|
|
}}
|
|
|
|
tlsManager := NewManager()
|
|
tlsManager.UpdateConfigs(context.Background(), nil, nil, dynamicConfigs)
|
|
|
|
certs := tlsManager.GetStore("default").DynamicCerts.Get().(map[string]*tls.Certificate)
|
|
if len(certs) == 0 {
|
|
t.Fatal("got error: default store must have TLS certificates.")
|
|
}
|
|
}
|
|
|
|
func TestTLSInvalidStore(t *testing.T) {
|
|
dynamicConfigs := []*CertAndStores{{
|
|
Certificate: Certificate{
|
|
CertFile: localhostCert,
|
|
KeyFile: localhostKey,
|
|
},
|
|
}}
|
|
|
|
tlsManager := NewManager()
|
|
tlsManager.UpdateConfigs(context.Background(),
|
|
map[string]Store{
|
|
"default": {
|
|
DefaultCertificate: &Certificate{
|
|
CertFile: "/wrong",
|
|
KeyFile: "/wrong",
|
|
},
|
|
},
|
|
}, nil, dynamicConfigs)
|
|
|
|
certs := tlsManager.GetStore("default").DynamicCerts.Get().(map[string]*tls.Certificate)
|
|
if len(certs) == 0 {
|
|
t.Fatal("got error: default store must have TLS certificates.")
|
|
}
|
|
}
|
|
|
|
func TestManager_Get(t *testing.T) {
|
|
dynamicConfigs := []*CertAndStores{{
|
|
Certificate: Certificate{
|
|
CertFile: localhostCert,
|
|
KeyFile: localhostKey,
|
|
},
|
|
}}
|
|
|
|
tlsConfigs := map[string]Options{
|
|
"foo": {MinVersion: "VersionTLS12"},
|
|
"bar": {MinVersion: "VersionTLS11"},
|
|
"invalid": {CurvePreferences: []string{"42"}},
|
|
}
|
|
|
|
testCases := []struct {
|
|
desc string
|
|
tlsOptionsName string
|
|
expectedMinVersion uint16
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "Get a tls config from a valid name",
|
|
tlsOptionsName: "foo",
|
|
expectedMinVersion: uint16(tls.VersionTLS12),
|
|
},
|
|
{
|
|
desc: "Get another tls config from a valid name",
|
|
tlsOptionsName: "bar",
|
|
expectedMinVersion: uint16(tls.VersionTLS11),
|
|
},
|
|
{
|
|
desc: "Get a tls config from an invalid name",
|
|
tlsOptionsName: "unknown",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "Get a tls config from unexisting 'default' name",
|
|
tlsOptionsName: "default",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "Get an invalid tls config",
|
|
tlsOptionsName: "invalid",
|
|
expectedError: true,
|
|
},
|
|
}
|
|
|
|
tlsManager := NewManager()
|
|
tlsManager.UpdateConfigs(context.Background(), nil, tlsConfigs, dynamicConfigs)
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
config, err := tlsManager.Get("default", test.tlsOptionsName)
|
|
if test.expectedError {
|
|
require.Nil(t, config)
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, test.expectedMinVersion, config.MinVersion)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientAuth(t *testing.T) {
|
|
tlsConfigs := map[string]Options{
|
|
"eca": {
|
|
ClientAuth: ClientAuth{},
|
|
},
|
|
"ecat": {
|
|
ClientAuth: ClientAuth{ClientAuthType: ""},
|
|
},
|
|
"ncc": {
|
|
ClientAuth: ClientAuth{ClientAuthType: "NoClientCert"},
|
|
},
|
|
"rcc": {
|
|
ClientAuth: ClientAuth{ClientAuthType: "RequestClientCert"},
|
|
},
|
|
"racc": {
|
|
ClientAuth: ClientAuth{ClientAuthType: "RequireAnyClientCert"},
|
|
},
|
|
"vccig": {
|
|
ClientAuth: ClientAuth{
|
|
CAFiles: []types.FileOrContent{localhostCert},
|
|
ClientAuthType: "VerifyClientCertIfGiven",
|
|
},
|
|
},
|
|
"vccigwca": {
|
|
ClientAuth: ClientAuth{ClientAuthType: "VerifyClientCertIfGiven"},
|
|
},
|
|
"ravcc": {
|
|
ClientAuth: ClientAuth{ClientAuthType: "RequireAndVerifyClientCert"},
|
|
},
|
|
"ravccwca": {
|
|
ClientAuth: ClientAuth{
|
|
CAFiles: []types.FileOrContent{localhostCert},
|
|
ClientAuthType: "RequireAndVerifyClientCert",
|
|
},
|
|
},
|
|
"ravccwbca": {
|
|
ClientAuth: ClientAuth{
|
|
CAFiles: []types.FileOrContent{"Bad content"},
|
|
ClientAuthType: "RequireAndVerifyClientCert",
|
|
},
|
|
},
|
|
"ucat": {
|
|
ClientAuth: ClientAuth{ClientAuthType: "Unknown"},
|
|
},
|
|
}
|
|
|
|
block, _ := pem.Decode([]byte(localhostCert))
|
|
cert, err := x509.ParseCertificate(block.Bytes)
|
|
require.NoError(t, err)
|
|
|
|
testCases := []struct {
|
|
desc string
|
|
tlsOptionsName string
|
|
expectedClientAuth tls.ClientAuthType
|
|
expectedRawSubject []byte
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "Empty ClientAuth option should get a tls.NoClientCert (default value)",
|
|
tlsOptionsName: "eca",
|
|
expectedClientAuth: tls.NoClientCert,
|
|
},
|
|
{
|
|
desc: "Empty ClientAuthType option should get a tls.NoClientCert (default value)",
|
|
tlsOptionsName: "ecat",
|
|
expectedClientAuth: tls.NoClientCert,
|
|
},
|
|
{
|
|
desc: "NoClientCert option should get a tls.NoClientCert as ClientAuthType",
|
|
tlsOptionsName: "ncc",
|
|
expectedClientAuth: tls.NoClientCert,
|
|
},
|
|
{
|
|
desc: "RequestClientCert option should get a tls.RequestClientCert as ClientAuthType",
|
|
tlsOptionsName: "rcc",
|
|
expectedClientAuth: tls.RequestClientCert,
|
|
},
|
|
{
|
|
desc: "RequireAnyClientCert option should get a tls.RequireAnyClientCert as ClientAuthType",
|
|
tlsOptionsName: "racc",
|
|
expectedClientAuth: tls.RequireAnyClientCert,
|
|
},
|
|
{
|
|
desc: "VerifyClientCertIfGiven option should get a tls.VerifyClientCertIfGiven as ClientAuthType",
|
|
tlsOptionsName: "vccig",
|
|
expectedClientAuth: tls.VerifyClientCertIfGiven,
|
|
},
|
|
{
|
|
desc: "VerifyClientCertIfGiven option without CAFiles yields a default ClientAuthType (NoClientCert)",
|
|
tlsOptionsName: "vccigwca",
|
|
expectedClientAuth: tls.NoClientCert,
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "RequireAndVerifyClientCert option without CAFiles yields a default ClientAuthType (NoClientCert)",
|
|
tlsOptionsName: "ravcc",
|
|
expectedClientAuth: tls.NoClientCert,
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "RequireAndVerifyClientCert option should get a tls.RequireAndVerifyClientCert as ClientAuthType with CA files",
|
|
tlsOptionsName: "ravccwca",
|
|
expectedClientAuth: tls.RequireAndVerifyClientCert,
|
|
expectedRawSubject: cert.RawSubject,
|
|
},
|
|
{
|
|
desc: "Unknown option yields a default ClientAuthType (NoClientCert)",
|
|
tlsOptionsName: "ucat",
|
|
expectedClientAuth: tls.NoClientCert,
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "Bad CA certificate content yields a default ClientAuthType (NoClientCert)",
|
|
tlsOptionsName: "ravccwbca",
|
|
expectedClientAuth: tls.NoClientCert,
|
|
expectedError: true,
|
|
},
|
|
}
|
|
|
|
tlsManager := NewManager()
|
|
tlsManager.UpdateConfigs(context.Background(), nil, tlsConfigs, nil)
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
config, err := tlsManager.Get("default", test.tlsOptionsName)
|
|
|
|
if test.expectedError {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
|
|
assert.NoError(t, err)
|
|
|
|
if test.expectedRawSubject != nil {
|
|
subjects := config.ClientCAs.Subjects()
|
|
assert.Len(t, subjects, 1)
|
|
assert.Equal(t, test.expectedRawSubject, subjects[0])
|
|
}
|
|
|
|
assert.Equal(t, test.expectedClientAuth, config.ClientAuth)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestManager_Get_DefaultValues(t *testing.T) {
|
|
tlsManager := NewManager()
|
|
|
|
// Ensures we won't break things for Traefik users when updating Go
|
|
config, _ := tlsManager.Get("default", "default")
|
|
assert.Equal(t, uint16(tls.VersionTLS12), config.MinVersion)
|
|
assert.Equal(t, []string{"h2", "http/1.1", "acme-tls/1"}, config.NextProtos)
|
|
assert.Equal(t, []uint16{
|
|
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
|
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
|
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_AES_128_GCM_SHA256,
|
|
tls.TLS_AES_256_GCM_SHA384,
|
|
tls.TLS_CHACHA20_POLY1305_SHA256,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
|
}, config.CipherSuites)
|
|
}
|