Add wildcard host in Host and HostSNI matchers

This commit is contained in:
Julien Salleyron
2026-03-31 16:14:06 +02:00
committed by GitHub
parent 9a8ff969ac
commit ea92a3e150
27 changed files with 705 additions and 133 deletions
+106 -19
View File
@@ -30,6 +30,41 @@ func TestHTTPSSuite(t *testing.T) {
suite.Run(t, &HTTPSSuite{})
}
// TestWithWildcardHost verifies that a wildcard Host rule Host(`*.snitest.com`)
// routes HTTPS requests for any matching subdomain to the configured service.
func (s *SimpleSuite) TestWithWildcardHost() {
backend := startTestServer("9040", http.StatusOK, "")
defer backend.Close()
file := s.adaptFile("fixtures/https/https_wildcard_host.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
// Wait for Traefik to load the wildcard router.
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second,
try.BodyContains("Host(`*.snitest.com`)"))
require.NoError(s.T(), err)
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
// foo.snitest.com matches the wildcard and must be routed to the backend.
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
require.NoError(s.T(), err)
req.Host = "foo.snitest.com"
err = try.RequestWithTransport(req, 5*time.Second, tr, try.StatusCodeIs(http.StatusOK))
require.NoError(s.T(), err)
// bar.snitest.com also matches the wildcard and must be routed to the backend.
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
require.NoError(s.T(), err)
req.Host = "bar.snitest.com"
err = try.RequestWithTransport(req, 3*time.Second, tr, try.StatusCodeIs(http.StatusOK))
require.NoError(s.T(), err)
}
// TestWithSNIConfigHandshake involves a client sending a SNI hostname of
// "snitest.com", which happens to match the CN of 'snitest.com.crt'. The test
// verifies that traefik presents the correct certificate.
@@ -115,7 +150,6 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute() {
}
// TestWithTLSOptions verifies that traefik routes the requests with the associated tls options.
func (s *HTTPSSuite) TestWithTLSOptions() {
file := s.adaptFile("fixtures/https/https_tls_options.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
@@ -196,8 +230,71 @@ func (s *HTTPSSuite) TestWithTLSOptions() {
require.NoError(s.T(), err)
}
// TestWithConflictingTLSOptions checks that routers with same SNI but different TLS options get fallbacked to the default TLS options.
func (s *HTTPSSuite) TestWithTLSOptionsAndWildcard() {
backend := startTestServer("0", http.StatusNoContent, "")
defer backend.Close()
err := try.GetRequest(backend.URL, 1*time.Second, try.StatusCodeIs(http.StatusNoContent))
require.NoError(s.T(), err)
file := s.adaptFile("fixtures/https/https_wildcard_tls_options.toml", struct{ BackendURL string }{backend.URL})
s.traefikCmd(withConfigFile(file))
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`*.snitest.com`)"))
require.NoError(s.T(), err)
tr1 := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
MaxVersion: tls.VersionTLS12,
MinVersion: tls.VersionTLS12,
ServerName: "bar.snitest.com",
},
}
tr2 := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
MaxVersion: tls.VersionTLS13,
MinVersion: tls.VersionTLS13,
ServerName: "foo.snitest.com",
},
}
tr3 := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
MaxVersion: tls.VersionTLS11,
MinVersion: tls.VersionTLS11,
ServerName: "other.snitest.com",
},
}
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
require.NoError(s.T(), err)
req.Host = tr1.TLSClientConfig.ServerName
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.StatusCodeIs(http.StatusNoContent))
require.NoError(s.T(), err)
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
require.NoError(s.T(), err)
req.Host = tr2.TLSClientConfig.ServerName
err = try.RequestWithTransport(req, 3*time.Second, tr2, try.StatusCodeIs(http.StatusNoContent))
require.NoError(s.T(), err)
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
require.NoError(s.T(), err)
req.Host = tr3.TLSClientConfig.ServerName
err = try.RequestWithTransport(req, 3*time.Second, tr3, try.StatusCodeIs(http.StatusNoContent))
require.NoError(s.T(), err)
}
// TestWithConflictingTLSOptions checks that routers with same SNI but different TLS options get fallbacked to the
// default TLS options.
func (s *HTTPSSuite) TestWithConflictingTLSOptions() {
file := s.adaptFile("fixtures/https/https_tls_options.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
@@ -262,7 +359,6 @@ func (s *HTTPSSuite) TestWithConflictingTLSOptions() {
// 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() {
file := s.adaptFile("fixtures/https/https_sni_strict.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
@@ -284,7 +380,6 @@ func (s *HTTPSSuite) TestWithSNIStrictNotMatchedRequest() {
// 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() {
file := s.adaptFile("fixtures/https/https_sni_default_cert.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
@@ -316,7 +411,6 @@ func (s *HTTPSSuite) TestWithDefaultCertificate() {
// 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() {
file := s.adaptFile("fixtures/https/https_sni_default_cert.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
@@ -348,7 +442,6 @@ func (s *HTTPSSuite) TestWithDefaultCertificateNoSNI() {
// "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() {
file := s.adaptFile("fixtures/https/https_sni_default_cert.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
@@ -381,7 +474,6 @@ func (s *HTTPSSuite) TestWithOverlappingStaticCertificate() {
// "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() {
file := s.adaptFile("fixtures/https/dynamic_https_sni_default_cert.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
@@ -410,9 +502,8 @@ func (s *HTTPSSuite) TestWithOverlappingDynamicCertificate() {
assert.Equal(s.T(), "h2", proto)
}
// TestWithClientCertificateAuthentication
// The client can send a certificate signed by a CA trusted by the server but it's optional.
// TestWithClientCertificateAuthentication tests that a client can send a certificate signed by a CA trusted by the server
// but it's optional.
func (s *HTTPSSuite) TestWithClientCertificateAuthentication() {
file := s.adaptFile("fixtures/https/clientca/https_1ca1config.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
@@ -464,9 +555,8 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthentication() {
assert.NoError(s.T(), err, "should be allowed to connect to server")
}
// TestWithClientCertificateAuthentication
// Use two CA:s and test that clients with client signed by either of them can connect.
// TestWithClientCertificateAuthenticationMultipleCAs uses two CA:s
// and test that clients with client signed by either of them can connect.
func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs() {
server1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { _, _ = rw.Write([]byte("server1")) }))
server2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { _, _ = rw.Write([]byte("server2")) }))
@@ -557,9 +647,8 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs() {
assert.Error(s.T(), err)
}
// TestWithClientCertificateAuthentication
// Use two CA:s in two different files and test that clients with client signed by either of them can connect.
// TestWithClientCertificateAuthenticationMultipleCAsMultipleFiles uses two CA:s in two different files
// and test that clients with client signed by either of them can connect.
func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAsMultipleFiles() {
server1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { _, _ = rw.Write([]byte("server1")) }))
server2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { _, _ = rw.Write([]byte("server2")) }))
@@ -768,7 +857,6 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange() {
// that traefik updates its configuration when the HTTPS configuration is modified and
// it routes the requests to the expected backends thanks to given certificate if possible
// otherwise thanks to the default one.
func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange() {
dynamicConfFileName := s.adaptFile("fixtures/https/dynamic_https.toml", struct{}{})
confFileName := s.adaptFile("fixtures/https/dynamic_https_sni.toml", struct {
@@ -833,7 +921,6 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange() {
// that traefik updates its configuration when the HTTPS configuration is modified, even if it totally deleted, and
// it routes the requests to the expected backends thanks to given certificate if possible
// otherwise thanks to the default one.
func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion() {
dynamicConfFileName := s.adaptFile("fixtures/https/dynamic_https.toml", struct{}{})
confFileName := s.adaptFile("fixtures/https/dynamic_https_sni.toml", struct {
@@ -1143,7 +1230,7 @@ func (s *HTTPSSuite) TestWithInvalidTLSOption() {
}
}
// modifyCertificateConfFileContent replaces the content of a HTTPS configuration file.
// modifyCertificateConfFileContent replaces the content of an HTTPS configuration file.
func (s *HTTPSSuite) modifyCertificateConfFileContent(certFileName, confFileName string) {
file, err := os.OpenFile("./"+confFileName, os.O_WRONLY, os.ModeExclusive)
require.NoError(s.T(), err)