Improve TLS Handshake
This commit is contained in:
parent
2303301d38
commit
689f120410
20 changed files with 819 additions and 60 deletions
11
acme/acme.go
11
acme/acme.go
|
@ -22,7 +22,6 @@ import (
|
|||
"github.com/containous/traefik/log"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/tls/generate"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/eapache/channels"
|
||||
|
@ -57,7 +56,6 @@ type ACME struct {
|
|||
ACMELogging bool `description:"Enable debug logging of ACME actions."`
|
||||
OverrideCertificates bool `description:"Enable to override certificates in key-value store when using storeconfig"`
|
||||
client *acme.Client
|
||||
defaultCertificate *tls.Certificate
|
||||
store cluster.Store
|
||||
challengeHTTPProvider *challengeHTTPProvider
|
||||
challengeTLSProvider *challengeTLSProvider
|
||||
|
@ -76,14 +74,6 @@ func (a *ACME) init() error {
|
|||
legolog.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||
}
|
||||
|
||||
// no certificates in TLS config, so we add a default one
|
||||
cert, err := generate.DefaultCertificate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.defaultCertificate = cert
|
||||
|
||||
a.jobs = channels.NewInfiniteChannel()
|
||||
return nil
|
||||
}
|
||||
|
@ -131,7 +121,6 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
|
|||
a.dynamicCerts = certs
|
||||
a.challengeTLSProvider = &challengeTLSProvider{store: a.store}
|
||||
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
|
||||
tlsConfig.GetCertificate = a.getCertificate
|
||||
a.TLSConfig = tlsConfig
|
||||
|
||||
|
|
|
@ -209,10 +209,7 @@ func runCmd(globalConfiguration *configuration.GlobalConfiguration, configFile s
|
|||
entryPoint.OnDemandListener = acmeprovider.ListenRequest
|
||||
}
|
||||
|
||||
entryPoint.CertificateStore = &traefiktls.CertificateStore{
|
||||
DynamicCerts: &safe.Safe{},
|
||||
StaticCerts: &safe.Safe{},
|
||||
}
|
||||
entryPoint.CertificateStore = traefiktls.NewCertificateStore()
|
||||
acmeprovider.SetCertificateStore(entryPoint.CertificateStore)
|
||||
|
||||
}
|
||||
|
|
|
@ -247,6 +247,17 @@ func makeEntryPointTLS(result map[string]string) (*tls.TLS, error) {
|
|||
if len(result["tls_ciphersuites"]) > 0 {
|
||||
configTLS.CipherSuites = strings.Split(result["tls_ciphersuites"], ",")
|
||||
}
|
||||
|
||||
if len(result["tls_snistrict"]) > 0 {
|
||||
configTLS.SniStrict = toBool(result, "tls_snistrict")
|
||||
}
|
||||
|
||||
if len(result["tls_defaultcertificate_cert"]) > 0 && len(result["tls_defaultcertificate_key"]) > 0 {
|
||||
configTLS.DefaultCertificate = &tls.Certificate{
|
||||
CertFile: tls.FileOrContent(result["tls_defaultcertificate_cert"]),
|
||||
KeyFile: tls.FileOrContent(result["tls_defaultcertificate_key"]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return configTLS, nil
|
||||
|
|
|
@ -111,6 +111,9 @@ TLS:/my/path/foo.cert,/my/path/foo.key;/my/path/goo.cert,/my/path/goo.key;/my/pa
|
|||
TLS
|
||||
TLS.MinVersion:VersionTLS11
|
||||
TLS.CipherSuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384
|
||||
TLS.SniStrict:true
|
||||
TLS.DefaultCertificate.Cert:path/to/foo.cert
|
||||
TLS.DefaultCertificate.Key:path/to/foo.key
|
||||
CA:car
|
||||
CA.Optional:true
|
||||
Redirect.EntryPoint:https
|
||||
|
@ -212,7 +215,7 @@ Define an entrypoint with SNI support.
|
|||
```
|
||||
|
||||
!!! note
|
||||
If an empty TLS configuration is done, default self-signed certificates are generated.
|
||||
If an empty TLS configuration is provided, default self-signed certificates are generated.
|
||||
|
||||
|
||||
### Dynamic Certificates
|
||||
|
@ -375,6 +378,40 @@ To specify an https entry point with a minimum TLS version, and specifying an ar
|
|||
keyFile = "integration/fixtures/https/snitest.org.key"
|
||||
```
|
||||
|
||||
## Strict SNI Checking
|
||||
|
||||
To enable strict SNI checking, so that connections cannot be made if a matching certificate does not exist.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
sniStrict = true
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.com.key"
|
||||
```
|
||||
|
||||
## Default Certificate
|
||||
|
||||
To enable a default certificate to serve, so that connections without SNI or without a matching domain will be served this certificate.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[entryPoints.https.tls.defaultCertificate]
|
||||
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.com.key"
|
||||
```
|
||||
|
||||
!!! note
|
||||
There can only be one `defaultCertificate` set per entrypoint.
|
||||
Use a single set of square brackets `[ ]`, instead of the two needed for normal certificates.
|
||||
If no default certificate is provided, a self-signed certificate will be generated by Traefik, and used instead.
|
||||
|
||||
## Compression
|
||||
|
||||
To enable compression support using gzip format.
|
||||
|
|
|
@ -272,8 +272,6 @@ func (s *AcmeSuite) TestHTTP01OnDemand(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *AcmeSuite) TestHTTP01OnDemandStaticCertificatesWithWildcard(c *check.C) {
|
||||
// FIXME flaky
|
||||
c.Skip("Flaky behavior will be fixed in the next PR")
|
||||
testCase := acmeTestCase{
|
||||
traefikConfFilePath: "fixtures/acme/acme_tls.toml",
|
||||
template: templateModel{
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
logLevel = "DEBUG"
|
||||
|
||||
defaultEntryPoints = ["https"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":4443"
|
||||
[entryPoints.https.tls]
|
||||
[entryPoints.https.tls.defaultCertificate]
|
||||
certFile = "fixtures/https/snitest.com.cert"
|
||||
keyFile = "fixtures/https/snitest.com.key"
|
||||
|
||||
[api]
|
||||
|
||||
[file]
|
||||
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://127.0.0.1:9010"
|
||||
weight = 1
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host:snitest.com"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host:www.snitest.com"
|
||||
|
||||
[[tls]]
|
||||
entryPoints = ["https"]
|
||||
[tls.certificate]
|
||||
certFile = "fixtures/https/wildcard.snitest.com.cert"
|
||||
keyFile = "fixtures/https/wildcard.snitest.com.key"
|
||||
|
||||
[[tls]]
|
||||
entryPoints = ["https"]
|
||||
[tls.certificate]
|
||||
certFile = "fixtures/https/www.snitest.com.cert"
|
||||
keyFile = "fixtures/https/www.snitest.com.key"
|
37
integration/fixtures/https/https_sni_default_cert.toml
Normal file
37
integration/fixtures/https/https_sni_default_cert.toml
Normal file
|
@ -0,0 +1,37 @@
|
|||
logLevel = "DEBUG"
|
||||
|
||||
defaultEntryPoints = ["https"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":4443"
|
||||
[entryPoints.https.tls]
|
||||
[entryPoints.https.tls.defaultCertificate]
|
||||
certFile = "fixtures/https/snitest.com.cert"
|
||||
keyFile = "fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "fixtures/https/wildcard.snitest.com.cert"
|
||||
keyFile = "fixtures/https/wildcard.snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "fixtures/https/www.snitest.com.cert"
|
||||
keyFile = "fixtures/https/www.snitest.com.key"
|
||||
|
||||
[api]
|
||||
|
||||
[file]
|
||||
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://127.0.0.1:9010"
|
||||
weight = 1
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host:snitest.com"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host:www.snitest.com"
|
28
integration/fixtures/https/https_sni_strict.toml
Normal file
28
integration/fixtures/https/https_sni_strict.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
logLevel = "DEBUG"
|
||||
|
||||
defaultEntryPoints = ["https"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":4443"
|
||||
[entryPoints.https.tls]
|
||||
sniStrict = true
|
||||
[entryPoints.https.tls.defaultCertificate]
|
||||
certFile = "fixtures/https/snitest.com.cert"
|
||||
keyFile = "fixtures/https/snitest.com.key"
|
||||
|
||||
[api]
|
||||
|
||||
[file]
|
||||
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://127.0.0.1:9010"
|
||||
weight = 1
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host:snitest.com"
|
29
integration/fixtures/https/wildcard.snitest.com.cert
Normal file
29
integration/fixtures/https/wildcard.snitest.com.cert
Normal file
|
@ -0,0 +1,29 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE4DCCAsgCCQCBCSnAJ0he3jANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJV
|
||||
UzELMAkGA1UECAwCQUwxFjAUBgNVBAMMDSouc25pdGVzdC5jb20wHhcNMTgwNjE5
|
||||
MjAyMTEzWhcNMTgwNzE5MjAyMTEzWjAyMQswCQYDVQQGEwJVUzELMAkGA1UECAwC
|
||||
QUwxFjAUBgNVBAMMDSouc25pdGVzdC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4IC
|
||||
DwAwggIKAoICAQDOuys7ZjGVxwY/Rp5OMECkYNTOfZ1CEyAL0+pod5cd8et7k0cc
|
||||
T+tEP/25rNR9N0d3/AmtPX3XTy1MA05bYGD07cD+8meANJ+rLGMfcWh8QbBCFAWZ
|
||||
+zWkOSwpwW/DvI+67FvxHNa04u3Wv2qUld6qb0mSuZi9hKQ2s6/L3o/SxtDL/G4N
|
||||
rW71ZCkIwfzkIqvh6KWYQCZvOAIF+BFVZ2UgLbRD7/RYDrIIOrNAkW8O26C7Tck/
|
||||
JhDHDCOmfNkYqfhUW+D+GgoCi38uJqngZyxypscKdaA6SM2oFoo1jCShxDrOhvJU
|
||||
rE/l+T2Xtyr+FupJUv93iowibUwHWR5YwRNYOPkdDSG3oSKxz5xzi7Qa8L2fI7wo
|
||||
A50TDVh2AvmMCUufd5adS70bLYBfxdFNmnUhH+LHbg4v83K1eR4xMiWjpvLZ6Oub
|
||||
ufVJF6s5QEw+3K3s31UPbjzam073afSMLBpfHOsbwvcb1MBYWvf0intQo3a8MvYZ
|
||||
DCj3Y7W1Vw8lbn4v1N37KSLSNMMX1SyKxK6386t/AHwFuCM9ygI6f/l/XERL1B61
|
||||
qj9rZngKOo2cW3Yjj+oUETF3nHmcCwKBYTiWLswBI3fg6oFHTMocypY3eKhiyVaU
|
||||
mf9kBMgkDGUjWrAfOEuW9jCaDnag+Yy4XUXOlc/XaT9M2Ajvpfh9gYxWIQIDAQAB
|
||||
MA0GCSqGSIb3DQEBCwUAA4ICAQC3ut8Qeq3pYt/OGTATaUcYxrfqezW5hJN6bVfr
|
||||
/+UN+B0DfGd1/gKRUmb/t3RtmeMctTW6F21c8jyXBObjOhYVV6aE6iF61Uopozux
|
||||
+VZq8H3VJ5Qiu/Yfb5dh0iGf9srREeSAkUHBuJ9qAosM6iJsoaxQuhw/yDSxrhhg
|
||||
3jS850EZYEt9ZFjz3IdSnPCiLYqu+wMOCfT2sqBD3S8JCohTdpzuvKI2KaaY5drW
|
||||
NK4mrpJIjucfZIqbA1hbd/lCqzI4jW6i86GFhoikxxWbJCEuSWOiLJtxVnOvQgxi
|
||||
qOnOIMx88ivIxrZUHTvy2ncv/RH0q5qsaQddPkY+ll1E+1T7L7CeMTAMANS2vdlH
|
||||
nwJgsQqowisLYJQ4ztsbvpZung2szwx4ImASICYF5aVkbuNJd+lRVUoHEfSYNIYM
|
||||
Rtjteu48lYFzoBMwl6TFJA2yvL1LNaTE4/zTDgGx21aDHK14J3eIG+05wZE8AXkC
|
||||
lNGsY6n2Fn6yLK9nLxcOkpGyY62ndZwDEezqr0liOz+CKBeSZwk+VFxcgE3uGo+r
|
||||
DUcfLaU7Lx5KovP1DYAKJR3caSPAIVPnQth2kunCNs4kD7370JmmnvTlS7CTeUBT
|
||||
1P7wLh3QMrq826lGtLXNMasR1w+Q6jVx7HoOD2HFRbJHFv10R/GuWpAWAD8X5m92
|
||||
a/HFDg==
|
||||
-----END CERTIFICATE-----
|
52
integration/fixtures/https/wildcard.snitest.com.key
Normal file
52
integration/fixtures/https/wildcard.snitest.com.key
Normal file
|
@ -0,0 +1,52 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
F6gCgvoJLFw+v5SAX5kx3hxj+QYH5w==
|
||||
-----END PRIVATE KEY-----
|
29
integration/fixtures/https/www.snitest.com.cert
Normal file
29
integration/fixtures/https/www.snitest.com.cert
Normal file
|
@ -0,0 +1,29 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE5DCCAswCCQDXCA89wY62zzANBgkqhkiG9w0BAQsFADA0MQswCQYDVQQGEwJV
|
||||
UzELMAkGA1UECAwCQUwxGDAWBgNVBAMMD3d3dy5zbml0ZXN0LmNvbTAeFw0xODA2
|
||||
MTkyMDIyMTRaFw0xODA3MTkyMDIyMTRaMDQxCzAJBgNVBAYTAlVTMQswCQYDVQQI
|
||||
DAJBTDEYMBYGA1UEAwwPd3d3LnNuaXRlc3QuY29tMIICIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAg8AMIICCgKCAgEA+vPeeTESpGmzGHvyR4kCdGlmJjA9x230ghFU2tdCMl1C
|
||||
aAR3uaZWxg9ldAiu54yvX3ViV/BMpNyQu6Knb293W5wcxidi8aHXcqACRLNtwwmn
|
||||
NMX48Su3OvnU7Dc/fi0mpQLyblxXloCyOG/gtNjzZXVwrn3weMCe/XsvxkpcAOJz
|
||||
7ZZrXCsrQ8pk5V0vMgryQ19zMc+uK3aAPQ+ePFjraWlVH2rOxtzRBGnVM864J9XR
|
||||
tL0ZOAD2gdu4CVIt4xiU24E7W8jfZ3CTePERKhSCBGnkO4roPmRiNwgnP0Wk5lrR
|
||||
kOQkhh4JF+GPMy4IDf6elCmEpnCT39+p36vRSP9sip1OctdfuVyCJMYgb1YCh4k2
|
||||
5CMR+MrkzxzrB2Spl46he5mGkVWXssr70F/gFrIeZPUweh7OBDHnS7twWfhhsElP
|
||||
QYOXpJBWjWJkUKANDqWxM+ObUA+Kjdgk5NEOvQs7yVxpGB8Z9yK+OIJ0k77QDazD
|
||||
VIWhjxjlwgpJW4KALn9xXkUKLhsn7P3hrEDkpTYnr0g22cgPjsgnAFfVVkcloeRi
|
||||
pSfFINIJUBFLGtU0GSyqPJ9aj8CpZZe798nyt6FpSq9AuA2DF0MoECjNbch6C2gi
|
||||
VUqNyuCVjUezw9VtKy3M16GYtnMSsNOY6tnkvfXeXmLrQlfsBs01a8DQBcmOK2MC
|
||||
AwEAATANBgkqhkiG9w0BAQsFAAOCAgEAkugSNyzc+Y6MBE4/Y+Bz5HrGtKIweuar
|
||||
7F70fBk9PgWpKIBJC8s+xJRgBXMFAy5HXZir1tNWvCeJhjCbBZRnpvKvDD61gBcM
|
||||
odde6BLc4r8cRT5l0rILA01cVwyr3C3TzRREThInqNLSsnf845jA9TB9YKN2P6QB
|
||||
TT4j3VMVRlR6OL9EaAUpIWHgKPfqXfbgPQ6rfPrQQGxvZbkL2g85IkpPH+DecN42
|
||||
PK53YZG6NW1+V2Z3agvc2/4qskqoVNdpe3JkafNicokDXTVd24MNtUemWzP3gq0i
|
||||
tv75zgcwLBVVOP43mVFo5e+xZgdS65ZrWyJVL2PG929gARJSEXjLHs9avRXlpXeE
|
||||
tBpCRC5gwvq2fnC7tVbKcbYyH3lr5u3nlfRlfsomoSACC6fw2cQKnp+us/+BsVyA
|
||||
ntqrGxqC/WbQ/LHtk/YJfwFSnuzEPGClKx/F7+EoDETZGAf526VkxLTKtDPmwh5N
|
||||
HFJpeczPE2IdxdaNdOnERUB5xeSDXnObTe3e8jIfpxF0rppGo5Dxw2tfhFscQGBM
|
||||
Cs6cT9gkfX71P81JjrFrbx0bWWDf8N5meNqKqcZNTI15+dDGKXfjr9YbgtI9HHYa
|
||||
Dhb+ondnii+KAcFchC7vCgDvG+bOuWxfM9N808bsBoPXvrKF+iWsOFeKmiV1B2OT
|
||||
w0ZLNJ3AW5o=
|
||||
-----END CERTIFICATE-----
|
52
integration/fixtures/https/www.snitest.com.key
Normal file
52
integration/fixtures/https/www.snitest.com.key
Normal file
|
@ -0,0 +1,52 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
3zJ/pivaO6YjG22SZ5fXH5BkKWnnwGa9
|
||||
-----END PRIVATE KEY-----
|
|
@ -115,6 +115,167 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
|
|||
c.Assert(resp.StatusCode, checker.Equals, http.StatusResetContent)
|
||||
}
|
||||
|
||||
// TestWithSNIStrictNotMatchedRequest involves a client sending a SNI hostname of
|
||||
// "snitest.org", which does not match the CN of 'snitest.com.crt'. The test
|
||||
// verifies that traefik closes the connection.
|
||||
func (s *HTTPSSuite) TestWithSNIStrictNotMatchedRequest(c *check.C) {
|
||||
cmd, display := s.traefikCmd(withConfigFile("fixtures/https/https_sni_strict.toml"))
|
||||
defer display(c)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// wait for Traefik
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 500*time.Millisecond, try.BodyContains("Host:snitest.com"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.org",
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
}
|
||||
// Connection with no matching certificate should fail
|
||||
_, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.NotNil, check.Commentf("failed to connect to server"))
|
||||
}
|
||||
|
||||
// TestWithDefaultCertificate involves a client sending a SNI hostname of
|
||||
// "snitest.org", which does not match the CN of 'snitest.com.crt'. The test
|
||||
// verifies that traefik returns the default certificate.
|
||||
func (s *HTTPSSuite) TestWithDefaultCertificate(c *check.C) {
|
||||
cmd, display := s.traefikCmd(withConfigFile("fixtures/https/https_sni_default_cert.toml"))
|
||||
defer display(c)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// wait for Traefik
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 500*time.Millisecond, try.BodyContains("Host:snitest.com"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.org",
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
}
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
|
||||
defer conn.Close()
|
||||
err = conn.Handshake()
|
||||
c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error"))
|
||||
|
||||
cs := conn.ConnectionState()
|
||||
err = cs.PeerCertificates[0].VerifyHostname("snitest.com")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("certificate did not serve correct default certificate"))
|
||||
|
||||
proto := cs.NegotiatedProtocol
|
||||
c.Assert(proto, checker.Equals, "h2")
|
||||
}
|
||||
|
||||
// TestWithDefaultCertificateNoSNI involves a client sending a request with no ServerName
|
||||
// which does not match the CN of 'snitest.com.crt'. The test
|
||||
// verifies that traefik returns the default certificate.
|
||||
func (s *HTTPSSuite) TestWithDefaultCertificateNoSNI(c *check.C) {
|
||||
cmd, display := s.traefikCmd(withConfigFile("fixtures/https/https_sni_default_cert.toml"))
|
||||
defer display(c)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// wait for Traefik
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 500*time.Millisecond, try.BodyContains("Host:snitest.com"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
}
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
|
||||
defer conn.Close()
|
||||
err = conn.Handshake()
|
||||
c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error"))
|
||||
|
||||
cs := conn.ConnectionState()
|
||||
err = cs.PeerCertificates[0].VerifyHostname("snitest.com")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("certificate did not serve correct default certificate"))
|
||||
|
||||
proto := cs.NegotiatedProtocol
|
||||
c.Assert(proto, checker.Equals, "h2")
|
||||
}
|
||||
|
||||
// TestWithOverlappingCertificate involves a client sending a SNI hostname of
|
||||
// "www.snitest.com", which matches the CN of two static certificates:
|
||||
// 'wildcard.snitest.com.crt', and `www.snitest.com.crt`. The test
|
||||
// verifies that traefik returns the non-wildcard certificate.
|
||||
func (s *HTTPSSuite) TestWithOverlappingStaticCertificate(c *check.C) {
|
||||
cmd, display := s.traefikCmd(withConfigFile("fixtures/https/https_sni_default_cert.toml"))
|
||||
defer display(c)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// wait for Traefik
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 500*time.Millisecond, try.BodyContains("Host:snitest.com"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "www.snitest.com",
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
}
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
|
||||
defer conn.Close()
|
||||
err = conn.Handshake()
|
||||
c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error"))
|
||||
|
||||
cs := conn.ConnectionState()
|
||||
err = cs.PeerCertificates[0].VerifyHostname("www.snitest.com")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("certificate did not serve correct default certificate"))
|
||||
|
||||
proto := cs.NegotiatedProtocol
|
||||
c.Assert(proto, checker.Equals, "h2")
|
||||
}
|
||||
|
||||
// TestWithOverlappingCertificate involves a client sending a SNI hostname of
|
||||
// "www.snitest.com", which matches the CN of two dynamic certificates:
|
||||
// 'wildcard.snitest.com.crt', and `www.snitest.com.crt`. The test
|
||||
// verifies that traefik returns the non-wildcard certificate.
|
||||
func (s *HTTPSSuite) TestWithOverlappingDynamicCertificate(c *check.C) {
|
||||
cmd, display := s.traefikCmd(withConfigFile("fixtures/https/dynamic_https_sni_default_cert.toml"))
|
||||
defer display(c)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// wait for Traefik
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 500*time.Millisecond, try.BodyContains("Host:snitest.com"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "www.snitest.com",
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
}
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
|
||||
defer conn.Close()
|
||||
err = conn.Handshake()
|
||||
c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error"))
|
||||
|
||||
cs := conn.ConnectionState()
|
||||
err = cs.PeerCertificates[0].VerifyHostname("www.snitest.com")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("certificate did not serve correct default certificate"))
|
||||
|
||||
proto := cs.NegotiatedProtocol
|
||||
c.Assert(proto, checker.Equals, "h2")
|
||||
}
|
||||
|
||||
// TestWithClientCertificateAuthentication
|
||||
// The client can send a certificate signed by a CA trusted by the server but it's optional
|
||||
func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) {
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -79,7 +78,7 @@ type serverEntryPoint struct {
|
|||
httpServer *h2c.Server
|
||||
listener net.Listener
|
||||
httpRouter *middlewares.HandlerSwitcher
|
||||
certs *safe.Safe
|
||||
certs *traefiktls.CertificateStore
|
||||
onDemandListener func(string) (*tls.Certificate, error)
|
||||
tlsALPNGetter func(string) (*tls.Certificate, error)
|
||||
}
|
||||
|
@ -276,18 +275,12 @@ func (s *Server) AddListener(listener func(types.Configuration)) {
|
|||
|
||||
// getCertificate allows to customize tlsConfig.GetCertificate behaviour to get the certificates inserted dynamically
|
||||
func (s *serverEntryPoint) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
domainToCheck := types.CanonicalDomain(clientHello.ServerName)
|
||||
bestCertificate := s.certs.GetBestCertificate(clientHello)
|
||||
if bestCertificate != nil {
|
||||
return bestCertificate, nil
|
||||
}
|
||||
|
||||
if s.certs.Get() != nil {
|
||||
for domains, cert := range s.certs.Get().(map[string]*tls.Certificate) {
|
||||
for _, certDomain := range strings.Split(domains, ",") {
|
||||
if types.MatchDomain(domainToCheck, certDomain) {
|
||||
return cert, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("No certificate provided dynamically can check the domain %q, a per default certificate will be used.", domainToCheck)
|
||||
}
|
||||
domainToCheck := types.CanonicalDomain(clientHello.ServerName)
|
||||
|
||||
if s.tlsALPNGetter != nil {
|
||||
cert, err := s.tlsALPNGetter(domainToCheck)
|
||||
|
@ -300,11 +293,17 @@ func (s *serverEntryPoint) getCertificate(clientHello *tls.ClientHelloInfo) (*tl
|
|||
}
|
||||
}
|
||||
|
||||
if s.onDemandListener != nil {
|
||||
if s.onDemandListener != nil && len(domainToCheck) > 0 {
|
||||
// Only check for an onDemandCert if there is a domain name
|
||||
return s.onDemandListener(domainToCheck)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
if s.certs.SniStrict {
|
||||
return nil, fmt.Errorf("strict SNI enabled - No certificate found for domain: %q, closing connection", domainToCheck)
|
||||
}
|
||||
|
||||
log.Debugf("Serving default cert for request: %q", domainToCheck)
|
||||
return s.certs.DefaultCertificate, nil
|
||||
}
|
||||
|
||||
func (s *Server) startProvider() {
|
||||
|
@ -335,7 +334,7 @@ func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefiktls.TL
|
|||
return nil, err
|
||||
}
|
||||
|
||||
s.serverEntryPoints[entryPointName].certs.Set(make(map[string]*tls.Certificate))
|
||||
s.serverEntryPoints[entryPointName].certs.DynamicCerts.Set(make(map[string]*tls.Certificate))
|
||||
|
||||
// ensure http2 enabled
|
||||
config.NextProtos = []string{"h2", "http/1.1", acme.ACMETLS1Protocol}
|
||||
|
@ -345,6 +344,7 @@ func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefiktls.TL
|
|||
tlsOption.ClientCA.Files = tlsOption.ClientCAFiles
|
||||
tlsOption.ClientCA.Optional = false
|
||||
}
|
||||
|
||||
if len(tlsOption.ClientCA.Files) > 0 {
|
||||
pool := x509.NewCertPool()
|
||||
for _, caFile := range tlsOption.ClientCA.Files {
|
||||
|
@ -376,7 +376,7 @@ func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefiktls.TL
|
|||
return false
|
||||
}
|
||||
|
||||
err := s.globalConfiguration.ACME.CreateClusterConfig(s.leadership, config, s.serverEntryPoints[entryPointName].certs, checkOnDemandDomain)
|
||||
err := s.globalConfiguration.ACME.CreateClusterConfig(s.leadership, config, s.serverEntryPoints[entryPointName].certs.DynamicCerts, checkOnDemandDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -385,17 +385,16 @@ func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefiktls.TL
|
|||
config.GetCertificate = s.serverEntryPoints[entryPointName].getCertificate
|
||||
}
|
||||
|
||||
if len(config.Certificates) == 0 {
|
||||
return nil, fmt.Errorf("no certificates found for TLS entrypoint %s", entryPointName)
|
||||
}
|
||||
|
||||
// BuildNameToCertificate parses the CommonName and SubjectAlternateName fields
|
||||
// in each certificate and populates the config.NameToCertificate map.
|
||||
config.BuildNameToCertificate()
|
||||
if len(config.Certificates) != 0 {
|
||||
certMap := s.buildNameOrIPToCertificate(config.Certificates)
|
||||
|
||||
if s.entryPoints[entryPointName].CertificateStore != nil {
|
||||
s.entryPoints[entryPointName].CertificateStore.StaticCerts.Set(config.NameToCertificate)
|
||||
s.entryPoints[entryPointName].CertificateStore.StaticCerts.Set(certMap)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove certs from the TLS config object
|
||||
config.Certificates = []tls.Certificate{}
|
||||
|
||||
// Set the minimum TLS version if set in the config TOML
|
||||
if minConst, exists := traefiktls.MinVersion[s.entryPoints[entryPointName].Configuration.TLS.MinVersion]; exists {
|
||||
|
@ -593,3 +592,24 @@ func stopMetricsClients() {
|
|||
metrics.StopStatsd()
|
||||
metrics.StopInfluxDB()
|
||||
}
|
||||
|
||||
func (s *Server) buildNameOrIPToCertificate(certs []tls.Certificate) map[string]*tls.Certificate {
|
||||
certMap := make(map[string]*tls.Certificate)
|
||||
for i := range certs {
|
||||
cert := &certs[i]
|
||||
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(x509Cert.Subject.CommonName) > 0 {
|
||||
certMap[x509Cert.Subject.CommonName] = cert
|
||||
}
|
||||
for _, san := range x509Cert.DNSNames {
|
||||
certMap[san] = cert
|
||||
}
|
||||
for _, ipSan := range x509Cert.IPAddresses {
|
||||
certMap[ipSan.String()] = cert
|
||||
}
|
||||
}
|
||||
return certMap
|
||||
}
|
||||
|
|
|
@ -19,8 +19,8 @@ import (
|
|||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/middlewares/pipelining"
|
||||
"github.com/containous/traefik/rules"
|
||||
"github.com/containous/traefik/safe"
|
||||
traefiktls "github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/tls/generate"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/eapache/channels"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -55,11 +55,12 @@ func (s *Server) loadConfiguration(configMsg types.ConfigMessage) {
|
|||
s.serverEntryPoints[newServerEntryPointName].httpRouter.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler())
|
||||
|
||||
if s.entryPoints[newServerEntryPointName].Configuration.TLS == nil {
|
||||
if newServerEntryPoint.certs.Get() != nil {
|
||||
if newServerEntryPoint.certs.ContainsCertificates() {
|
||||
log.Debugf("Certificates not added to non-TLS entryPoint %s.", newServerEntryPointName)
|
||||
}
|
||||
} else {
|
||||
s.serverEntryPoints[newServerEntryPointName].certs.Set(newServerEntryPoint.certs.Get())
|
||||
s.serverEntryPoints[newServerEntryPointName].certs.DynamicCerts.Set(newServerEntryPoint.certs.DynamicCerts.Get())
|
||||
s.serverEntryPoints[newServerEntryPointName].certs.ResetCache()
|
||||
}
|
||||
log.Infof("Server configuration reloaded on %s", s.serverEntryPoints[newServerEntryPointName].httpServer.Addr)
|
||||
}
|
||||
|
@ -123,7 +124,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
|
|||
for serverEntryPointName, serverEntryPoint := range serverEntryPoints {
|
||||
serverEntryPoint.httpRouter.GetHandler().SortRoutes()
|
||||
if _, exists := entryPointsCertificates[serverEntryPointName]; exists {
|
||||
serverEntryPoint.certs.Set(entryPointsCertificates[serverEntryPointName])
|
||||
serverEntryPoint.certs.DynamicCerts.Set(entryPointsCertificates[serverEntryPointName])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -559,10 +560,33 @@ func (s *Server) buildServerEntryPoints() map[string]*serverEntryPoint {
|
|||
onDemandListener: entryPoint.OnDemandListener,
|
||||
tlsALPNGetter: entryPoint.TLSALPNGetter,
|
||||
}
|
||||
|
||||
if entryPoint.CertificateStore != nil {
|
||||
serverEntryPoints[entryPointName].certs = entryPoint.CertificateStore.DynamicCerts
|
||||
serverEntryPoints[entryPointName].certs = entryPoint.CertificateStore
|
||||
} else {
|
||||
serverEntryPoints[entryPointName].certs = &safe.Safe{}
|
||||
serverEntryPoints[entryPointName].certs = traefiktls.NewCertificateStore()
|
||||
}
|
||||
|
||||
if entryPoint.Configuration.TLS != nil {
|
||||
serverEntryPoints[entryPointName].certs.SniStrict = entryPoint.Configuration.TLS.SniStrict
|
||||
|
||||
if entryPoint.Configuration.TLS.DefaultCertificate != nil {
|
||||
cert, err := tls.LoadX509KeyPair(entryPoint.Configuration.TLS.DefaultCertificate.CertFile.String(), entryPoint.Configuration.TLS.DefaultCertificate.KeyFile.String())
|
||||
if err != nil {
|
||||
}
|
||||
serverEntryPoints[entryPointName].certs.DefaultCertificate = &cert
|
||||
} else {
|
||||
cert, err := generate.DefaultCertificate()
|
||||
if err != nil {
|
||||
}
|
||||
serverEntryPoints[entryPointName].certs.DefaultCertificate = cert
|
||||
}
|
||||
if len(entryPoint.Configuration.TLS.Certificates) > 0 {
|
||||
config, _ := entryPoint.Configuration.TLS.Certificates.CreateTLSConfig(entryPointName)
|
||||
certMap := s.buildNameOrIPToCertificate(config.Certificates)
|
||||
serverEntryPoints[entryPointName].certs.StaticCerts.Set(certMap)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return serverEntryPoints
|
||||
|
|
|
@ -215,7 +215,7 @@ func TestServerLoadCertificateWithDefaultEntryPoint(t *testing.T) {
|
|||
srv := NewServer(globalConfig, nil, entryPoints)
|
||||
if mapEntryPoints, err := srv.loadConfig(dynamicConfigs, globalConfig); err != nil {
|
||||
t.Fatalf("got error: %s", err)
|
||||
} else if mapEntryPoints["https"].certs.Get() == nil {
|
||||
} else if !mapEntryPoints["https"].certs.ContainsCertificates() {
|
||||
t.Fatal("got error: https entryPoint must have TLS certificates.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,16 +152,28 @@ func (c *Certificate) AppendCertificates(certs map[string]map[string]*tls.Certif
|
|||
|
||||
parsedCert, _ := x509.ParseCertificate(tlsCert.Certificate[0])
|
||||
|
||||
certKey := parsedCert.Subject.CommonName
|
||||
var SANs []string
|
||||
if parsedCert.Subject.CommonName != "" {
|
||||
SANs = append(SANs, parsedCert.Subject.CommonName)
|
||||
}
|
||||
if parsedCert.DNSNames != nil {
|
||||
sort.Strings(parsedCert.DNSNames)
|
||||
for _, dnsName := range parsedCert.DNSNames {
|
||||
if dnsName != parsedCert.Subject.CommonName {
|
||||
certKey += fmt.Sprintf(",%s", dnsName)
|
||||
SANs = append(SANs, dnsName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if parsedCert.IPAddresses != nil {
|
||||
for _, ip := range parsedCert.IPAddresses {
|
||||
if ip.String() != parsedCert.Subject.CommonName {
|
||||
SANs = append(SANs, ip.String())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
certKey := strings.Join(SANs, ",")
|
||||
|
||||
certExists := false
|
||||
if certs[ep] == nil {
|
||||
|
|
|
@ -2,14 +2,32 @@ package tls
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
// CertificateStore store for dynamic and static certificates
|
||||
type CertificateStore struct {
|
||||
DynamicCerts *safe.Safe
|
||||
StaticCerts *safe.Safe
|
||||
DefaultCertificate *tls.Certificate
|
||||
CertCache *cache.Cache
|
||||
SniStrict bool
|
||||
}
|
||||
|
||||
// NewCertificateStore create a store for dynamic and static certificates
|
||||
func NewCertificateStore() *CertificateStore {
|
||||
return &CertificateStore{
|
||||
StaticCerts: &safe.Safe{},
|
||||
DynamicCerts: &safe.Safe{},
|
||||
CertCache: cache.New(1*time.Hour, 10*time.Minute),
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllDomains return a slice with all the certificate domain
|
||||
|
@ -31,3 +49,89 @@ func (c CertificateStore) GetAllDomains() []string {
|
|||
}
|
||||
return allCerts
|
||||
}
|
||||
|
||||
// GetBestCertificate returns the best match certificate, and caches the response
|
||||
func (c CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo) *tls.Certificate {
|
||||
domainToCheck := strings.ToLower(strings.TrimSpace(clientHello.ServerName))
|
||||
if len(domainToCheck) == 0 {
|
||||
// If no ServerName is provided, Check for local IP address matches
|
||||
host, _, err := net.SplitHostPort(clientHello.Conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
log.Debugf("Could not split host/port: %v", err)
|
||||
}
|
||||
domainToCheck = strings.TrimSpace(host)
|
||||
}
|
||||
|
||||
if cert, ok := c.CertCache.Get(domainToCheck); ok {
|
||||
return cert.(*tls.Certificate)
|
||||
}
|
||||
|
||||
matchedCerts := map[string]*tls.Certificate{}
|
||||
if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil {
|
||||
for domains, cert := range c.DynamicCerts.Get().(map[string]*tls.Certificate) {
|
||||
for _, certDomain := range strings.Split(domains, ",") {
|
||||
if MatchDomain(domainToCheck, certDomain) {
|
||||
matchedCerts[certDomain] = cert
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.StaticCerts != nil && c.StaticCerts.Get() != nil {
|
||||
for domains, cert := range c.StaticCerts.Get().(map[string]*tls.Certificate) {
|
||||
for _, certDomain := range strings.Split(domains, ",") {
|
||||
if MatchDomain(domainToCheck, certDomain) {
|
||||
matchedCerts[certDomain] = cert
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(matchedCerts) > 0 {
|
||||
// sort map by keys
|
||||
keys := make([]string, 0, len(matchedCerts))
|
||||
for k := range matchedCerts {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// cache best match
|
||||
c.CertCache.SetDefault(domainToCheck, matchedCerts[keys[len(keys)-1]])
|
||||
return matchedCerts[keys[len(keys)-1]]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainsCertificates checks if there are any certs in the store
|
||||
func (c CertificateStore) ContainsCertificates() bool {
|
||||
return c.StaticCerts.Get() != nil || c.DynamicCerts.Get() != nil
|
||||
}
|
||||
|
||||
// ResetCache clears the cache in the store
|
||||
func (c CertificateStore) ResetCache() {
|
||||
if c.CertCache != nil {
|
||||
c.CertCache.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// MatchDomain return true if a domain match the cert domain
|
||||
func MatchDomain(domain string, certDomain string) bool {
|
||||
if domain == certDomain {
|
||||
return true
|
||||
}
|
||||
|
||||
for len(certDomain) > 0 && certDomain[len(certDomain)-1] == '.' {
|
||||
certDomain = certDomain[:len(certDomain)-1]
|
||||
}
|
||||
|
||||
labels := strings.Split(domain, ".")
|
||||
for i := range labels {
|
||||
labels[i] = "*"
|
||||
candidate := strings.Join(labels, ".")
|
||||
if certDomain == candidate {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
134
tls/certificate_store_test.go
Normal file
134
tls/certificate_store_test.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetBestCertificate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
domainToCheck string
|
||||
staticCert string
|
||||
dynamicCert string
|
||||
expectedCert string
|
||||
}{
|
||||
{
|
||||
desc: "Empty Store, returns no certs",
|
||||
domainToCheck: "snitest.com",
|
||||
staticCert: "",
|
||||
dynamicCert: "",
|
||||
expectedCert: "",
|
||||
},
|
||||
{
|
||||
desc: "Empty static cert store",
|
||||
domainToCheck: "snitest.com",
|
||||
staticCert: "",
|
||||
dynamicCert: "snitest.com",
|
||||
expectedCert: "snitest.com",
|
||||
},
|
||||
{
|
||||
desc: "Empty dynamic cert store",
|
||||
domainToCheck: "snitest.com",
|
||||
staticCert: "snitest.com",
|
||||
dynamicCert: "",
|
||||
expectedCert: "snitest.com",
|
||||
},
|
||||
{
|
||||
desc: "Best Match",
|
||||
domainToCheck: "snitest.com",
|
||||
staticCert: "snitest.com",
|
||||
dynamicCert: "snitest.org",
|
||||
expectedCert: "snitest.com",
|
||||
},
|
||||
{
|
||||
desc: "Best Match with wildcard dynamic and exact static",
|
||||
domainToCheck: "www.snitest.com",
|
||||
staticCert: "www.snitest.com",
|
||||
dynamicCert: "*.snitest.com",
|
||||
expectedCert: "www.snitest.com",
|
||||
},
|
||||
{
|
||||
desc: "Best Match with wildcard static and exact dynamic",
|
||||
domainToCheck: "www.snitest.com",
|
||||
staticCert: "*.snitest.com",
|
||||
dynamicCert: "www.snitest.com",
|
||||
expectedCert: "www.snitest.com",
|
||||
},
|
||||
{
|
||||
desc: "Best Match with static wildcard only",
|
||||
domainToCheck: "www.snitest.com",
|
||||
staticCert: "*.snitest.com",
|
||||
dynamicCert: "",
|
||||
expectedCert: "*.snitest.com",
|
||||
},
|
||||
{
|
||||
desc: "Best Match with dynamic wildcard only",
|
||||
domainToCheck: "www.snitest.com",
|
||||
staticCert: "",
|
||||
dynamicCert: "*.snitest.com",
|
||||
expectedCert: "*.snitest.com",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
staticMap := map[string]*tls.Certificate{}
|
||||
dynamicMap := map[string]*tls.Certificate{}
|
||||
|
||||
if test.staticCert != "" {
|
||||
cert, err := loadTestCert(test.staticCert)
|
||||
require.NoError(t, err)
|
||||
staticMap[test.staticCert] = cert
|
||||
}
|
||||
|
||||
if test.dynamicCert != "" {
|
||||
cert, err := loadTestCert(test.dynamicCert)
|
||||
require.NoError(t, err)
|
||||
dynamicMap[test.dynamicCert] = cert
|
||||
}
|
||||
|
||||
store := &CertificateStore{
|
||||
DynamicCerts: safe.New(dynamicMap),
|
||||
StaticCerts: safe.New(staticMap),
|
||||
CertCache: cache.New(1*time.Hour, 10*time.Minute),
|
||||
}
|
||||
|
||||
var expected *tls.Certificate
|
||||
if test.expectedCert != "" {
|
||||
cert, err := loadTestCert(test.expectedCert)
|
||||
require.NoError(t, err)
|
||||
expected = cert
|
||||
}
|
||||
|
||||
clientHello := &tls.ClientHelloInfo{
|
||||
ServerName: test.domainToCheck,
|
||||
}
|
||||
|
||||
actual := store.GetBestCertificate(clientHello)
|
||||
assert.Equal(t, expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func loadTestCert(certName string) (*tls.Certificate, error) {
|
||||
staticCert, err := tls.LoadX509KeyPair(
|
||||
fmt.Sprintf("../integration/fixtures/https/%s.cert", strings.Replace(certName, "*", "wildcard", -1)),
|
||||
fmt.Sprintf("../integration/fixtures/https/%s.key", strings.Replace(certName, "*", "wildcard", -1)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &staticCert, nil
|
||||
}
|
|
@ -27,6 +27,8 @@ type TLS struct {
|
|||
Certificates Certificates
|
||||
ClientCAFiles []string // Deprecated
|
||||
ClientCA ClientCA
|
||||
DefaultCertificate *Certificate
|
||||
SniStrict bool `export:"true"`
|
||||
}
|
||||
|
||||
// RootCAs hold the CA we want to have in root
|
||||
|
|
Loading…
Reference in a new issue