CVE-2022-46153 (Go, published 2022-12-08T22:15Z)
Description: value: 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.
Severity: nan (CVSS v3 base score: 6.5)
Weakness: CWE-295 \u2014 Improper Certificate Validation
The product does not validate, or incorrectly validates, a certificate.
Repository: https://github.com/traefik/traefik
Files changed:
integration/fixtures/https/https_invalid_tls_options.toml
integration/fixtures/tcp/multi-tls-options.toml
integration/https_test.go
integration/tcp_test.go
pkg/server/router/router.go
pkg/server/router/router_test.go
pkg/server/router/tcp/manager.go
pkg/server/router/tcp/router.go
pkg/server/routerfactory.go
pkg/tls/tlsmanager.go
pkg/tls/tlsmanager_test.go
Commit message: 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>

Vulnerable code:
// NewManager Creates a new Manager.
func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry) *Manager {
			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())


			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
	// Keyed by domain. The source of truth for doing SNI checking, and for what TLS
	// options will actually be used for the connection.
			// 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.
			// 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:
			//	# 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.
		tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
		if err != nil {
			routerHTTPConfig.AddError(err, true)
			logger.Error(err)
			continue
			logger.Debugf("Adding route for %s with TLS options %s", hostSNI, optionsName)
		} 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)
			}
			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)
			router.AddHTTPTLSConfig(hostSNI, defaultTLSConf)
		handler, err := m.buildTCPHandler(ctxRouter, routerConfig)
		if err != nil {
			routerConfig.AddError(err, true)
			logger.Error(err)
			continue
		}

			if err := router.AddRouteTLS(routerConfig.Rule, routerConfig.Priority, handler, nil); err != nil {
		// 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.
		if err := router.AddRouteTLS(routerConfig.Rule, routerConfig.Priority, handler, tlsConf); err != nil {

	return router, nil
	// Handles all HTTP requests.
	// Handles (indirectly through muxerHTTPS, or directly) all HTTPS requests.
	// 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.
	httpsTLSConfig    *tls.Config            // default TLS config
	// 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.
		// 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).
	// needed to handle 404s for HTTPS, as well as all non-Host (e.g. PathPrefix) matches.
// 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,
	})
}

// SetHTTPSForwarder sets the tcp handler that will forward the TLS connections to an http handler.
		err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, &tcp.TLSHandler{
			Next:   handler,
			Config: tlsConf,
		})
	// 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.
	// It can be type asserted against *net.TCPConn or other types
	// as needed. It should not be read from directly unless
	// Peeked is nil.
	// 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.
			// we consider SSLv2 as TLS and it will be refused by real TLS handshake.
	routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry)
	var tlsConfig *tls.Config
	var err error

	if ok {
		sniStrict = config.SniStrict
		tlsConfig, err = buildTLSConfig(config)
	} else {
		err = fmt.Errorf("unknown TLS options: %s", configName)
		tlsConfig = &tls.Config{}
	if acmeTLSStore == nil {
				// 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.
		"foo": {MinVersion: "VersionTLS12"},
		"bar": {MinVersion: "VersionTLS11"},
			desc:           "Get an tls config from an invalid name",
			desc:           "Get an tls config from unexisting 'default' name",
				assert.Error(t, err)
			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{})
			test.expectedCertificate(t, certificate)

Fixed code:
[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"
    [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"


  [tls.options.invalid.clientAuth]
    # Missing CA files to have an invalid mTLS configuration.
    clientAuthType = "RequireAndVerifyClientCert"

// 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)
	}
}

	// 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)
	"fmt"
	"github.com/traefik/traefik/v2/pkg/tls"
	tlsManager         *tls.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 {
		tlsManager:         tlsManager,
	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)
		}
	}

	"github.com/traefik/traefik/v2/pkg/tls"
			tlsManager := tls.NewManager()
			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
			tlsManager := tls.NewManager()
			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
		tlsOptions       map[string]tls.Options
		{
			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,
		},
				TLS: &dynamic.TLSConfiguration{
					Options: test.tlsOptions,
				},
			tlsManager := tls.NewManager()
			tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil)
			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
			_ = routerManager.BuildHandlers(context.Background(), entryPoints, true)
	tlsManager := tls.NewManager()
	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
	tlsManager := tls.NewManager()
	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
	// 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.
	// Keyed by domain. The source of truth for doing SNI checking (domain fronting).
	// The actual source of truth for what TLS options will actually be used for the connection.
			// 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.
			// 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:
			//	# 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.
		// 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)
	// Keep in mind that defaultTLSConf might be nil here.
			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)
			continue
		}

		// multiple tlsConfigs
		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)
		}
		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) {
		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 err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {

			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)
			}
		// 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,
		}

		err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler)
		if err != nil {
	// httpForwarder handles all HTTP requests.
	// httpsForwarder handles (indirectly through muxerHTTPS, or directly) all HTTPS requests.
	// 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.
	httpsTLSConfig *tls.Config // default TLS config
	// hostHTTPTLSConfig contains TLS configs keyed by SNI.
	// A nil config is the hint to set up a brokenTLSRouter.
	// 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.
		// 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).
	// To handle 404s for HTTPS.
// 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.
		var tcpHandler tcp.Handler
		if tlsConf == nil {
			tcpHandler = &brokenTLSRouter{}
		} else {
			tcpHandler = &tcp.TLSHandler{
				Next:   handler,
				Config: tlsConf,
			}
		}

		err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, tcpHandler)
	if r.httpsTLSConfig == nil {
		r.httpsForwarder = &brokenTLSRouter{}
		return
	}

	// 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.
	// It can be type asserted against *net.TCPConn or other types as needed.
	// It should not be read from directly unless Peeked is nil.
	// 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.
			// we consider SSLv2 as TLS, and it will be refused by real TLS handshake.
	routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry, f.tlsManager)
	if !ok {
		return nil, fmt.Errorf("unknown TLS options: %s", configName)

	sniStrict = config.SniStrict
	tlsConfig, err := buildTLSConfig(config)
		return nil, fmt.Errorf("building TLS config: %w", err)
	if acmeTLSStore == nil && err == nil {
				// 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.
		"foo":     {MinVersion: "VersionTLS12"},
		"bar":     {MinVersion: "VersionTLS11"},
		"invalid": {CurvePreferences: []string{"42"}},
			desc:           "Get a tls config from an invalid name",
			desc:           "Get a tls config from unexisting 'default' name",
		{
			desc:           "Get an invalid tls config",
			tlsOptionsName: "invalid",
			expectedError:  true,
		},
				require.Nil(t, config)
				require.Error(t, err)
			assert.Equal(t, config.MinVersion, test.expectedMinVersion)