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>
This commit is contained in:
parent
778188ed34
commit
7e3fe48b80
11 changed files with 404 additions and 159 deletions
60
integration/fixtures/https/https_invalid_tls_options.toml
Normal file
60
integration/fixtures/https/https_invalid_tls_options.toml
Normal file
|
@ -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"
|
|
@ -33,6 +33,13 @@
|
||||||
[tcp.routers.to-whoami-sni-strict.tls]
|
[tcp.routers.to-whoami-sni-strict.tls]
|
||||||
options = "bar"
|
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]
|
||||||
[tcp.services.whoami-no-cert.loadBalancer]
|
[tcp.services.whoami-no-cert.loadBalancer]
|
||||||
[[tcp.services.whoami-no-cert.loadBalancer.servers]]
|
[[tcp.services.whoami-no-cert.loadBalancer.servers]]
|
||||||
|
@ -45,3 +52,7 @@
|
||||||
|
|
||||||
[tls.options.bar]
|
[tls.options.bar]
|
||||||
minVersion = "VersionTLS13"
|
minVersion = "VersionTLS13"
|
||||||
|
|
||||||
|
[tls.options.invalid.clientAuth]
|
||||||
|
# Missing CA files to have an invalid mTLS configuration.
|
||||||
|
clientAuthType = "RequireAndVerifyClientCert"
|
||||||
|
|
|
@ -1226,3 +1226,53 @@ func (s *HTTPSSuite) TestWithDomainFronting(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -116,6 +116,14 @@ func (s *TCPSuite) TestTLSOptions(c *check.C) {
|
||||||
_, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12)
|
_, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12)
|
||||||
c.Assert(err, checker.NotNil)
|
c.Assert(err, checker.NotNil)
|
||||||
c.Assert(err.Error(), checker.Contains, "protocol version not supported")
|
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) {
|
func (s *TCPSuite) TestNonTLSFallback(c *check.C) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package router
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/containous/alice"
|
"github.com/containous/alice"
|
||||||
|
@ -16,6 +17,7 @@ import (
|
||||||
httpmuxer "github.com/traefik/traefik/v2/pkg/muxer/http"
|
httpmuxer "github.com/traefik/traefik/v2/pkg/muxer/http"
|
||||||
"github.com/traefik/traefik/v2/pkg/server/middleware"
|
"github.com/traefik/traefik/v2/pkg/server/middleware"
|
||||||
"github.com/traefik/traefik/v2/pkg/server/provider"
|
"github.com/traefik/traefik/v2/pkg/server/provider"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
type middlewareBuilder interface {
|
type middlewareBuilder interface {
|
||||||
|
@ -35,10 +37,11 @@ type Manager struct {
|
||||||
middlewaresBuilder middlewareBuilder
|
middlewaresBuilder middlewareBuilder
|
||||||
chainBuilder *middleware.ChainBuilder
|
chainBuilder *middleware.ChainBuilder
|
||||||
conf *runtime.Configuration
|
conf *runtime.Configuration
|
||||||
|
tlsManager *tls.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager Creates a new Manager.
|
// NewManager creates a new Manager.
|
||||||
func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry) *Manager {
|
func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry, tlsManager *tls.Manager) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
routerHandlers: make(map[string]http.Handler),
|
routerHandlers: make(map[string]http.Handler),
|
||||||
serviceManager: serviceManager,
|
serviceManager: serviceManager,
|
||||||
|
@ -46,6 +49,7 @@ func NewManager(conf *runtime.Configuration, serviceManager serviceManager, midd
|
||||||
middlewaresBuilder: middlewaresBuilder,
|
middlewaresBuilder: middlewaresBuilder,
|
||||||
chainBuilder: chainBuilder,
|
chainBuilder: chainBuilder,
|
||||||
conf: conf,
|
conf: conf,
|
||||||
|
tlsManager: tlsManager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,6 +145,17 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou
|
||||||
return handler, nil
|
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)
|
handler, err := m.buildHTTPHandler(ctx, routerConfig, routerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/traefik/traefik/v2/pkg/server/middleware"
|
"github.com/traefik/traefik/v2/pkg/server/middleware"
|
||||||
"github.com/traefik/traefik/v2/pkg/server/service"
|
"github.com/traefik/traefik/v2/pkg/server/service"
|
||||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/tls"
|
||||||
"github.com/traefik/traefik/v2/pkg/types"
|
"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)
|
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||||
chainBuilder := middleware.NewChainBuilder(nil, nil, 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)
|
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)
|
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||||
chainBuilder := middleware.NewChainBuilder(nil, nil, 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)
|
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
|
||||||
|
|
||||||
|
@ -462,6 +465,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
serviceConfig map[string]*dynamic.Service
|
serviceConfig map[string]*dynamic.Service
|
||||||
routerConfig map[string]*dynamic.Router
|
routerConfig map[string]*dynamic.Router
|
||||||
middlewareConfig map[string]*dynamic.Middleware
|
middlewareConfig map[string]*dynamic.Middleware
|
||||||
|
tlsOptions map[string]tls.Options
|
||||||
expectedError int
|
expectedError int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -665,7 +669,6 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectedError: 1,
|
expectedError: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
desc: "Router with broken middleware",
|
desc: "Router with broken middleware",
|
||||||
serviceConfig: map[string]*dynamic.Service{
|
serviceConfig: map[string]*dynamic.Service{
|
||||||
|
@ -696,8 +699,71 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectedError: 2,
|
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 {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
@ -711,6 +777,9 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
Routers: test.routerConfig,
|
Routers: test.routerConfig,
|
||||||
Middlewares: test.middlewareConfig,
|
Middlewares: test.middlewareConfig,
|
||||||
},
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{
|
||||||
|
Options: test.tlsOptions,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
roundTripperManager := service.NewRoundTripperManager()
|
roundTripperManager := service.NewRoundTripperManager()
|
||||||
|
@ -718,10 +787,13 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||||
chainBuilder := middleware.NewChainBuilder(nil, nil, 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, false)
|
||||||
|
_ = routerManager.BuildHandlers(context.Background(), entryPoints, true)
|
||||||
|
|
||||||
// even though rtConf was passed by argument to the manager builders above,
|
// 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
|
// 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)
|
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||||
chainBuilder := middleware.NewChainBuilder(nil, nil, 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)
|
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||||
|
|
||||||
|
@ -861,8 +934,9 @@ func BenchmarkRouterServe(b *testing.B) {
|
||||||
serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res})
|
serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res})
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||||
chainBuilder := middleware.NewChainBuilder(nil, nil, 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)
|
handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||||
|
|
||||||
|
|
|
@ -103,18 +103,21 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||||
|
|
||||||
router.SetHTTPHandler(handlerHTTP)
|
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)
|
defaultTLSConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, traefiktls.DefaultTLSConfigName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromContext(ctx).Errorf("Error during the build of the default TLS configuration: %v", err)
|
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
|
// Keyed by domain. The source of truth for doing SNI checking (domain fronting).
|
||||||
// options will actually be used for the connection.
|
|
||||||
// As soon as there's (at least) two different tlsOptions found for the same domain,
|
// As soon as there's (at least) two different tlsOptions found for the same domain,
|
||||||
// we set the value to the default TLS conf.
|
// we set the value to the default TLS conf.
|
||||||
tlsOptionsForHost := map[string]string{}
|
tlsOptionsForHost := map[string]string{}
|
||||||
|
|
||||||
// Keyed by domain, then by options reference.
|
// 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
|
// 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
|
// 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).
|
// 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 {
|
if len(domains) == 0 {
|
||||||
// Extra Host(*) rule, for HTTPS routers with no Host rule, and for requests for
|
// Extra Host(*) rule, for HTTPS routers with no Host rule,
|
||||||
// which the SNI does not match _any_ of the other existing routers Host. This is
|
// and for requests for which the SNI does not match _any_ of the other existing routers Host.
|
||||||
// only about choosing the TLS configuration. The actual routing will be done
|
// This is only about choosing the TLS configuration.
|
||||||
// further on by the HTTPS handler. See examples below.
|
// The actual routing will be done further on by the HTTPS handler.
|
||||||
|
// See examples below.
|
||||||
router.AddHTTPTLSConfig("*", defaultTLSConf)
|
router.AddHTTPTLSConfig("*", defaultTLSConf)
|
||||||
|
|
||||||
// The server name (from a Host(SNI) rule) is the only parameter (available in
|
// 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,
|
||||||
// HTTP routing rules) on which we can map a TLS config, because it is the only one
|
// because it is the only one accessible before decryption (we obtain it during the ClientHello).
|
||||||
// accessible before decryption (we obtain it during the ClientHello). Therefore,
|
// Therefore, when a router has no Host rule, it does not make any sense to specify some TLS options.
|
||||||
// when a router has no Host rule, it does not make any sense to specify some TLS
|
// Consequently, when it comes to deciding what TLS config will be used,
|
||||||
// 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,
|
||||||
// for a request that will match an HTTPS router with no Host rule, the result will
|
// the result will depend on the _others_ existing routers (their Host rule, to be precise), and the TLS options associated with them,
|
||||||
// depend on the _others_ existing routers (their Host rule, to be precise), and
|
// even though they don't match the incoming request. Consider the following examples:
|
||||||
// the TLS options associated with them, even though they don't match the incoming
|
|
||||||
// request. Consider the following examples:
|
|
||||||
|
|
||||||
// # conf1
|
// # conf1
|
||||||
// httpRouter1:
|
// httpRouter1:
|
||||||
|
@ -170,17 +172,19 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||||
// httpRouter2:
|
// httpRouter2:
|
||||||
// rule: Host("foo.com") && PathPrefix("/bar")
|
// rule: Host("foo.com") && PathPrefix("/bar")
|
||||||
// tlsoptions: myTLSOptions
|
// tlsoptions: myTLSOptions
|
||||||
// # When a request for "/foo" comes, even though it won't be routed by
|
// # When a request for "/foo" comes, even though it won't be routed by httpRouter2,
|
||||||
// httpRouter2, if its SNI is set to foo.com, myTLSOptions will be used for the TLS
|
// # if its SNI is set to foo.com, myTLSOptions will be used for the TLS connection.
|
||||||
// connection. Otherwise, it will fallback to the default TLS config.
|
// # 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)
|
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)
|
// Even though the error is seemingly ignored (aside from logging it),
|
||||||
if err != nil {
|
// we actually rely later on the fact that a tls config is nil (which happens when an error is returned) to take special steps
|
||||||
routerHTTPConfig.AddError(err, true)
|
// when assigning a handler to a route.
|
||||||
logger.Error(err)
|
tlsConf, tlsConfErr := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
|
||||||
continue
|
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 {
|
for _, domain := range domains {
|
||||||
|
@ -204,6 +208,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||||
|
|
||||||
sniCheck := snicheck.New(tlsOptionsForHost, handlerHTTPS)
|
sniCheck := snicheck.New(tlsOptionsForHost, handlerHTTPS)
|
||||||
|
|
||||||
|
// Keep in mind that defaultTLSConf might be nil here.
|
||||||
router.SetHTTPSHandler(sniCheck, defaultTLSConf)
|
router.SetHTTPSHandler(sniCheck, defaultTLSConf)
|
||||||
|
|
||||||
logger := log.FromContext(ctx)
|
logger := log.FromContext(ctx)
|
||||||
|
@ -217,10 +222,21 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||||
break
|
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)
|
router.AddHTTPTLSConfig(hostSNI, config)
|
||||||
} else {
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiple tlsConfigs
|
||||||
|
|
||||||
routers := make([]string, 0, len(tlsConfigs))
|
routers := make([]string, 0, len(tlsConfigs))
|
||||||
for _, v := range 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)
|
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)
|
||||||
|
@ -228,11 +244,20 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
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)
|
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 {
|
for routerName, routerConfig := range configs {
|
||||||
ctxRouter := log.With(provider.AddInContext(ctx, routerName), log.Str(log.RouterName, routerName))
|
ctxRouter := log.With(provider.AddInContext(ctx, routerName), log.Str(log.RouterName, routerName))
|
||||||
logger := log.FromContext(ctxRouter)
|
logger := log.FromContext(ctxRouter)
|
||||||
|
@ -251,13 +276,6 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
handler, err := m.buildTCPHandler(ctxRouter, routerConfig)
|
|
||||||
if err != nil {
|
|
||||||
routerConfig.AddError(err, true)
|
|
||||||
logger.Error(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
domains, err := tcpmuxer.ParseHostSNI(routerConfig.Rule)
|
domains, err := tcpmuxer.ParseHostSNI(routerConfig.Rule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
routerErr := fmt.Errorf("invalid rule: %q , %w", routerConfig.Rule, err)
|
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)
|
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 {
|
if routerConfig.TLS == nil {
|
||||||
logger.Debugf("Adding route for %q", routerConfig.Rule)
|
logger.Debugf("Adding route for %q", routerConfig.Rule)
|
||||||
if err := router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
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 {
|
if routerConfig.TLS.Passthrough {
|
||||||
logger.Debugf("Adding Passthrough route for %q", routerConfig.Rule)
|
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)
|
routerConfig.AddError(err, true)
|
||||||
logger.Error(err)
|
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)
|
tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
routerConfig.AddError(err, true)
|
routerConfig.AddError(err, true)
|
||||||
|
|
||||||
logger.Error(err)
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,20 +363,30 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||||
// rule: HostSNI(foo.com) && ClientIP(IP2)
|
// rule: HostSNI(foo.com) && ClientIP(IP2)
|
||||||
// tlsOption: tlsTwo
|
// tlsOption: tlsTwo
|
||||||
// i.e. same HostSNI but different tlsOptions
|
// i.e. same HostSNI but different tlsOptions
|
||||||
// This is only applicable if the muxer can decide about the routing _before_
|
// 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).
|
||||||
// telling the client about the tlsConf (i.e. before the TLS HandShake). This seems
|
// This seems to be the case so far with the existing matchers (HostSNI, and ClientIP), so it's all good.
|
||||||
// to be the case so far with the existing matchers (HostSNI, and ClientIP), so
|
// Otherwise, we would have to do as for HTTPS, i.e. disallow different TLS configs for the same HostSNIs.
|
||||||
// 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)
|
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)
|
routerConfig.AddError(err, true)
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return router, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) buildTCPHandler(ctx context.Context, router *runtime.TCPRouterInfo) (tcp.Handler, error) {
|
func (m *Manager) buildTCPHandler(ctx context.Context, router *runtime.TCPRouterInfo) (tcp.Handler, error) {
|
||||||
|
|
|
@ -27,19 +27,20 @@ type Router struct {
|
||||||
muxerHTTPS tcpmuxer.Muxer
|
muxerHTTPS tcpmuxer.Muxer
|
||||||
|
|
||||||
// Forwarder handlers.
|
// Forwarder handlers.
|
||||||
// Handles all HTTP requests.
|
// httpForwarder handles all HTTP requests.
|
||||||
httpForwarder tcp.Handler
|
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
|
httpsForwarder tcp.Handler
|
||||||
|
|
||||||
// Neither is used directly, but they are held here, and recreated on config
|
// Neither is used directly, but they are held here, and recreated on config reload,
|
||||||
// reload, so that they can be passed to the Switcher at the end of the config
|
// so that they can be passed to the Switcher at the end of the config reload phase.
|
||||||
// reload phase.
|
|
||||||
httpHandler http.Handler
|
httpHandler http.Handler
|
||||||
httpsHandler http.Handler
|
httpsHandler http.Handler
|
||||||
|
|
||||||
// TLS configs.
|
// 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
|
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.
|
// ServeTCP forwards the connection to the right TCP/HTTP handler.
|
||||||
func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
||||||
// Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS
|
// Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS routers on the entryPoint,
|
||||||
// routers on the entryPoint, and if there is at least one non-TLS TCP router.
|
// 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
|
// In the case of a non-TLS TCP client (that does not "send" first),
|
||||||
// block forever on clientHelloInfo, which is why we want to detect and
|
// we would block forever on clientHelloInfo,
|
||||||
// handle that case first and foremost.
|
// which is why we want to detect and handle that case first and foremost.
|
||||||
if r.muxerTCP.HasRoutes() && !r.muxerTCPTLS.HasRoutes() && !r.muxerHTTPS.HasRoutes() {
|
if r.muxerTCP.HasRoutes() && !r.muxerTCPTLS.HasRoutes() && !r.muxerHTTPS.HasRoutes() {
|
||||||
connData, err := tcpmuxer.NewConnData("", conn, nil)
|
connData, err := tcpmuxer.NewConnData("", conn, nil)
|
||||||
if err != 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.
|
// (wrapped inside the returned handler) requested for the given HostSNI.
|
||||||
handlerHTTPS, catchAllHTTPS := r.muxerHTTPS.Match(connData)
|
handlerHTTPS, catchAllHTTPS := r.muxerHTTPS.Match(connData)
|
||||||
if handlerHTTPS != nil && !catchAllHTTPS {
|
if handlerHTTPS != nil && !catchAllHTTPS {
|
||||||
// In order not to depart from the behavior in 2.6, we only allow an HTTPS router
|
// In order not to depart from the behavior in 2.6,
|
||||||
// to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router (so
|
// we only allow an HTTPS router to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router
|
||||||
// basically any router that has a specific HostSNI based rule).
|
// (so basically any router that has a specific HostSNI based rule).
|
||||||
handlerHTTPS.ServeTCP(r.GetConn(conn, hello.peeked))
|
handlerHTTPS.ServeTCP(r.GetConn(conn, hello.peeked))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -180,7 +181,7 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
||||||
return
|
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 {
|
if r.httpsForwarder != nil {
|
||||||
r.httpsForwarder.ServeTCP(r.GetConn(conn, hello.peeked))
|
r.httpsForwarder.ServeTCP(r.GetConn(conn, hello.peeked))
|
||||||
return
|
return
|
||||||
|
@ -194,19 +195,6 @@ func (r *Router) AddRoute(rule string, priority int, target tcp.Handler) error {
|
||||||
return r.muxerTCP.AddRoute(rule, priority, target)
|
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.
|
// AddHTTPTLSConfig defines a handler for a given sniHost and sets the matching tlsConfig.
|
||||||
func (r *Router) AddHTTPTLSConfig(sniHost string, config *tls.Config) {
|
func (r *Router) AddHTTPTLSConfig(sniHost string, config *tls.Config) {
|
||||||
if r.hostHTTPTLSConfig == nil {
|
if r.hostHTTPTLSConfig == nil {
|
||||||
|
@ -242,20 +230,44 @@ func (r *Router) SetHTTPForwarder(handler tcp.Handler) {
|
||||||
r.httpForwarder = 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) {
|
func (r *Router) SetHTTPSForwarder(handler tcp.Handler) {
|
||||||
for sniHost, tlsConf := range r.hostHTTPTLSConfig {
|
for sniHost, tlsConf := range r.hostHTTPTLSConfig {
|
||||||
// muxerHTTPS only contains single HostSNI rules (and no other kind of rules),
|
var tcpHandler tcp.Handler
|
||||||
// so there's no need for specifying a priority for them.
|
if tlsConf == nil {
|
||||||
err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, &tcp.TLSHandler{
|
tcpHandler = &brokenTLSRouter{}
|
||||||
|
} else {
|
||||||
|
tcpHandler = &tcp.TLSHandler{
|
||||||
Next: handler,
|
Next: handler,
|
||||||
Config: tlsConf,
|
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, tcpHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithoutContext().Errorf("Error while adding route for host: %v", err)
|
log.WithoutContext().Errorf("Error while adding route for host: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.httpsTLSConfig == nil {
|
||||||
|
r.httpsForwarder = &brokenTLSRouter{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
r.httpsForwarder = &tcp.TLSHandler{
|
r.httpsForwarder = &tcp.TLSHandler{
|
||||||
Next: handler,
|
Next: handler,
|
||||||
Config: r.httpsTLSConfig,
|
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.
|
// Conn is a connection proxy that handles Peeked bytes.
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
// Peeked are the bytes that have been read from Conn for the
|
// Peeked are the bytes that have been read from Conn for the purposes of route matching,
|
||||||
// purposes of route matching, but have not yet been consumed
|
// but have not yet been consumed by Read calls.
|
||||||
// by Read calls. It set to nil by Read when fully consumed.
|
// It set to nil by Read when fully consumed.
|
||||||
Peeked []byte
|
Peeked []byte
|
||||||
|
|
||||||
// Conn is the underlying connection.
|
// Conn is the underlying connection.
|
||||||
// It can be type asserted against *net.TCPConn or other types
|
// It can be type asserted against *net.TCPConn or other types as needed.
|
||||||
// as needed. It should not be read from directly unless
|
// It should not be read from directly unless Peeked is nil.
|
||||||
// Peeked is nil.
|
|
||||||
tcp.WriteCloser
|
tcp.WriteCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,15 +331,14 @@ func clientHelloInfo(br *bufio.Reader) (*clientHello, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// No valid TLS record has a type of 0x80, however SSLv2 handshakes
|
// No valid TLS record has a type of 0x80, however SSLv2 handshakes start with an uint16 length
|
||||||
// start with a uint16 length where the MSB is set and the first record
|
// where the MSB is set and the first record is always < 256 bytes long.
|
||||||
// is always < 256 bytes long. Therefore typ == 0x80 strongly suggests
|
// Therefore, typ == 0x80 strongly suggests an SSLv2 client.
|
||||||
// an SSLv2 client.
|
|
||||||
const recordTypeSSLv2 = 0x80
|
const recordTypeSSLv2 = 0x80
|
||||||
const recordTypeHandshake = 0x16
|
const recordTypeHandshake = 0x16
|
||||||
if hdr[0] != recordTypeHandshake {
|
if hdr[0] != recordTypeHandshake {
|
||||||
if hdr[0] == recordTypeSSLv2 {
|
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{
|
return &clientHello{
|
||||||
isTLS: true,
|
isTLS: true,
|
||||||
peeked: getPeeked(br),
|
peeked: getPeeked(br),
|
||||||
|
|
|
@ -72,7 +72,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
|
||||||
|
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
|
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)
|
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
|
||||||
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
|
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
|
||||||
|
|
|
@ -157,19 +157,16 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
|
||||||
m.lock.RLock()
|
m.lock.RLock()
|
||||||
defer m.lock.RUnlock()
|
defer m.lock.RUnlock()
|
||||||
|
|
||||||
var tlsConfig *tls.Config
|
|
||||||
var err error
|
|
||||||
|
|
||||||
sniStrict := false
|
sniStrict := false
|
||||||
config, ok := m.configs[configName]
|
config, ok := m.configs[configName]
|
||||||
if ok {
|
if !ok {
|
||||||
sniStrict = config.SniStrict
|
return nil, fmt.Errorf("unknown TLS options: %s", configName)
|
||||||
tlsConfig, err = buildTLSConfig(config)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("unknown TLS options: %s", configName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sniStrict = config.SniStrict
|
||||||
|
tlsConfig, err := buildTLSConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlsConfig = &tls.Config{}
|
return nil, fmt.Errorf("building TLS config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
store := m.getStore(storeName)
|
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)
|
err = fmt.Errorf("TLS store %s not found", storeName)
|
||||||
}
|
}
|
||||||
acmeTLSStore := m.getStore(tlsalpn01.ACMETLS1Protocol)
|
acmeTLSStore := m.getStore(tlsalpn01.ACMETLS1Protocol)
|
||||||
if acmeTLSStore == nil {
|
if acmeTLSStore == nil && err == nil {
|
||||||
err = fmt.Errorf("ACME TLS store %s not found", tlsalpn01.ACMETLS1Protocol)
|
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)
|
certificate := acmeTLSStore.GetBestCertificate(clientHello)
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
log.WithoutContext().Debugf("TLS: no certificate for TLSALPN challenge: %s", domainToCheck)
|
log.WithoutContext().Debugf("TLS: no certificate for TLSALPN challenge: %s", domainToCheck)
|
||||||
// We want the user to eventually get the (alertUnrecognizedName) "unrecognized
|
// We want the user to eventually get the (alertUnrecognizedName) "unrecognized name" error.
|
||||||
// name" error.
|
// Unfortunately, if we returned an error here,
|
||||||
// Unfortunately, if we returned an error here, since we can't use
|
// since we can't use the unexported error (errNoCertificates) that our caller (config.getCertificate in crypto/tls) uses as a sentinel,
|
||||||
// the unexported error (errNoCertificates) that our caller (config.getCertificate
|
// it would report an (alertInternalError) "internal error" instead of an alertUnrecognizedName.
|
||||||
// in crypto/tls) uses as a sentinel, it would report an (alertInternalError)
|
// Which is why we return no error, and we let the caller detect that there's actually no certificate,
|
||||||
// "internal error" instead of an alertUnrecognizedName.
|
// and fall back into the flow that will report the desired error.
|
||||||
// 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
|
// https://cs.opensource.google/go/go/+/dev.boringcrypto.go1.17:src/crypto/tls/common.go;l=1058
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,6 +121,7 @@ func TestManager_Get(t *testing.T) {
|
||||||
tlsConfigs := map[string]Options{
|
tlsConfigs := map[string]Options{
|
||||||
"foo": {MinVersion: "VersionTLS12"},
|
"foo": {MinVersion: "VersionTLS12"},
|
||||||
"bar": {MinVersion: "VersionTLS11"},
|
"bar": {MinVersion: "VersionTLS11"},
|
||||||
|
"invalid": {CurvePreferences: []string{"42"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
@ -140,15 +141,20 @@ func TestManager_Get(t *testing.T) {
|
||||||
expectedMinVersion: uint16(tls.VersionTLS11),
|
expectedMinVersion: uint16(tls.VersionTLS11),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Get an tls config from an invalid name",
|
desc: "Get a tls config from an invalid name",
|
||||||
tlsOptionsName: "unknown",
|
tlsOptionsName: "unknown",
|
||||||
expectedError: true,
|
expectedError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Get an tls config from unexisting 'default' name",
|
desc: "Get a tls config from unexisting 'default' name",
|
||||||
tlsOptionsName: "default",
|
tlsOptionsName: "default",
|
||||||
expectedError: true,
|
expectedError: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Get an invalid tls config",
|
||||||
|
tlsOptionsName: "invalid",
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsManager := NewManager()
|
tlsManager := NewManager()
|
||||||
|
@ -161,42 +167,13 @@ func TestManager_Get(t *testing.T) {
|
||||||
|
|
||||||
config, err := tlsManager.Get("default", test.tlsOptionsName)
|
config, err := tlsManager.Get("default", test.tlsOptionsName)
|
||||||
if test.expectedError {
|
if test.expectedError {
|
||||||
assert.Error(t, err)
|
require.Nil(t, config)
|
||||||
|
require.Error(t, err)
|
||||||
return
|
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)
|
require.NoError(t, err)
|
||||||
test.expectedCertificate(t, certificate)
|
assert.Equal(t, config.MinVersion, test.expectedMinVersion)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue