Implement Case-insensitive SNI matching
This commit is contained in:
parent
3b01488c8d
commit
5b3762be08
6 changed files with 139 additions and 9 deletions
|
@ -0,0 +1,36 @@
|
||||||
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":4443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
[entryPoints.https.tls.defaultCertificate]
|
||||||
|
certFile = "fixtures/https/wildcard.snitest.com.cert"
|
||||||
|
keyFile = "fixtures/https/wildcard.snitest.com.key"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
|
||||||
|
[providers]
|
||||||
|
[providers.file]
|
||||||
|
|
||||||
|
[Routers]
|
||||||
|
[Routers.router1]
|
||||||
|
Service = "service1"
|
||||||
|
rule = "HostRegexp: {subdomain:[a-z1-9-]+}.snitest.com"
|
||||||
|
[Routers.router2]
|
||||||
|
Service = "service1"
|
||||||
|
rule = "HostRegexp: {subdomain:[a-z1-9-]+}.www.snitest.com"
|
||||||
|
|
||||||
|
[Services]
|
||||||
|
[Services.service1]
|
||||||
|
[Services.service1.LoadBalancer]
|
||||||
|
|
||||||
|
[[Services.service1.LoadBalancer.Servers]]
|
||||||
|
URL = "http://127.0.0.1:9010"
|
||||||
|
Weight = 1
|
||||||
|
|
||||||
|
[[tls]]
|
||||||
|
entryPoints = ["https"]
|
||||||
|
[tls.certificate]
|
||||||
|
certFile = "fixtures/https/uppercase_wildcard.www.snitest.com.cert"
|
||||||
|
keyFile = "fixtures/https/uppercase_wildcard.www.snitest.com.key"
|
|
@ -0,0 +1,19 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDDDCCAfSgAwIBAgIJAI1YpPACcsQnMA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNV
|
||||||
|
BAMME0ZPTy5XV1cuU05JVEVTVC5DT00wHhcNMTgxMDI5MTU1NDI4WhcNMjgxMDI2
|
||||||
|
MTU1NDI4WjAeMRwwGgYDVQQDDBNGT08uV1dXLlNOSVRFU1QuQ09NMIIBIjANBgkq
|
||||||
|
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxyWr+1O/tf4yjwhfp3/SDGT5fD0chhGs
|
||||||
|
Qc+QM7Ewb5SOmIL5UskxT5pCKc6Kuie5qqEp9xH8Rrfo18iEJQPdhFC1YkaBEI0L
|
||||||
|
l1qvN4jmXzAK/E/u4+X+FFprHyruXCmuXqsWQt/qEOqU1ciN47GE9+ZW4R+q70uB
|
||||||
|
zrEQ+dzN7IBsyf1lzzS3/TwDgj085QmiZYxKxX40d5hZW6AHxPEKJa2p+Gweqg74
|
||||||
|
SpzBWL1DYQLcqHUuMKlbigHg+gleqcO8NiHT5UdeSPVokD5VJPO1La1PMqkLmJYr
|
||||||
|
3vVkQ9YzNQ615bX98VMIi17cmE7LE+vz+v287cdFT2f1pNXr3pCGzQIDAQABo00w
|
||||||
|
SzALBgNVHQ8EBAMCBDAwCQYDVR0TBAIwADATBgNVHSUEDDAKBggrBgEFBQcDATAc
|
||||||
|
BgNVHREEFTATghEqLldXVy5TTklURVNULkNPTTANBgkqhkiG9w0BAQsFAAOCAQEA
|
||||||
|
HJyMCj9oHwECmSGWHnYHkO42zeyj24RKlhNG5skUCqZmpmeDc2BRMYH4fjP75MD2
|
||||||
|
kuasZBMAxyQnur/DEn8TzQ1mlKxYCqoza1ql5PkfcwNUp/tvQ7Jhf45Z5mQVeUM7
|
||||||
|
RSiBhpeetjHY5/xQb7gXHa97+OjDoRJ6NL/dzGxqypf37kiQPw2jWI5RTFBkP+h/
|
||||||
|
sPbeAZJjmiEzvw31SAw9IGj3VvIwcuTxbsdJQITU7hCXDSd1EIocmzAoobY7WRcT
|
||||||
|
B1pLmHlP/BaIsM7m0NF/HgUsgo/kgSsxnGA2MHMYQiTImR2DUgrJYzKlJ5acscLK
|
||||||
|
sMq9xUnjr6KF1C15R2FpDw==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHJav7U7+1/jKP
|
||||||
|
CF+nf9IMZPl8PRyGEaxBz5AzsTBvlI6YgvlSyTFPmkIpzoq6J7mqoSn3EfxGt+jX
|
||||||
|
yIQlA92EULViRoEQjQuXWq83iOZfMAr8T+7j5f4UWmsfKu5cKa5eqxZC3+oQ6pTV
|
||||||
|
yI3jsYT35lbhH6rvS4HOsRD53M3sgGzJ/WXPNLf9PAOCPTzlCaJljErFfjR3mFlb
|
||||||
|
oAfE8Qolran4bB6qDvhKnMFYvUNhAtyodS4wqVuKAeD6CV6pw7w2IdPlR15I9WiQ
|
||||||
|
PlUk87UtrU8yqQuYlive9WRD1jM1DrXltf3xUwiLXtyYTssT6/P6/bztx0VPZ/Wk
|
||||||
|
1evekIbNAgMBAAECggEAVOFEnTmD47D1oasjAgRj5a5/+6kcaDROJDqwrqeeCmDa
|
||||||
|
KjzgwZ1JLDGGc8U5scBOzWAlv83lpcqrLpWjZRdxqfywYrPEPOaxAxC+z7/E2Ntk
|
||||||
|
Q0hafL5BfjFPqRgmQhft3yGyukwvuogRadEyUNMP5o1BiHBz7cxUBmHH54dqKZuO
|
||||||
|
ueUMgqraJX/GK+Om2rIUst0oOT9yUED+f6ciIjVAmCx1EVxZmX7sxKig10e70eOJ
|
||||||
|
rfHlRguJWtxy0+Wl8R8TVrpI5r7qsE8y2fet9RqFOof/4ds8uA2nlZ3NpGkAq3Oo
|
||||||
|
+65h/2fjD5uQ7jmT+XZcbC7SGhboV42zIrmn0DyNIQKBgQDneeqzMlooNzLD6x+v
|
||||||
|
bXo6BJAHXuml440zS5i5RawKc3+/GxGQjBvnfhFH6AQ7cL4ohYyfuAo4srgifRle
|
||||||
|
x3Gl8yvFf0uLaQHj811HPWV0fU8bwekI77jmH7WZi2ED/qX7X06R2vvUPGshvJi5
|
||||||
|
yPCmJpDQQA6wmxBG1U4SqNw0xQKBgQDcPu2DMAJpbMWWeb5xWv5/6h6TUF4tV7fV
|
||||||
|
eIBWuVfe9Jry3gAnb6YUOKYmA5xYJJ+fTz4Nhe4+LQbFS1esT/7ZIATvILogZc3S
|
||||||
|
X9+ZCYG/tmDDZvhZqIWWSzzdrjb7dseP1RI4Wp6OnRqHWErrkfzDJKuN15qgW5vR
|
||||||
|
FUR2ykV6aQKBgQCv5ZQ00dly3+ciu+QbAb00o0zzXOt91Lnytcp7V3dRhc0YYrBp
|
||||||
|
QB7gPYtSMfwtUxIdZsaihE64IQ8NnjSOMk6pRW0Iqh+083mtR7ylKwGSkLpxpFu6
|
||||||
|
H7hInuX3pNN3HqXwq87fxSFCeRsLyu3fl9NO3tWCenrvNxYaTXMDeO/E5QKBgE7D
|
||||||
|
XlMU/zfOg1bN0PJe1TbPdgG+sv9KKF76CgN5otgD58nE5I812VHP9HMRxX6sEj15
|
||||||
|
rDpP1CR+G7bAu+jObtgdIEaYEJf3cES0rpTfFnyF71LR5yzBHIzj+S9Z1yXUk4d3
|
||||||
|
bl2i4qMjwdH3HEvkWF09JvDB0vVX7YA3N9W3fmNJAoGBALRi9EbkEBW1vMPwMzps
|
||||||
|
YoJ1lp/YyDGTFcg6KFgTfNaOYccb6EXL2Cd21qvDsJw6wthXS+cSqX3qlTLAVLY8
|
||||||
|
az/NfyFmW1fUtGjs2s0ZtplStGBhv8VR+2fpt9fgDOOrGYiN2dtmPm7jCAmyQQq7
|
||||||
|
JCg7Vq6f0q95DUwiUAo24CBn
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -830,3 +830,37 @@ func (s *HTTPSSuite) TestEntrypointHttpsRedirectAndPathModification(c *check.C)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestWithSNIDynamicCaseInsensitive involves a client sending a SNI hostname of
|
||||||
|
// "bar.www.snitest.com", which matches the DNS SAN of '*.WWW.SNITEST.COM'. The test
|
||||||
|
// verifies that traefik presents the correct certificate.
|
||||||
|
func (s *HTTPSSuite) TestWithSNIDynamicCaseInsensitive(c *check.C) {
|
||||||
|
cmd, display := s.traefikCmd(withConfigFile("fixtures/https/https_sni_case_insensitive_dynamic.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/file/routers", 500*time.Millisecond, try.BodyContains("HostRegexp: {subdomain:[a-z1-9-]+}.www.snitest.com"))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
ServerName: "bar.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 match SNI servername"))
|
||||||
|
|
||||||
|
proto := conn.ConnectionState().NegotiatedProtocol
|
||||||
|
c.Assert(proto, checker.Equals, "h2")
|
||||||
|
}
|
||||||
|
|
|
@ -154,13 +154,13 @@ func (c *Certificate) AppendCertificates(certs map[string]map[string]*tls.Certif
|
||||||
|
|
||||||
var SANs []string
|
var SANs []string
|
||||||
if parsedCert.Subject.CommonName != "" {
|
if parsedCert.Subject.CommonName != "" {
|
||||||
SANs = append(SANs, parsedCert.Subject.CommonName)
|
SANs = append(SANs, strings.ToLower(parsedCert.Subject.CommonName))
|
||||||
}
|
}
|
||||||
if parsedCert.DNSNames != nil {
|
if parsedCert.DNSNames != nil {
|
||||||
sort.Strings(parsedCert.DNSNames)
|
sort.Strings(parsedCert.DNSNames)
|
||||||
for _, dnsName := range parsedCert.DNSNames {
|
for _, dnsName := range parsedCert.DNSNames {
|
||||||
if dnsName != parsedCert.Subject.CommonName {
|
if dnsName != parsedCert.Subject.CommonName {
|
||||||
SANs = append(SANs, dnsName)
|
SANs = append(SANs, strings.ToLower(dnsName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ func (c *Certificate) AppendCertificates(certs map[string]map[string]*tls.Certif
|
||||||
if parsedCert.IPAddresses != nil {
|
if parsedCert.IPAddresses != nil {
|
||||||
for _, ip := range parsedCert.IPAddresses {
|
for _, ip := range parsedCert.IPAddresses {
|
||||||
if ip.String() != parsedCert.Subject.CommonName {
|
if ip.String() != parsedCert.Subject.CommonName {
|
||||||
SANs = append(SANs, ip.String())
|
SANs = append(SANs, strings.ToLower(ip.String()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ func TestGetBestCertificate(t *testing.T) {
|
||||||
domainToCheck string
|
domainToCheck string
|
||||||
dynamicCert string
|
dynamicCert string
|
||||||
expectedCert string
|
expectedCert string
|
||||||
|
uppercase bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "Empty Store, returns no certs",
|
desc: "Empty Store, returns no certs",
|
||||||
|
@ -45,6 +46,13 @@ func TestGetBestCertificate(t *testing.T) {
|
||||||
dynamicCert: "*.snitest.com",
|
dynamicCert: "*.snitest.com",
|
||||||
expectedCert: "*.snitest.com",
|
expectedCert: "*.snitest.com",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Best Match with dynamic wildcard only, case insensitive",
|
||||||
|
domainToCheck: "bar.www.snitest.com",
|
||||||
|
dynamicCert: "*.www.snitest.com",
|
||||||
|
expectedCert: "*.www.snitest.com",
|
||||||
|
uppercase: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
@ -54,9 +62,9 @@ func TestGetBestCertificate(t *testing.T) {
|
||||||
dynamicMap := map[string]*tls.Certificate{}
|
dynamicMap := map[string]*tls.Certificate{}
|
||||||
|
|
||||||
if test.dynamicCert != "" {
|
if test.dynamicCert != "" {
|
||||||
cert, err := loadTestCert(test.dynamicCert)
|
cert, err := loadTestCert(test.dynamicCert, test.uppercase)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
dynamicMap[test.dynamicCert] = cert
|
dynamicMap[strings.ToLower(test.dynamicCert)] = cert
|
||||||
}
|
}
|
||||||
|
|
||||||
store := &CertificateStore{
|
store := &CertificateStore{
|
||||||
|
@ -66,7 +74,7 @@ func TestGetBestCertificate(t *testing.T) {
|
||||||
|
|
||||||
var expected *tls.Certificate
|
var expected *tls.Certificate
|
||||||
if test.expectedCert != "" {
|
if test.expectedCert != "" {
|
||||||
cert, err := loadTestCert(test.expectedCert)
|
cert, err := loadTestCert(test.expectedCert, test.uppercase)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
expected = cert
|
expected = cert
|
||||||
}
|
}
|
||||||
|
@ -81,10 +89,15 @@ func TestGetBestCertificate(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadTestCert(certName string) (*tls.Certificate, error) {
|
func loadTestCert(certName string, uppercase bool) (*tls.Certificate, error) {
|
||||||
|
replacement := "wildcard"
|
||||||
|
if uppercase {
|
||||||
|
replacement = "uppercase_wildcard"
|
||||||
|
}
|
||||||
|
|
||||||
staticCert, err := tls.LoadX509KeyPair(
|
staticCert, err := tls.LoadX509KeyPair(
|
||||||
fmt.Sprintf("../integration/fixtures/https/%s.cert", strings.Replace(certName, "*", "wildcard", -1)),
|
fmt.Sprintf("../integration/fixtures/https/%s.cert", strings.Replace(certName, "*", replacement, -1)),
|
||||||
fmt.Sprintf("../integration/fixtures/https/%s.key", strings.Replace(certName, "*", "wildcard", -1)),
|
fmt.Sprintf("../integration/fixtures/https/%s.key", strings.Replace(certName, "*", replacement, -1)),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
Loading…
Reference in a new issue