CVE-2022-46153 (published 2022-12-08T22:15Z)

Traefik is an open source HTTP reverse proxy and load balancer. In affected versions there is a potential vulnerability in Traefik managing TLS connections. A router configured with a not well-formatted TLSOption is exposed with an empty TLSOption. For instance, a route secured using an mTLS connection set with a wrong CA file is exposed without verifying the client certificates. Users are advised to upgrade to version 2.9.6. Users unable to upgrade should check their logs to detect the error messages and fix your TLS options.

CVSS v3 base score: 6.5, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-295
cwe_name: Improper Certificate Validation
description: The product does not validate, or incorrectly validates, a certificate.
extended_description: When a certificate is invalid or malicious, it might allow an attacker to spoof a trusted entity by interfering in the communication path between the host and client. The product might connect to a malicious host while believing it is a trusted host, or the product might be deceived into accepting spoofed data that appears to originate from a trusted host.
         

Fix information:
repo_url: https://github.com/traefik/traefik
commit_details: repo_url: https://github.com/traefik/traefik
author: mpl
author_date: 2022-12-06 18:28:05+01:00
msg: Handle broken TLS conf better

Co-authored-by: Jean-Baptiste Doumenjou <925513+jbdoumenjou@users.noreply.github.com>
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
repository: repo_url: https://github.com/traefik/traefik
repo_name: traefik/traefik
description: The Cloud Native Application Proxy
repo_language: Go
owner: traefik
file_changes: filename: https_invalid_tls_options.toml
change_type: ModificationType.ADD
diff: @@ -0,0 +1,60 @@
+[global]
+  checkNewVersion = false
+  sendAnonymousUsage = false
+
+[log]
+  level = "DEBUG"
+
+[entryPoints.websecure]
+  address = ":4443"
+
+[api]
+  insecure = true
+
+[providers.file]
+  filename = "{{ .SelfFilename }}"
+
+## dynamic configuration ##
+
+[http.routers]
+
+  [http.routers.router1]
+    entryPoints = ["websecure"]
+    service = "service1"
+    rule = "Host(`snitest.com`)"
+    [http.routers.router1.tls]
+      options = "invalidTLSOptions"
+
+  [http.routers.router2]
+    entryPoints = ["websecure"]
+    service = "service1"
+    rule = "Host(`snitest.org`)"
+    [http.routers.router2.tls]
+
+  # fallback router
+  [http.routers.router3]
+    entryPoints = ["websecure"]
+    service = "service1"
+    rule = "Path(`/`)"
+    [http.routers.router3.tls]
+
+[[http.services.service1.loadBalancer.servers]]
+  url = "http://127.0.0.1:9010"
+
+[[tls.certificates]]
+  certFile = "fixtures/https/snitest.com.cert"
+  keyFile = "fixtures/https/snitest.com.key"
+
+[[tls.certificates]]
+  certFile = "fixtures/https/snitest.org.cert"
+  keyFile = "fixtures/https/snitest.org.key"
+
+[tls.options]
+
+  [tls.options.default.clientAuth]
+    # Missing caFile to have an invalid mTLS configuration.
+    clientAuthType = "RequireAndVerifyClientCert"
+
+  [tls.options.invalidTLSOptions.clientAuth]
+    # Missing caFile to have an invalid mTLS configuration.
+    clientAuthType = "RequireAndVerifyClientCert"

programming_language: CoffeeScript
filename: multi-tls-options.toml
change_type: ModificationType.MODIFY
diff: @@ -33,6 +33,13 @@
       [tcp.routers.to-whoami-sni-strict.tls]
         options = "bar"
 
+    [tcp.routers.to-whoami-invalid-tls]
+      rule = "HostSNI(`whoami-i.test`)"
+      service = "whoami-no-cert"
+      entryPoints = [ "tcp" ]
+      [tcp.routers.to-whoami-invalid-tls.tls]
+        options = "invalid"
+
     [tcp.services.whoami-no-cert]
       [tcp.services.whoami-no-cert.loadBalancer]
         [[tcp.services.whoami-no-cert.loadBalancer.servers]]
@@ -45,3 +52,7 @@
 
   [tls.options.bar]
     minVersion = "VersionTLS13"
+
+  [tls.options.invalid.clientAuth]
+    # Missing CA files to have an invalid mTLS configuration.
+    clientAuthType = "RequireAndVerifyClientCert"

programming_language: CoffeeScript
filename: https_test.go
change_type: ModificationType.MODIFY
diff: @@ -1226,3 +1226,53 @@ func (s *HTTPSSuite) TestWithDomainFronting(c *check.C) {
 		c.Assert(err, checker.IsNil)
 	}
 }
+
+// TestWithInvalidTLSOption verifies the behavior when using an invalid tlsOption configuration.
+func (s *HTTPSSuite) TestWithInvalidTLSOption(c *check.C) {
+	backend := startTestServer("9010", http.StatusOK, "server1")
+	defer backend.Close()
+
+	file := s.adaptFile(c, "fixtures/https/https_invalid_tls_options.toml", struct{}{})
+	defer os.Remove(file)
+	cmd, display := s.traefikCmd(withConfigFile(file))
+	defer display(c)
+	err := cmd.Start()
+	c.Assert(err, checker.IsNil)
+	defer s.killCmd(cmd)
+
+	// wait for Traefik
+	err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)"))
+	c.Assert(err, checker.IsNil)
+
+	testCases := []struct {
+		desc       string
+		serverName string
+	}{
+		{
+			desc:       "With invalid TLS Options specified",
+			serverName: "snitest.com",
+		},
+		{
+			desc:       "With invalid Default TLS Options",
+			serverName: "snitest.org",
+		},
+		{
+			desc: "With TLS Options without servername (fallback to default)",
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+
+		tlsConfig := &tls.Config{
+			InsecureSkipVerify: true,
+		}
+		if test.serverName != "" {
+			tlsConfig.ServerName = test.serverName
+		}
+
+		conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
+		c.Assert(err, checker.NotNil, check.Commentf("connected to server successfully"))
+		c.Assert(conn, checker.IsNil)
+	}
+}

programming_language: Go
filename: tcp_test.go
change_type: ModificationType.MODIFY
diff: @@ -116,6 +116,14 @@ func (s *TCPSuite) TestTLSOptions(c *check.C) {
 	_, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12)
 	c.Assert(err, checker.NotNil)
 	c.Assert(err.Error(), checker.Contains, "protocol version not supported")
+
+	// Check that we can't reach a route with an invalid mTLS configuration.
+	conn, err := tls.Dial("tcp", "127.0.0.1:8093", &tls.Config{
+		ServerName:         "whoami-i.test",
+		InsecureSkipVerify: true,
+	})
+	c.Assert(conn, checker.IsNil)
+	c.Assert(err, checker.NotNil)
 }
 
 func (s *TCPSuite) TestNonTLSFallback(c *check.C) {

programming_language: Go
filename: router.go
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@ package router
 import (
 	"context"
 	"errors"
+	"fmt"
 	"net/http"
 
 	"github.com/containous/alice"
@@ -16,6 +17,7 @@ import (
 	httpmuxer "github.com/traefik/traefik/v2/pkg/muxer/http"
 	"github.com/traefik/traefik/v2/pkg/server/middleware"
 	"github.com/traefik/traefik/v2/pkg/server/provider"
+	"github.com/traefik/traefik/v2/pkg/tls"
 )
 
 type middlewareBuilder interface {
@@ -35,10 +37,11 @@ type Manager struct {
 	middlewaresBuilder middlewareBuilder
 	chainBuilder       *middleware.ChainBuilder
 	conf               *runtime.Configuration
+	tlsManager         *tls.Manager
 }
 
-// NewManager Creates a new Manager.
-func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry) *Manager {
+// NewManager creates a new Manager.
+func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry, tlsManager *tls.Manager) *Manager {
 	return &Manager{
 		routerHandlers:     make(map[string]http.Handler),
 		serviceManager:     serviceManager,
@@ -46,6 +49,7 @@ func NewManager(conf *runtime.Configuration, serviceManager serviceManager, midd
 		middlewaresBuilder: middlewaresBuilder,
 		chainBuilder:       chainBuilder,
 		conf:               conf,
+		tlsManager:         tlsManager,
 	}
 }
 
@@ -141,6 +145,17 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou
 		return handler, nil
 	}
 
+	if routerConfig.TLS != nil {
+		// Don't build the router if the TLSOptions configuration is invalid.
+		tlsOptionsName := tls.DefaultTLSConfigName
+		if len(routerConfig.TLS.Options) > 0 && routerConfig.TLS.Options != tls.DefaultTLSConfigName {
+			tlsOptionsName = provider.GetQualifiedName(ctx, routerConfig.TLS.Options)
+		}
+		if _, err := m.tlsManager.Get(tls.DefaultTLSStoreName, tlsOptionsName); err != nil {
+			return nil, fmt.Errorf("building router handler: %w", err)
+		}
+	}
+
 	handler, err := m.buildHTTPHandler(ctx, routerConfig, routerName)
 	if err != nil {
 		return nil, err

programming_language: Go
filename: router_test.go
change_type: ModificationType.MODIFY
diff: @@ -20,6 +20,7 @@ import (
 	"github.com/traefik/traefik/v2/pkg/server/middleware"
 	"github.com/traefik/traefik/v2/pkg/server/service"
 	"github.com/traefik/traefik/v2/pkg/testhelpers"
+	"github.com/traefik/traefik/v2/pkg/tls"
 	"github.com/traefik/traefik/v2/pkg/types"
 )
 
@@ -317,8 +318,9 @@ func TestRouterManager_Get(t *testing.T) {
 			serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 			middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
 			chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
+			tlsManager := tls.NewManager()
 
-			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
+			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
 
 			handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
 
@@ -423,8 +425,9 @@ func TestAccessLog(t *testing.T) {
 			serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 			middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
 			chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
+			tlsManager := tls.NewManager()
 
-			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
+			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
 
 			handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
 
@@ -462,6 +465,7 @@ func TestRuntimeConfiguration(t *testing.T) {
 		serviceConfig    map[string]*dynamic.Service
 		routerConfig     map[string]*dynamic.Router
 		middlewareConfig map[string]*dynamic.Middleware
+		tlsOptions       map[string]tls.Options
 		expectedError    int
 	}{
 		{
@@ -665,7 +669,6 @@ func TestRuntimeConfiguration(t *testing.T) {
 			},
 			expectedError: 1,
 		},
-
 		{
 			desc: "Router with broken middleware",
 			serviceConfig: map[string]*dynamic.Service{
@@ -696,8 +699,71 @@ func TestRuntimeConfiguration(t *testing.T) {
 			},
 			expectedError: 2,
 		},
+		{
+			desc: "Router with broken tlsOption",
+			serviceConfig: map[string]*dynamic.Service{
+				"foo-service": {
+					LoadBalancer: &dynamic.ServersLoadBalancer{
+						Servers: []dynamic.Server{
+							{
+								URL: "http://127.0.0.1",
+							},
+						},
+					},
+				},
+			},
+			middlewareConfig: map[string]*dynamic.Middleware{},
+			routerConfig: map[string]*dynamic.Router{
+				"bar": {
+					EntryPoints: []string{"web"},
+					Service:     "foo-service",
+					Rule:        "Host(`foo.bar`)",
+					TLS: &dynamic.RouterTLSConfig{
+						Options: "broken-tlsOption",
+					},
+				},
+			},
+			tlsOptions: map[string]tls.Options{
+				"broken-tlsOption": {
+					ClientAuth: tls.ClientAuth{
+						ClientAuthType: "foobar",
+					},
+				},
+			},
+			expectedError: 1,
+		},
+		{
+			desc: "Router with broken default tlsOption",
+			serviceConfig: map[string]*dynamic.Service{
+				"foo-service": {
+					LoadBalancer: &dynamic.ServersLoadBalancer{
+						Servers: []dynamic.Server{
+							{
+								URL: "http://127.0.0.1",
+							},
+						},
+					},
+				},
+			},
+			middlewareConfig: map[string]*dynamic.Middleware{},
+			routerConfig: map[string]*dynamic.Router{
+				"bar": {
+					EntryPoints: []string{"web"},
+					Service:     "foo-service",
+					Rule:        "Host(`foo.bar`)",
+					TLS:         &dynamic.RouterTLSConfig{},
+				},
+			},
+			tlsOptions: map[string]tls.Options{
+				"default": {
+					ClientAuth: tls.ClientAuth{
+						ClientAuthType: "foobar",
+					},
+				},
+			},
+			expectedError: 1,
+		},
 	}
-
 	for _, test := range testCases {
 		test := test
 		t.Run(test.desc, func(t *testing.T) {
@@ -711,6 +777,9 @@ func TestRuntimeConfiguration(t *testing.T) {
 					Routers:     test.routerConfig,
 					Middlewares: test.middlewareConfig,
 				},
+				TLS: &dynamic.TLSConfiguration{
+					Options: test.tlsOptions,
+				},
 			})
 
 			roundTripperManager := service.NewRoundTripperManager()
@@ -718,10 +787,13 @@ func TestRuntimeConfiguration(t *testing.T) {
 			serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 			middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
 			chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
+			tlsManager := tls.NewManager()
+			tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil)
 
-			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
+			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
 
 			_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
+			_ = routerManager.BuildHandlers(context.Background(), entryPoints, true)
 
 			// even though rtConf was passed by argument to the manager builders above,
 			// it's ok to use it as the result we check, because everything worth checking
@@ -793,8 +865,9 @@ func TestProviderOnMiddlewares(t *testing.T) {
 	serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 	middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
 	chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
+	tlsManager := tls.NewManager()
 
-	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
+	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
 
 	_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
 
@@ -861,8 +934,9 @@ func BenchmarkRouterServe(b *testing.B) {
 	serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res})
 	middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
 	chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
+	tlsManager := tls.NewManager()
 
-	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
+	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
 
 	handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)
 

programming_language: Go
filename: manager.go
change_type: ModificationType.MODIFY
diff: @@ -103,18 +103,21 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 
 	router.SetHTTPHandler(handlerHTTP)
 
+	// Even though the error is seemingly ignored (aside from logging it),
+	// we actually rely later on the fact that a tls config is nil (which happens when an error is returned) to take special steps
+	// when assigning a handler to a route.
 	defaultTLSConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, traefiktls.DefaultTLSConfigName)
 	if err != nil {
 		log.FromContext(ctx).Errorf("Error during the build of the default TLS configuration: %v", err)
 	}
 
-	// Keyed by domain. The source of truth for doing SNI checking, and for what TLS
-	// options will actually be used for the connection.
+	// Keyed by domain. The source of truth for doing SNI checking (domain fronting).
 	// As soon as there's (at least) two different tlsOptions found for the same domain,
 	// we set the value to the default TLS conf.
 	tlsOptionsForHost := map[string]string{}
 
 	// Keyed by domain, then by options reference.
+	// The actual source of truth for what TLS options will actually be used for the connection.
 	// As opposed to tlsOptionsForHost, it keeps track of all the (different) TLS
 	// options that occur for a given host name, so that later on we can set relevant
 	// errors and logging for all the routers concerned (i.e. wrongly configured).
@@ -142,21 +145,20 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 		}
 
 		if len(domains) == 0 {
-			// Extra Host(*) rule, for HTTPS routers with no Host rule, and for requests for
-			// which the SNI does not match _any_ of the other existing routers Host. This is
-			// only about choosing the TLS configuration. The actual routing will be done
-			// further on by the HTTPS handler. See examples below.
+			// Extra Host(*) rule, for HTTPS routers with no Host rule,
+			// and for requests for which the SNI does not match _any_ of the other existing routers Host.
+			// This is only about choosing the TLS configuration.
+			// The actual routing will be done further on by the HTTPS handler.
+			// See examples below.
 			router.AddHTTPTLSConfig("*", defaultTLSConf)
 
-			// The server name (from a Host(SNI) rule) is the only parameter (available in
-			// HTTP routing rules) on which we can map a TLS config, because it is the only one
-			// accessible before decryption (we obtain it during the ClientHello). Therefore,
-			// when a router has no Host rule, it does not make any sense to specify some TLS
-			// options. Consequently, when it comes to deciding what TLS config will be used,
-			// for a request that will match an HTTPS router with no Host rule, the result will
-			// depend on the _others_ existing routers (their Host rule, to be precise), and
-			// the TLS options associated with them, even though they don't match the incoming
-			// request. Consider the following examples:
+			// The server name (from a Host(SNI) rule) is the only parameter (available in HTTP routing rules) on which we can map a TLS config,
+			// because it is the only one accessible before decryption (we obtain it during the ClientHello).
+			// Therefore, when a router has no Host rule, it does not make any sense to specify some TLS options.
+			// Consequently, when it comes to deciding what TLS config will be used,
+			// for a request that will match an HTTPS router with no Host rule,
+			// the result will depend on the _others_ existing routers (their Host rule, to be precise), and the TLS options associated with them,
+			// even though they don't match the incoming request. Consider the following examples:
 
 			//	# conf1
 			//	httpRouter1:
@@ -170,17 +172,19 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 			//	httpRouter2:
 			//		rule: Host("foo.com") && PathPrefix("/bar")
 			//		tlsoptions: myTLSOptions
-			//	# When a request for "/foo" comes, even though it won't be routed by
-			//	httpRouter2, if its SNI is set to foo.com, myTLSOptions will be used for the TLS
-			//	connection. Otherwise, it will fallback to the default TLS config.
+			//	# When a request for "/foo" comes, even though it won't be routed by httpRouter2,
+			//	# if its SNI is set to foo.com, myTLSOptions will be used for the TLS connection.
+			//	# Otherwise, it will fallback to the default TLS config.
 			logger.Warnf("No domain found in rule %v, the TLS options applied for this router will depend on the SNI of each request", routerHTTPConfig.Rule)
 		}
 
-		tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
-		if err != nil {
-			routerHTTPConfig.AddError(err, true)
-			logger.Error(err)
-			continue
+		// Even though the error is seemingly ignored (aside from logging it),
+		// we actually rely later on the fact that a tls config is nil (which happens when an error is returned) to take special steps
+		// when assigning a handler to a route.
+		tlsConf, tlsConfErr := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
+		if tlsConfErr != nil {
+			// Note: we do not call AddError here because we already did so when buildRouterHandler errored for the same reason.
+			logger.Error(tlsConfErr)
 		}
 
 		for _, domain := range domains {
@@ -204,6 +208,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 
 	sniCheck := snicheck.New(tlsOptionsForHost, handlerHTTPS)
 
+	// Keep in mind that defaultTLSConf might be nil here.
 	router.SetHTTPSHandler(sniCheck, defaultTLSConf)
 
 	logger := log.FromContext(ctx)
@@ -217,22 +222,42 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 				break
 			}
 
-			logger.Debugf("Adding route for %s with TLS options %s", hostSNI, optionsName)
+			if config == nil {
+				// we use nil config as a signal to insert a handler
+				// that enforces that TLS connection attempts to the corresponding (broken) router should fail.
+				logger.Debugf("Adding special closing route for %s because broken TLS options %s", hostSNI, optionsName)
+				router.AddHTTPTLSConfig(hostSNI, nil)
+				continue
+			}
 
+			logger.Debugf("Adding route for %s with TLS options %s", hostSNI, optionsName)
 			router.AddHTTPTLSConfig(hostSNI, config)
-		} else {
-			routers := make([]string, 0, len(tlsConfigs))
-			for _, v := range tlsConfigs {
-				configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false)
-				routers = append(routers, v.routerName)
-			}
+			continue
+		}
+
+		// multiple tlsConfigs
 
-			logger.Warnf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers)
+		routers := make([]string, 0, len(tlsConfigs))
+		for _, v := range tlsConfigs {
+			configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false)
+			routers = append(routers, v.routerName)
+		}
 
-			router.AddHTTPTLSConfig(hostSNI, defaultTLSConf)
+		logger.Warnf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers)
+		if defaultTLSConf == nil {
+			logger.Debugf("Adding special closing route for %s because broken default TLS options", hostSNI)
 		}
+
+		router.AddHTTPTLSConfig(hostSNI, defaultTLSConf)
 	}
 
+	m.addTCPHandlers(ctx, configs, router)
+
+	return router, nil
+}
+
+// addTCPHandlers creates the TCP handlers defined in configs, and adds them to router.
+func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtime.TCPRouterInfo, router *Router) {
 	for routerName, routerConfig := range configs {
 		ctxRouter := log.With(provider.AddInContext(ctx, routerName), log.Str(log.RouterName, routerName))
 		logger := log.FromContext(ctxRouter)
@@ -251,13 +276,6 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 			continue
 		}
 
-		handler, err := m.buildTCPHandler(ctxRouter, routerConfig)
-		if err != nil {
-			routerConfig.AddError(err, true)
-			logger.Error(err)
-			continue
-		}
-
 		domains, err := tcpmuxer.ParseHostSNI(routerConfig.Rule)
 		if err != nil {
 			routerErr := fmt.Errorf("invalid rule: %q , %w", routerConfig.Rule, err)
@@ -274,6 +292,16 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 			logger.Error(routerErr)
 		}
 
+		var handler tcp.Handler
+		if routerConfig.TLS == nil || routerConfig.TLS.Passthrough {
+			handler, err = m.buildTCPHandler(ctxRouter, routerConfig)
+			if err != nil {
+				routerConfig.AddError(err, true)
+				logger.Error(err)
+				continue
+			}
+		}
+
 		if routerConfig.TLS == nil {
 			logger.Debugf("Adding route for %q", routerConfig.Rule)
 			if err := router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
@@ -285,7 +313,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 
 		if routerConfig.TLS.Passthrough {
 			logger.Debugf("Adding Passthrough route for %q", routerConfig.Rule)
-			if err := router.AddRouteTLS(routerConfig.Rule, routerConfig.Priority, handler, nil); err != nil {
+			if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
 				routerConfig.AddError(err, true)
 				logger.Error(err)
 			}
@@ -315,7 +343,15 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 		tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
 		if err != nil {
 			routerConfig.AddError(err, true)
+
 			logger.Error(err)
+			logger.Debugf("Adding special TLS closing route for %q because broken TLS options %s", routerConfig.Rule, tlsOptionsName)
+
+			err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, &brokenTLSRouter{})
+			if err != nil {
+				routerConfig.AddError(err, true)
+				logger.Error(err)
+			}
 			continue
 		}
 
@@ -327,20 +363,30 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 		//		rule: HostSNI(foo.com) && ClientIP(IP2)
 		//		tlsOption: tlsTwo
 		// i.e. same HostSNI but different tlsOptions
-		// This is only applicable if the muxer can decide about the routing _before_
-		// telling the client about the tlsConf (i.e. before the TLS HandShake). This seems
-		// to be the case so far with the existing matchers (HostSNI, and ClientIP), so
-		// it's all good. Otherwise, we would have to do as for HTTPS, i.e. disallow
-		// different TLS configs for the same HostSNIs.
+		// This is only applicable if the muxer can decide about the routing _before_ telling the client about the tlsConf (i.e. before the TLS HandShake).
+		// This seems to be the case so far with the existing matchers (HostSNI, and ClientIP), so it's all good.
+		// Otherwise, we would have to do as for HTTPS, i.e. disallow different TLS configs for the same HostSNIs.
+
+		handler, err = m.buildTCPHandler(ctxRouter, routerConfig)
+		if err != nil {
+			routerConfig.AddError(err, true)
+			logger.Error(err)
+			continue
+		}
+
+		handler = &tcp.TLSHandler{
+			Next:   handler,
+			Config: tlsConf,
+		}
 
 		logger.Debugf("Adding TLS route for %q", routerConfig.Rule)
-		if err := router.AddRouteTLS(routerConfig.Rule, routerConfig.Priority, handler, tlsConf); err != nil {
+
+		err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler)
+		if err != nil {
 			routerConfig.AddError(err, true)
 			logger.Error(err)
 		}
 	}
-
-	return router, nil
 }
 
 func (m *Manager) buildTCPHandler(ctx context.Context, router *runtime.TCPRouterInfo) (tcp.Handler, error) {

programming_language: Go
filename: router.go
change_type: ModificationType.MODIFY
diff: @@ -27,19 +27,20 @@ type Router struct {
 	muxerHTTPS tcpmuxer.Muxer
 
 	// Forwarder handlers.
-	// Handles all HTTP requests.
+	// httpForwarder handles all HTTP requests.
 	httpForwarder tcp.Handler
-	// Handles (indirectly through muxerHTTPS, or directly) all HTTPS requests.
+	// httpsForwarder handles (indirectly through muxerHTTPS, or directly) all HTTPS requests.
 	httpsForwarder tcp.Handler
 
-	// Neither is used directly, but they are held here, and recreated on config
-	// reload, so that they can be passed to the Switcher at the end of the config
-	// reload phase.
+	// Neither is used directly, but they are held here, and recreated on config reload,
+	// so that they can be passed to the Switcher at the end of the config reload phase.
 	httpHandler  http.Handler
 	httpsHandler http.Handler
 
 	// TLS configs.
-	httpsTLSConfig    *tls.Config            // default TLS config
+	httpsTLSConfig *tls.Config // default TLS config
+	// hostHTTPTLSConfig contains TLS configs keyed by SNI.
+	// A nil config is the hint to set up a brokenTLSRouter.
 	hostHTTPTLSConfig map[string]*tls.Config // TLS configs keyed by SNI
 }
 
@@ -80,11 +81,11 @@ func (r *Router) GetTLSGetClientInfo() func(info *tls.ClientHelloInfo) (*tls.Con
 
 // ServeTCP forwards the connection to the right TCP/HTTP handler.
 func (r *Router) ServeTCP(conn tcp.WriteCloser) {
-	// Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS
-	// routers on the entryPoint, and if there is at least one non-TLS TCP router.
-	// In the case of a non-TLS TCP client (that does not "send" first), we would
-	// block forever on clientHelloInfo, which is why we want to detect and
-	// handle that case first and foremost.
+	// Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS routers on the entryPoint,
+	// and if there is at least one non-TLS TCP router.
+	// In the case of a non-TLS TCP client (that does not "send" first),
+	// we would block forever on clientHelloInfo,
+	// which is why we want to detect and handle that case first and foremost.
 	if r.muxerTCP.HasRoutes() && !r.muxerTCPTLS.HasRoutes() && !r.muxerHTTPS.HasRoutes() {
 		connData, err := tcpmuxer.NewConnData("", conn, nil)
 		if err != nil {
@@ -152,9 +153,9 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
 	// (wrapped inside the returned handler) requested for the given HostSNI.
 	handlerHTTPS, catchAllHTTPS := r.muxerHTTPS.Match(connData)
 	if handlerHTTPS != nil && !catchAllHTTPS {
-		// In order not to depart from the behavior in 2.6, we only allow an HTTPS router
-		// to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router (so
-		// basically any router that has a specific HostSNI based rule).
+		// In order not to depart from the behavior in 2.6,
+		// we only allow an HTTPS router to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router
+		// (so basically any router that has a specific HostSNI based rule).
 		handlerHTTPS.ServeTCP(r.GetConn(conn, hello.peeked))
 		return
 	}
@@ -180,7 +181,7 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
 		return
 	}
 
-	// needed to handle 404s for HTTPS, as well as all non-Host (e.g. PathPrefix) matches.
+	// To handle 404s for HTTPS.
 	if r.httpsForwarder != nil {
 		r.httpsForwarder.ServeTCP(r.GetConn(conn, hello.peeked))
 		return
@@ -194,19 +195,6 @@ func (r *Router) AddRoute(rule string, priority int, target tcp.Handler) error {
 	return r.muxerTCP.AddRoute(rule, priority, target)
 }
 
-// AddRouteTLS defines a handler for a given rule and sets the matching tlsConfig.
-func (r *Router) AddRouteTLS(rule string, priority int, target tcp.Handler, config *tls.Config) error {
-	// TLS PassThrough
-	if config == nil {
-		return r.muxerTCPTLS.AddRoute(rule, priority, target)
-	}
-
-	return r.muxerTCPTLS.AddRoute(rule, priority, &tcp.TLSHandler{
-		Next:   target,
-		Config: config,
-	})
-}
-
 // AddHTTPTLSConfig defines a handler for a given sniHost and sets the matching tlsConfig.
 func (r *Router) AddHTTPTLSConfig(sniHost string, config *tls.Config) {
 	if r.hostHTTPTLSConfig == nil {
@@ -242,20 +230,44 @@ func (r *Router) SetHTTPForwarder(handler tcp.Handler) {
 	r.httpForwarder = handler
 }
 
-// SetHTTPSForwarder sets the tcp handler that will forward the TLS connections to an http handler.
+// brokenTLSRouter is associated to a Host(SNI) rule for which we know the TLS conf is broken.
+// It is used to make sure any attempt to connect to that hostname is closed,
+// since we cannot proceed with the intended TLS conf.
+type brokenTLSRouter struct{}
+
+// ServeTCP instantly closes the connection.
+func (t *brokenTLSRouter) ServeTCP(conn tcp.WriteCloser) {
+	_ = conn.Close()
+}
+
+// SetHTTPSForwarder sets the tcp handler that will forward the TLS connections to an HTTP handler.
+// It also sets up each TLS handler (with its TLS config) for each Host(SNI) rule we previously kept track of.
+// It sets up a special handler that closes the connection if a TLS config is nil.
 func (r *Router) SetHTTPSForwarder(handler tcp.Handler) {
 	for sniHost, tlsConf := range r.hostHTTPTLSConfig {
+		var tcpHandler tcp.Handler
+		if tlsConf == nil {
+			tcpHandler = &brokenTLSRouter{}
+		} else {
+			tcpHandler = &tcp.TLSHandler{
+				Next:   handler,
+				Config: tlsConf,
+			}
+		}
+
 		// muxerHTTPS only contains single HostSNI rules (and no other kind of rules),
 		// so there's no need for specifying a priority for them.
-		err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, &tcp.TLSHandler{
-			Next:   handler,
-			Config: tlsConf,
-		})
+		err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, tcpHandler)
 		if err != nil {
 			log.WithoutContext().Errorf("Error while adding route for host: %v", err)
 		}
 	}
 
+	if r.httpsTLSConfig == nil {
+		r.httpsForwarder = &brokenTLSRouter{}
+		return
+	}
+
 	r.httpsForwarder = &tcp.TLSHandler{
 		Next:   handler,
 		Config: r.httpsTLSConfig,
@@ -275,15 +287,14 @@ func (r *Router) SetHTTPSHandler(handler http.Handler, config *tls.Config) {
 
 // Conn is a connection proxy that handles Peeked bytes.
 type Conn struct {
-	// Peeked are the bytes that have been read from Conn for the
-	// purposes of route matching, but have not yet been consumed
-	// by Read calls. It set to nil by Read when fully consumed.
+	// Peeked are the bytes that have been read from Conn for the purposes of route matching,
+	// but have not yet been consumed by Read calls.
+	// It set to nil by Read when fully consumed.
 	Peeked []byte
 
 	// Conn is the underlying connection.
-	// It can be type asserted against *net.TCPConn or other types
-	// as needed. It should not be read from directly unless
-	// Peeked is nil.
+	// It can be type asserted against *net.TCPConn or other types as needed.
+	// It should not be read from directly unless Peeked is nil.
 	tcp.WriteCloser
 }
 
@@ -320,15 +331,14 @@ func clientHelloInfo(br *bufio.Reader) (*clientHello, error) {
 		return nil, err
 	}
 
-	// No valid TLS record has a type of 0x80, however SSLv2 handshakes
-	// start with a uint16 length where the MSB is set and the first record
-	// is always < 256 bytes long. Therefore typ == 0x80 strongly suggests
-	// an SSLv2 client.
+	// No valid TLS record has a type of 0x80, however SSLv2 handshakes start with an uint16 length
+	// where the MSB is set and the first record is always < 256 bytes long.
+	// Therefore, typ == 0x80 strongly suggests an SSLv2 client.
 	const recordTypeSSLv2 = 0x80
 	const recordTypeHandshake = 0x16
 	if hdr[0] != recordTypeHandshake {
 		if hdr[0] == recordTypeSSLv2 {
-			// we consider SSLv2 as TLS and it will be refused by real TLS handshake.
+			// we consider SSLv2 as TLS, and it will be refused by real TLS handshake.
 			return &clientHello{
 				isTLS:  true,
 				peeked: getPeeked(br),

programming_language: Go
filename: routerfactory.go
change_type: ModificationType.MODIFY
diff: @@ -72,7 +72,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
 
 	middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
 
-	routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry)
+	routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry, f.tlsManager)
 
 	handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
 	handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)

programming_language: Go
filename: tlsmanager.go
change_type: ModificationType.MODIFY
diff: @@ -157,19 +157,16 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
 	m.lock.RLock()
 	defer m.lock.RUnlock()
 
-	var tlsConfig *tls.Config
-	var err error
-
 	sniStrict := false
 	config, ok := m.configs[configName]
-	if ok {
-		sniStrict = config.SniStrict
-		tlsConfig, err = buildTLSConfig(config)
-	} else {
-		err = fmt.Errorf("unknown TLS options: %s", configName)
+	if !ok {
+		return nil, fmt.Errorf("unknown TLS options: %s", configName)
 	}
+
+	sniStrict = config.SniStrict
+	tlsConfig, err := buildTLSConfig(config)
 	if err != nil {
-		tlsConfig = &tls.Config{}
+		return nil, fmt.Errorf("building TLS config: %w", err)
 	}
 
 	store := m.getStore(storeName)
@@ -177,7 +174,7 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
 		err = fmt.Errorf("TLS store %s not found", storeName)
 	}
 	acmeTLSStore := m.getStore(tlsalpn01.ACMETLS1Protocol)
-	if acmeTLSStore == nil {
+	if acmeTLSStore == nil && err == nil {
 		err = fmt.Errorf("ACME TLS store %s not found", tlsalpn01.ACMETLS1Protocol)
 	}
 
@@ -188,15 +185,12 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
 			certificate := acmeTLSStore.GetBestCertificate(clientHello)
 			if certificate == nil {
 				log.WithoutContext().Debugf("TLS: no certificate for TLSALPN challenge: %s", domainToCheck)
-				// We want the user to eventually get the (alertUnrecognizedName) "unrecognized
-				// name" error.
-				// Unfortunately, if we returned an error here, since we can't use
-				// the unexported error (errNoCertificates) that our caller (config.getCertificate
-				// in crypto/tls) uses as a sentinel, it would report an (alertInternalError)
-				// "internal error" instead of an alertUnrecognizedName.
-				// Which is why we return no error, and we let the caller detect that there's
-				// actually no certificate, and fall back into the flow that will report
-				// the desired error.
+				// We want the user to eventually get the (alertUnrecognizedName) "unrecognized name" error.
+				// Unfortunately, if we returned an error here,
+				// since we can't use the unexported error (errNoCertificates) that our caller (config.getCertificate in crypto/tls) uses as a sentinel,
+				// it would report an (alertInternalError) "internal error" instead of an alertUnrecognizedName.
+				// Which is why we return no error, and we let the caller detect that there's actually no certificate,
+				// and fall back into the flow that will report the desired error.
 				// https://cs.opensource.google/go/go/+/dev.boringcrypto.go1.17:src/crypto/tls/common.go;l=1058
 				return nil, nil
 			}

programming_language: Go
filename: tlsmanager_test.go
change_type: ModificationType.MODIFY
diff: @@ -119,8 +119,9 @@ func TestManager_Get(t *testing.T) {
 	}}
 
 	tlsConfigs := map[string]Options{
-		"foo": {MinVersion: "VersionTLS12"},
-		"bar": {MinVersion: "VersionTLS11"},
+		"foo":     {MinVersion: "VersionTLS12"},
+		"bar":     {MinVersion: "VersionTLS11"},
+		"invalid": {CurvePreferences: []string{"42"}},
 	}
 
 	testCases := []struct {
@@ -140,15 +141,20 @@ func TestManager_Get(t *testing.T) {
 			expectedMinVersion: uint16(tls.VersionTLS11),
 		},
 		{
-			desc:           "Get an tls config from an invalid name",
+			desc:           "Get a tls config from an invalid name",
 			tlsOptionsName: "unknown",
 			expectedError:  true,
 		},
 		{
-			desc:           "Get an tls config from unexisting 'default' name",
+			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()
@@ -161,42 +167,13 @@ func TestManager_Get(t *testing.T) {
 
 			config, err := tlsManager.Get("default", test.tlsOptionsName)
 			if test.expectedError {
-				assert.Error(t, err)
+				require.Nil(t, config)
+				require.Error(t, err)
 				return
 			}
 
-			assert.NoError(t, err)
-			assert.Equal(t, config.MinVersion, test.expectedMinVersion)
-		})
-	}
-}
-
-func TestManager_Get_GetCertificate(t *testing.T) {
-	testCases := []struct {
-		desc                 string
-		expectedGetConfigErr require.ErrorAssertionFunc
-		expectedCertificate  assert.ValueAssertionFunc
-	}{
-		{
-			desc:                 "Get a default certificate from non-existing store",
-			expectedGetConfigErr: require.Error,
-			expectedCertificate:  assert.Nil,
-		},
-	}
-
-	tlsManager := NewManager()
-
-	for _, test := range testCases {
-		test := test
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-
-			config, err := tlsManager.Get("default", "foo")
-			test.expectedGetConfigErr(t, err)
-
-			certificate, err := config.GetCertificate(&tls.ClientHelloInfo{})
 			require.NoError(t, err)
-			test.expectedCertificate(t, certificate)
+			assert.Equal(t, config.MinVersion, test.expectedMinVersion)
 		})
 	}
 }

programming_language: Go