Define TLS options on the Router configuration

Co-authored-by: juliens <julien@containo.us>
This commit is contained in:
Jean-Baptiste Doumenjou 2019-06-17 18:14:08 +02:00 committed by Traefiker Bot
parent d306c8fd50
commit 85ce16b34f
24 changed files with 958 additions and 148 deletions

View file

@ -9,6 +9,7 @@
Rule = "foobar"
priority = 42
[HTTP.Routers.Router0.tls]
options = "TLS0"
[HTTP.Middlewares]
@ -206,6 +207,7 @@
Rule = "foobar"
[TCP.Routers.TCPRouter0.tls]
passthrough = true
options = "TLS1"
[TCP.Services]

View file

@ -109,6 +109,7 @@ labels:
- "traefik.HTTP.Routers.Router0.Rule=foobar"
- "traefik.HTTP.Routers.Router0.Service=foobar"
- "traefik.HTTP.Routers.Router0.TLS=true"
- "traefik.HTTP.Routers.Router0.TLS.options=foo"
- "traefik.HTTP.Routers.Router1.EntryPoints=foobar, fiibar"
- "traefik.HTTP.Routers.Router1.Middlewares=foobar, fiibar"
- "traefik.HTTP.Routers.Router1.Priority=42"
@ -143,9 +144,11 @@ labels:
- "traefik.TCP.Routers.Router0.EntryPoints=foobar, fiibar"
- "traefik.TCP.Routers.Router0.Service=foobar"
- "traefik.TCP.Routers.Router0.TLS.Passthrough=false"
- "traefik.TCP.Routers.Router0.TLS.options=bar"
- "traefik.TCP.Routers.Router1.Rule=foobar"
- "traefik.TCP.Routers.Router1.EntryPoints=foobar, fiibar"
- "traefik.TCP.Routers.Router1.Service=foobar"
- "traefik.TCP.Routers.Router1.TLS.Passthrough=false"
- "traefik.TCP.Routers.Router1.TLS.options=foobar"
- "traefik.TCP.Services.Service0.LoadBalancer.server.Port=42"
- "traefik.TCP.Services.Service1.LoadBalancer.server.Port=42"

View file

@ -156,7 +156,9 @@ Services are the target for the router.
### TLS
When specifying a TLS section, you tell Traefik that the current router is dedicated to HTTPS requests only (and that the router should ignore HTTP (non tls) requests).
#### General
When a TLS section is specified, it instructs Traefik that the current router is dedicated to HTTPS requests only (and that the router should ignore HTTP (non TLS) requests).
Traefik will terminate the SSL connections (meaning that it will send decrypted data to the services).
??? example "Configuring the router to accept HTTPS requests only"
@ -172,8 +174,7 @@ Traefik will terminate the SSL connections (meaning that it will send decrypted
!!! note "HTTPS & ACME"
In the current version, with [ACME](../../https-tls/acme.md) enabled, automatic certificate generation will apply to every router declaring a TLS section.
In the near future, options will be available to enable fine-grain control of the TLS parameters.
!!! note "Passthrough"
On TCP routers, you can configure a passthrough option so that Traefik doesn't terminate the TLS connection.
@ -196,6 +197,31 @@ Traefik will terminate the SSL connections (meaning that it will send decrypted
service = "service-id"
```
#### `Options`
The `Options` field enables fine-grained control of the TLS parameters.
It refers to a [tlsOptions](../../https-tls/overview/#configuration-options) and will be applied only if a `Host` rule is defined.
??? example "Configuring the tls options"
```toml
[http.routers]
[http.routers.Router-1]
rule = "Host(`foo-domain`) && Path(`/foo-path/`)"
service = "service-id"
[http.routers.Router-1.tls] # will terminate the TLS request
options = "foo"
[tlsOptions]
[tlsOptions.foo]
minVersion = "VersionTLS12"
cipherSuites = [
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384"
]
```
## Configuring TCP Routers
### General
@ -269,8 +295,10 @@ Services are the target for the router.
### TLS
When specifying a TLS section, you tell Traefik that the current router is dedicated to TLS requests only (and that the router should ignore non-tls requests).
By default, Traefik will terminate the SSL connections (meaning that it will send decrypted data to the services), but you can tell Traefik that the request should pass through (keeping the encrypted data) and be forwarded to the service "as is".
#### General
When a TLS section is specified, it instructs Traefik that the current router is dedicated to TLS requests only (and that the router should ignore non-TLS requests).
By default, Traefik will terminate the SSL connections (meaning that it will send decrypted data to the services), but Traefik can be configured in order to let the requests pass through (keeping the data encrypted), and be forwarded to the service "as is".
??? example "Configuring TLS Termination"
@ -296,4 +324,28 @@ By default, Traefik will terminate the SSL connections (meaning that it will sen
!!! note "TLS & ACME"
In the current version, with [ACME](../../https-tls/acme.md) enabled, automatic certificate generation will apply to every router declaring a TLS section.
In the near future, options will be available to enable fine-grain control of the TLS parameters.
#### `Options`
The `Options` field enables fine-grained control of the TLS parameters.
It refers to a [tlsOptions](../../https-tls/overview/#configuration-options) and will be applied only if a `HostSNI` rule is defined.
??? example "Configuring the tls options"
```toml
[tcp.routers]
[tcp.routers.Router-1]
rule = "Host(`foo-domain`) && Path(`/foo-path/`)"
service = "service-id"
[tcp.routers.Router-1.tls] # will terminate the TLS request
options = "foo"
[tlsOptions]
[tlsOptions.foo]
minVersion = "VersionTLS12"
cipherSuites = [
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384"
]
```

View file

@ -0,0 +1,62 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.web-secure]
address = ":4443"
[api]
[providers]
[providers.file]
[http.routers]
[http.routers.router1]
Service = "service1"
Rule = "Host(`snitest.com`)"
[http.routers.router1.tls]
options = "foo"
[http.routers.router2]
Service = "service2"
Rule = "Host(`snitest.org`)"
[http.routers.router2.tls]
options = "bar"
[http.routers.router3]
Service = "service2"
Rule = "Host(`snitest.org`)"
[http.routers.router3.tls]
options = "unknown"
[http.services]
[http.services.service1]
[http.services.service1.LoadBalancer]
[[http.services.service1.LoadBalancer.Servers]]
URL = "http://127.0.0.1:9010"
[http.services.service2]
[http.services.service2.LoadBalancer]
[[http.services.service2.LoadBalancer.Servers]]
URL = "http://127.0.0.1:9020"
[[tls]]
[tls.certificate]
certFile = "fixtures/https/snitest.com.cert"
keyFile = "fixtures/https/snitest.com.key"
[[tls]]
[tls.certificate]
certFile = "fixtures/https/snitest.org.cert"
keyFile = "fixtures/https/snitest.org.key"
[tlsoptions.foo]
minversion = "VersionTLS11"
[tlsoptions.bar]
minversion = "VersionTLS12"

View file

@ -2,12 +2,19 @@
checkNewVersion = false
sendAnonymousUsage = false
[api]
entrypoint="api"
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.web]
address = ":80"
address = ":8081"
[entryPoints.api]
address = ":8080"
[providers]
[providers.file]

View file

@ -38,4 +38,3 @@ level = "DEBUG"
[http.services.whoami.loadbalancer]
[[http.services.whoami.loadbalancer.servers]]
url = "http://localhost:8085"
weight=1

View file

@ -0,0 +1,42 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.tcp]
address = ":8093"
[api]
[providers.file]
[tcp]
[tcp.routers]
[tcp.routers.to-whoami-no-cert]
rule = "HostSNI(`whoami-c.test`)"
service = "whoami-no-cert"
entryPoints = [ "tcp" ]
[tcp.routers.to-whoami-no-cert.tls]
options = "foo"
[tcp.routers.to-whoami-sni-strict]
rule = "HostSNI(`whoami-d.test`)"
service = "whoami-no-cert"
entryPoints = [ "tcp" ]
[tcp.routers.to-whoami-sni-strict.tls]
options = "bar"
[tcp.services.whoami-no-cert]
[tcp.services.whoami-no-cert.loadbalancer]
method = "wrr"
[[tcp.services.whoami-no-cert.loadbalancer.servers]]
address = "localhost:8083"
[tlsoptions.foo]
minversion = "VersionTLS11"
[tlsoptions.bar]
minversion = "VersionTLS12"

View file

@ -111,6 +111,90 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
c.Assert(err, checker.IsNil)
}
// TestWithTLSOptions verifies that traefik routes the requests with the associated tls options.
func (s *HTTPSSuite) TestWithTLSOptions(c *check.C) {
cmd, display := s.traefikCmd(withConfigFile("fixtures/https/https_tls_options.toml"))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)"))
c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent)
backend2 := startTestServer("9020", http.StatusResetContent)
defer backend1.Close()
defer backend2.Close()
err = try.GetRequest(backend1.URL, 1*time.Second, try.StatusCodeIs(http.StatusNoContent))
c.Assert(err, checker.IsNil)
err = try.GetRequest(backend2.URL, 1*time.Second, try.StatusCodeIs(http.StatusResetContent))
c.Assert(err, checker.IsNil)
tr1 := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
MaxVersion: tls.VersionTLS11,
ServerName: "snitest.com",
},
}
tr2 := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
MaxVersion: tls.VersionTLS12,
ServerName: "snitest.org",
},
}
tr3 := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
MaxVersion: tls.VersionTLS11,
ServerName: "snitest.org",
},
}
// With valid TLS options and request
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
c.Assert(err, checker.IsNil)
req.Host = tr1.TLSClientConfig.ServerName
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
req.Header.Set("Accept", "*/*")
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusNoContent))
c.Assert(err, checker.IsNil)
// With a valid TLS version
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
c.Assert(err, checker.IsNil)
req.Host = tr2.TLSClientConfig.ServerName
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
req.Header.Set("Accept", "*/*")
err = try.RequestWithTransport(req, 3*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusResetContent))
c.Assert(err, checker.IsNil)
// With a bad TLS version
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
c.Assert(err, checker.IsNil)
req.Host = tr3.TLSClientConfig.ServerName
req.Header.Set("Host", tr3.TLSClientConfig.ServerName)
req.Header.Set("Accept", "*/*")
client := http.Client{
Transport: tr3,
}
_, err = client.Do(req)
c.Assert(err, checker.NotNil)
c.Assert(err.Error(), checker.Contains, "protocol version not supported")
// with unknown tls option
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("unknown TLS options: unknown"))
c.Assert(err, checker.IsNil)
}
// TestWithSNIStrictNotMatchedRequest involves a client sending a SNI hostname of
// "snitest.org", which does not match the CN of 'snitest.com.crt'. The test
// verifies that traefik closes the connection.

View file

@ -28,34 +28,38 @@ func (s *RateLimitSuite) TestSimpleConfiguration(c *check.C) {
}{s.ServerIP})
defer os.Remove(file)
cmd, _ := s.cmdTraefik(withConfigFile(file))
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:80/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("ratelimit"))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:80/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:80/", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests))
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests))
c.Assert(err, checker.IsNil)
// sleep for 4 seconds to be certain the configured time period has elapsed
// then test another request and verify a 200 status code
time.Sleep(4 * time.Second)
err = try.GetRequest("http://127.0.0.1:80/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
// continue requests at 3 second intervals to test the other rate limit time period
time.Sleep(3 * time.Second)
err = try.GetRequest("http://127.0.0.1:80/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
time.Sleep(3 * time.Second)
err = try.GetRequest("http://127.0.0.1:80/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
time.Sleep(3 * time.Second)
err = try.GetRequest("http://127.0.0.1:80/", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests))
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests))
c.Assert(err, checker.IsNil)
}

View file

@ -70,6 +70,36 @@ func (s *TCPSuite) TestMixed(c *check.C) {
c.Assert(err, checker.IsNil)
}
func (s *TCPSuite) TestTLSOptions(c *check.C) {
file := s.adaptFile(c, "fixtures/tcp/multi-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 cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-c.test`)"))
c.Assert(err, checker.IsNil)
// Check that we can use a client tls version <= 1.1 with hostSNI 'whoami-c.test'
out, err := guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-c.test", true, tls.VersionTLS11)
c.Assert(err, checker.IsNil)
c.Assert(out, checker.Contains, "whoami-no-cert")
// Check that we can use a client tls version <= 1.2 with hostSNI 'whoami-d.test'
out, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12)
c.Assert(err, checker.IsNil)
c.Assert(out, checker.Contains, "whoami-no-cert")
// Check that we cannot use a client tls version <= 1.1 with hostSNI 'whoami-d.test'
_, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS11)
c.Assert(err, checker.NotNil)
c.Assert(err.Error(), checker.Contains, "protocol version not supported")
}
func (s *TCPSuite) TestNonTLSFallback(c *check.C) {
file := s.adaptFile(c, "fixtures/tcp/non-tls-fallback.toml", struct{}{})
defer os.Remove(file)
@ -191,11 +221,21 @@ func welcome(addr string) (string, error) {
}
func guessWho(addr, serverName string, tlsCall bool) (string, error) {
return guessWhoTLSMaxVersion(addr, serverName, tlsCall, 0)
}
func guessWhoTLSMaxVersion(addr, serverName string, tlsCall bool, tlsMaxVersion uint16) (string, error) {
var conn net.Conn
var err error
if tlsCall {
conn, err = tls.Dial("tcp", addr, &tls.Config{ServerName: serverName, InsecureSkipVerify: true})
conn, err = tls.Dial("tcp", addr, &tls.Config{
ServerName: serverName,
InsecureSkipVerify: true,
MinVersion: 0,
MaxVersion: tlsMaxVersion,
})
} else {
tcpAddr, err2 := net.ResolveTCPAddr("tcp", addr)
if err2 != nil {

View file

@ -58,7 +58,7 @@ func Test_doOnJSON(t *testing.T) {
"DNSProvider": "",
"DelayDontCheckDNS": 0,
"ACMELogging": false,
"TLSOptions": null
"Options": null
},
"DefaultEntryPoints": [
"https",
@ -141,7 +141,7 @@ func Test_doOnJSON(t *testing.T) {
"DNSProvider": "",
"DelayDontCheckDNS": 0,
"ACMELogging": false,
"TLSOptions": null
"Options": null
},
"DefaultEntryPoints": [
"https",

View file

@ -22,7 +22,9 @@ type Router struct {
}
// RouterTLSConfig holds the TLS configuration for a router
type RouterTLSConfig struct{}
type RouterTLSConfig struct {
Options string `json:"options,omitempty" toml:"options,omitzero"`
}
// TCPRouter holds the router configuration.
type TCPRouter struct {
@ -34,7 +36,8 @@ type TCPRouter struct {
// RouterTCPTLSConfig holds the TLS configuration for a router
type RouterTCPTLSConfig struct {
Passthrough bool `json:"passthrough" toml:"passthrough,omitzero"`
Passthrough bool `json:"passthrough" toml:"passthrough,omitzero"`
Options string `json:"options,omitempty" toml:"options,omitzero"`
}
// LoadBalancerService holds the LoadBalancerService configuration.

View file

@ -162,9 +162,11 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.tcp.routers.Router0.entrypoints": "foobar, fiibar",
"traefik.tcp.routers.Router0.service": "foobar",
"traefik.tcp.routers.Router0.tls.passthrough": "false",
"traefik.tcp.routers.Router0.tls.options": "foo",
"traefik.tcp.routers.Router1.rule": "foobar",
"traefik.tcp.routers.Router1.entrypoints": "foobar, fiibar",
"traefik.tcp.routers.Router1.service": "foobar",
"traefik.tcp.routers.Router1.tls.options": "foo",
"traefik.tcp.routers.Router1.tls.passthrough": "false",
"traefik.tcp.services.Service0.loadbalancer.server.Port": "42",
"traefik.tcp.services.Service1.loadbalancer.server.Port": "42",
@ -185,6 +187,7 @@ func TestDecodeConfiguration(t *testing.T) {
Rule: "foobar",
TLS: &config.RouterTCPTLSConfig{
Passthrough: false,
Options: "foo",
},
},
"Router1": {
@ -196,6 +199,7 @@ func TestDecodeConfiguration(t *testing.T) {
Rule: "foobar",
TLS: &config.RouterTCPTLSConfig{
Passthrough: false,
Options: "foo",
},
},
},
@ -580,6 +584,7 @@ func TestEncodeConfiguration(t *testing.T) {
Rule: "foobar",
TLS: &config.RouterTCPTLSConfig{
Passthrough: false,
Options: "foo",
},
},
"Router1": {
@ -591,6 +596,7 @@ func TestEncodeConfiguration(t *testing.T) {
Rule: "foobar",
TLS: &config.RouterTCPTLSConfig{
Passthrough: false,
Options: "foo",
},
},
},
@ -1110,10 +1116,12 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.TCP.Routers.Router0.EntryPoints": "foobar, fiibar",
"traefik.TCP.Routers.Router0.Service": "foobar",
"traefik.TCP.Routers.Router0.TLS.Passthrough": "false",
"traefik.TCP.Routers.Router0.TLS.Options": "foo",
"traefik.TCP.Routers.Router1.Rule": "foobar",
"traefik.TCP.Routers.Router1.EntryPoints": "foobar, fiibar",
"traefik.TCP.Routers.Router1.Service": "foobar",
"traefik.TCP.Routers.Router1.TLS.Passthrough": "false",
"traefik.TCP.Routers.Router1.TLS.Options": "foo",
"traefik.TCP.Services.Service0.LoadBalancer.server.Port": "42",
"traefik.TCP.Services.Service1.LoadBalancer.server.Port": "42",
}

View file

@ -1,6 +1,7 @@
package config
import (
"context"
"sort"
"strings"
"sync"
@ -128,6 +129,74 @@ func (r *RuntimeConfiguration) PopulateUsedBy() {
}
}
func contains(entryPoints []string, entryPointName string) bool {
for _, name := range entryPoints {
if name == entryPointName {
return true
}
}
return false
}
// GetRoutersByEntrypoints returns all the http routers by entrypoints name and routers name
func (r *RuntimeConfiguration) GetRoutersByEntrypoints(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*RouterInfo {
entryPointsRouters := make(map[string]map[string]*RouterInfo)
for rtName, rt := range r.Routers {
if (tls && rt.TLS == nil) || (!tls && rt.TLS != nil) {
continue
}
eps := rt.EntryPoints
if len(eps) == 0 {
eps = entryPoints
}
for _, entryPointName := range eps {
if !contains(entryPoints, entryPointName) {
log.FromContext(log.With(ctx, log.Str(log.EntryPointName, entryPointName))).
Errorf("entryPoint %q doesn't exist", entryPointName)
continue
}
if _, ok := entryPointsRouters[entryPointName]; !ok {
entryPointsRouters[entryPointName] = make(map[string]*RouterInfo)
}
entryPointsRouters[entryPointName][rtName] = rt
}
}
return entryPointsRouters
}
// GetTCPRoutersByEntrypoints returns all the tcp routers by entrypoints name and routers name
func (r *RuntimeConfiguration) GetTCPRoutersByEntrypoints(ctx context.Context, entryPoints []string) map[string]map[string]*TCPRouterInfo {
entryPointsRouters := make(map[string]map[string]*TCPRouterInfo)
for rtName, rt := range r.TCPRouters {
eps := rt.EntryPoints
if len(eps) == 0 {
eps = entryPoints
}
for _, entryPointName := range eps {
if !contains(entryPoints, entryPointName) {
log.FromContext(log.With(ctx, log.Str(log.EntryPointName, entryPointName))).
Errorf("entryPoint %q doesn't exist", entryPointName)
continue
}
if _, ok := entryPointsRouters[entryPointName]; !ok {
entryPointsRouters[entryPointName] = make(map[string]*TCPRouterInfo)
}
entryPointsRouters[entryPointName][rtName] = rt
}
}
return entryPointsRouters
}
// RouterInfo holds information about a currently running HTTP router
type RouterInfo struct {
*Router // dynamic configuration

View file

@ -1,6 +1,7 @@
package config_test
import (
"context"
"testing"
"github.com/containous/traefik/pkg/config"
@ -688,3 +689,399 @@ func TestPopulateUsedby(t *testing.T) {
}
}
func TestGetTCPRoutersByEntrypoints(t *testing.T) {
testCases := []struct {
desc string
conf config.Configuration
entryPoints []string
expected map[string]map[string]*config.TCPRouterInfo
}{
{
desc: "Empty Configuration without entrypoint",
conf: config.Configuration{},
entryPoints: []string{""},
expected: map[string]map[string]*config.TCPRouterInfo{},
},
{
desc: "Empty Configuration with unknown entrypoints",
conf: config.Configuration{},
entryPoints: []string{"foo"},
expected: map[string]map[string]*config.TCPRouterInfo{},
},
{
desc: "Valid configuration with an unknown entrypoint",
conf: config.Configuration{
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`bar.foo`)",
},
},
},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{
"foo": {
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "HostSNI(`bar.foo`)",
},
},
},
},
entryPoints: []string{"foo"},
expected: map[string]map[string]*config.TCPRouterInfo{},
},
{
desc: "Valid configuration with a known entrypoint",
conf: config.Configuration{
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`bar.foo`)",
},
"bar": {
EntryPoints: []string{"webs"},
Service: "myprovider.bar-service",
Rule: "Host(`foo.bar`)",
},
"foobar": {
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "Host(`bar.foobar`)",
},
},
},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{
"foo": {
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "HostSNI(`bar.foo`)",
},
"bar": {
EntryPoints: []string{"webs"},
Service: "myprovider.bar-service",
Rule: "HostSNI(`foo.bar`)",
},
"foobar": {
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "HostSNI(`bar.foobar`)",
},
},
},
},
entryPoints: []string{"web"},
expected: map[string]map[string]*config.TCPRouterInfo{
"web": {
"foo": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "HostSNI(`bar.foo`)",
},
},
"foobar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "HostSNI(`bar.foobar`)",
},
},
},
},
},
{
desc: "Valid configuration with multiple known entrypoints",
conf: config.Configuration{
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`bar.foo`)",
},
"bar": {
EntryPoints: []string{"webs"},
Service: "myprovider.bar-service",
Rule: "Host(`foo.bar`)",
},
"foobar": {
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "Host(`bar.foobar`)",
},
},
},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{
"foo": {
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "HostSNI(`bar.foo`)",
},
"bar": {
EntryPoints: []string{"webs"},
Service: "myprovider.bar-service",
Rule: "HostSNI(`foo.bar`)",
},
"foobar": {
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "HostSNI(`bar.foobar`)",
},
},
},
},
entryPoints: []string{"web", "webs"},
expected: map[string]map[string]*config.TCPRouterInfo{
"web": {
"foo": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "HostSNI(`bar.foo`)",
},
},
"foobar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "HostSNI(`bar.foobar`)",
},
},
},
"webs": {
"bar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"webs"},
Service: "myprovider.bar-service",
Rule: "HostSNI(`foo.bar`)",
},
},
"foobar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "HostSNI(`bar.foobar`)",
},
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
runtimeConfig := config.NewRuntimeConfig(test.conf)
actual := runtimeConfig.GetTCPRoutersByEntrypoints(context.Background(), test.entryPoints)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRoutersByEntrypoints(t *testing.T) {
testCases := []struct {
desc string
conf config.Configuration
entryPoints []string
expected map[string]map[string]*config.RouterInfo
}{
{
desc: "Empty Configuration without entrypoint",
conf: config.Configuration{},
entryPoints: []string{""},
expected: map[string]map[string]*config.RouterInfo{},
},
{
desc: "Empty Configuration with unknown entrypoints",
conf: config.Configuration{},
entryPoints: []string{"foo"},
expected: map[string]map[string]*config.RouterInfo{},
},
{
desc: "Valid configuration with an unknown entrypoint",
conf: config.Configuration{
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`bar.foo`)",
},
},
},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{
"foo": {
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "HostSNI(`bar.foo`)",
},
},
},
},
entryPoints: []string{"foo"},
expected: map[string]map[string]*config.RouterInfo{},
},
{
desc: "Valid configuration with a known entrypoint",
conf: config.Configuration{
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`bar.foo`)",
},
"bar": {
EntryPoints: []string{"webs"},
Service: "myprovider.bar-service",
Rule: "Host(`foo.bar`)",
},
"foobar": {
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "Host(`bar.foobar`)",
},
},
},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{
"foo": {
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "HostSNI(`bar.foo`)",
},
"bar": {
EntryPoints: []string{"webs"},
Service: "myprovider.bar-service",
Rule: "HostSNI(`foo.bar`)",
},
"foobar": {
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "HostSNI(`bar.foobar`)",
},
},
},
},
entryPoints: []string{"web"},
expected: map[string]map[string]*config.RouterInfo{
"web": {
"foo": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`bar.foo`)",
},
},
"foobar": {
Router: &config.Router{
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "Host(`bar.foobar`)",
},
},
},
},
},
{
desc: "Valid configuration with multiple known entrypoints",
conf: config.Configuration{
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`bar.foo`)",
},
"bar": {
EntryPoints: []string{"webs"},
Service: "myprovider.bar-service",
Rule: "Host(`foo.bar`)",
},
"foobar": {
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "Host(`bar.foobar`)",
},
},
},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{
"foo": {
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "HostSNI(`bar.foo`)",
},
"bar": {
EntryPoints: []string{"webs"},
Service: "myprovider.bar-service",
Rule: "HostSNI(`foo.bar`)",
},
"foobar": {
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "HostSNI(`bar.foobar`)",
},
},
},
},
entryPoints: []string{"web", "webs"},
expected: map[string]map[string]*config.RouterInfo{
"web": {
"foo": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`bar.foo`)",
},
},
"foobar": {
Router: &config.Router{
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "Host(`bar.foobar`)",
},
},
},
"webs": {
"bar": {
Router: &config.Router{
EntryPoints: []string{"webs"},
Service: "myprovider.bar-service",
Rule: "Host(`foo.bar`)",
},
},
"foobar": {
Router: &config.Router{
EntryPoints: []string{"web", "webs"},
Service: "myprovider.foobar-service",
Rule: "Host(`bar.foobar`)",
},
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
runtimeConfig := config.NewRuntimeConfig(test.conf)
actual := runtimeConfig.GetRoutersByEntrypoints(context.Background(), test.entryPoints, false)
assert.Equal(t, test.expected, actual)
})
}
}

View file

@ -2164,7 +2164,7 @@ func Test_buildConfiguration(t *testing.T) {
Name: "Test",
Labels: map[string]string{
"traefik.tcp.routers.foo.rule": "HostSNI(`foo.bar`)",
"traefik.tcp.routers.foo.tls": "true",
"traefik.tcp.routers.foo.tls.options": "foo",
"traefik.tcp.services.foo.loadbalancer.server.port": "8080",
},
NetworkSettings: networkSettings{
@ -2186,7 +2186,9 @@ func Test_buildConfiguration(t *testing.T) {
"foo": {
Service: "foo",
Rule: "HostSNI(`foo.bar`)",
TLS: &config.RouterTCPTLSConfig{},
TLS: &config.RouterTCPTLSConfig{
Options: "foo",
},
},
},
Services: map[string]*config.TCPService{

View file

@ -580,7 +580,6 @@ func Test_buildConfiguration(t *testing.T) {
Name: "Test",
Labels: map[string]string{
"traefik.tcp.routers.foo.rule": "HostSNI(`foo.bar`)",
"traefik.tcp.routers.foo.tls": "true",
"traefik.tcp.services.foo.loadbalancer.server.port": "8080",
},
Port: "80/tcp",
@ -595,7 +594,6 @@ func Test_buildConfiguration(t *testing.T) {
"foo": {
Service: "foo",
Rule: "HostSNI(`foo.bar`)",
TLS: &config.RouterTCPTLSConfig{},
},
},
Services: map[string]*config.TCPService{

View file

@ -2,7 +2,6 @@ package router
import (
"context"
"fmt"
"net/http"
"github.com/containous/alice"
@ -23,33 +22,42 @@ const (
)
// NewManager Creates a new Manager
func NewManager(routers map[string]*config.RouterInfo,
serviceManager *service.Manager, middlewaresBuilder *middleware.Builder, modifierBuilder *responsemodifiers.Builder,
func NewManager(conf *config.RuntimeConfiguration,
serviceManager *service.Manager,
middlewaresBuilder *middleware.Builder,
modifierBuilder *responsemodifiers.Builder,
) *Manager {
return &Manager{
routerHandlers: make(map[string]http.Handler),
configs: routers,
serviceManager: serviceManager,
middlewaresBuilder: middlewaresBuilder,
modifierBuilder: modifierBuilder,
conf: conf,
}
}
// Manager A route/router manager
type Manager struct {
routerHandlers map[string]http.Handler
configs map[string]*config.RouterInfo
serviceManager *service.Manager
middlewaresBuilder *middleware.Builder
modifierBuilder *responsemodifiers.Builder
conf *config.RuntimeConfiguration
}
func (m *Manager) getHTTPRouters(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*config.RouterInfo {
if m.conf != nil {
return m.conf.GetRoutersByEntrypoints(ctx, entryPoints, tls)
}
return make(map[string]map[string]*config.RouterInfo)
}
// BuildHandlers Builds handler for all entry points
func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, tls bool) map[string]http.Handler {
entryPointsRouters := m.filteredRouters(rootCtx, entryPoints, tls)
entryPointHandlers := make(map[string]http.Handler)
for entryPointName, routers := range entryPointsRouters {
for entryPointName, routers := range m.getHTTPRouters(rootCtx, entryPoints, tls) {
entryPointName := entryPointName
ctx := log.With(rootCtx, log.Str(log.EntryPointName, entryPointName))
@ -75,45 +83,6 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
return entryPointHandlers
}
func contains(entryPoints []string, entryPointName string) bool {
for _, name := range entryPoints {
if name == entryPointName {
return true
}
}
return false
}
func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*config.RouterInfo {
entryPointsRouters := make(map[string]map[string]*config.RouterInfo)
for rtName, rt := range m.configs {
if (tls && rt.TLS == nil) || (!tls && rt.TLS != nil) {
continue
}
eps := rt.EntryPoints
if len(eps) == 0 {
eps = entryPoints
}
for _, entryPointName := range eps {
if !contains(entryPoints, entryPointName) {
log.FromContext(log.With(ctx, log.Str(log.EntryPointName, entryPointName))).
Errorf("entryPoint %q doesn't exist", entryPointName)
continue
}
if _, ok := entryPointsRouters[entryPointName]; !ok {
entryPointsRouters[entryPointName] = make(map[string]*config.RouterInfo)
}
entryPointsRouters[entryPointName][rtName] = rt
}
}
return entryPointsRouters
}
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.RouterInfo) (http.Handler, error) {
router, err := rules.NewRouter()
if err != nil {
@ -124,7 +93,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
ctxRouter := log.With(internal.AddProviderInContext(ctx, routerName), log.Str(log.RouterName, routerName))
logger := log.FromContext(ctxRouter)
handler, err := m.buildRouterHandler(ctxRouter, routerName)
handler, err := m.buildRouterHandler(ctxRouter, routerName, routerConfig)
if err != nil {
routerConfig.Err = err.Error()
logger.Error(err)
@ -149,17 +118,12 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
return chain.Then(router)
}
func (m *Manager) buildRouterHandler(ctx context.Context, routerName string) (http.Handler, error) {
func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, routerConfig *config.RouterInfo) (http.Handler, error) {
if handler, ok := m.routerHandlers[routerName]; ok {
return handler, nil
}
configRouter, ok := m.configs[routerName]
if !ok {
return nil, fmt.Errorf("no configuration for %s", routerName)
}
handler, err := m.buildHTTPHandler(ctx, configRouter, routerName)
handler, err := m.buildHTTPHandler(ctx, routerConfig, routerName)
if err != nil {
return nil, err
}

View file

@ -308,7 +308,7 @@ func TestRouterManager_Get(t *testing.T) {
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := NewManager(rtConf.Routers, serviceManager, middlewaresBuilder, responseModifierFactory)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory)
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
@ -409,7 +409,7 @@ func TestAccessLog(t *testing.T) {
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := NewManager(rtConf.Routers, serviceManager, middlewaresBuilder, responseModifierFactory)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory)
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
@ -695,7 +695,7 @@ func TestRuntimeConfiguration(t *testing.T) {
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*config.MiddlewareInfo{})
routerManager := NewManager(rtConf.Routers, serviceManager, middlewaresBuilder, responseModifierFactory)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory)
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
@ -769,7 +769,7 @@ func BenchmarkRouterServe(b *testing.B) {
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res})
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := NewManager(rtConf.Routers, serviceManager, middlewaresBuilder, responseModifierFactory)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory)
handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)

View file

@ -2,7 +2,6 @@ package tcp
import (
"context"
"crypto/tls"
"fmt"
"net/http"
@ -12,6 +11,7 @@ import (
"github.com/containous/traefik/pkg/server/internal"
tcpservice "github.com/containous/traefik/pkg/server/service/tcp"
"github.com/containous/traefik/pkg/tcp"
"github.com/containous/traefik/pkg/tls"
)
// NewManager Creates a new Manager
@ -19,29 +19,46 @@ func NewManager(conf *config.RuntimeConfiguration,
serviceManager *tcpservice.Manager,
httpHandlers map[string]http.Handler,
httpsHandlers map[string]http.Handler,
tlsConfig *tls.Config,
tlsManager *tls.Manager,
) *Manager {
return &Manager{
configs: conf.TCPRouters,
serviceManager: serviceManager,
httpHandlers: httpHandlers,
httpsHandlers: httpsHandlers,
tlsConfig: tlsConfig,
tlsManager: tlsManager,
conf: conf,
}
}
// Manager is a route/router manager
type Manager struct {
configs map[string]*config.TCPRouterInfo
serviceManager *tcpservice.Manager
httpHandlers map[string]http.Handler
httpsHandlers map[string]http.Handler
tlsConfig *tls.Config
tlsManager *tls.Manager
conf *config.RuntimeConfiguration
}
func (m *Manager) getTCPRouters(ctx context.Context, entryPoints []string) map[string]map[string]*config.TCPRouterInfo {
if m.conf != nil {
return m.conf.GetTCPRoutersByEntrypoints(ctx, entryPoints)
}
return make(map[string]map[string]*config.TCPRouterInfo)
}
func (m *Manager) getHTTPRouters(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*config.RouterInfo {
if m.conf != nil {
return m.conf.GetRoutersByEntrypoints(ctx, entryPoints, tls)
}
return make(map[string]map[string]*config.RouterInfo)
}
// BuildHandlers builds the handlers for the given entrypoints
func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) map[string]*tcp.Router {
entryPointsRouters := m.filteredRouters(rootCtx, entryPoints)
entryPointsRouters := m.getTCPRouters(rootCtx, entryPoints)
entryPointsRoutersHTTP := m.getHTTPRouters(rootCtx, entryPoints, true)
entryPointHandlers := make(map[string]*tcp.Router)
for _, entryPointName := range entryPoints {
@ -51,7 +68,7 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m
ctx := log.With(rootCtx, log.Str(log.EntryPointName, entryPointName))
handler, err := m.buildEntryPointHandler(ctx, routers, m.httpHandlers[entryPointName], m.httpsHandlers[entryPointName])
handler, err := m.buildEntryPointHandler(ctx, routers, entryPointsRoutersHTTP[entryPointName], m.httpHandlers[entryPointName], m.httpsHandlers[entryPointName])
if err != nil {
log.FromContext(ctx).Error(err)
continue
@ -61,10 +78,50 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m
return entryPointHandlers
}
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.TCPRouterInfo, handlerHTTP http.Handler, handlerHTTPS http.Handler) (*tcp.Router, error) {
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.TCPRouterInfo, configsHTTP map[string]*config.RouterInfo, handlerHTTP http.Handler, handlerHTTPS http.Handler) (*tcp.Router, error) {
router := &tcp.Router{}
router.HTTPHandler(handlerHTTP)
router.HTTPSHandler(handlerHTTPS, m.tlsConfig)
defaultTLSConf, err := m.tlsManager.Get("default", "default")
if err != nil {
return nil, err
}
router.HTTPSHandler(handlerHTTPS, defaultTLSConf)
for routerHTTPName, routerHTTPConfig := range configsHTTP {
if len(routerHTTPConfig.TLS.Options) == 0 || routerHTTPConfig.TLS.Options == "default" {
continue
}
ctxRouter := log.With(internal.AddProviderInContext(ctx, routerHTTPName), log.Str(log.RouterName, routerHTTPName))
logger := log.FromContext(ctxRouter)
domains, err := rules.ParseDomains(routerHTTPConfig.Rule)
if err != nil {
routerErr := fmt.Errorf("invalid rule %s, error: %v", routerHTTPConfig.Rule, err)
routerHTTPConfig.Err = routerErr.Error()
logger.Debug(routerErr)
continue
}
if len(domains) == 0 {
logger.Warnf("The 'default' TLS options will be applied instead of %q as no domain has been found in the rule", routerHTTPConfig.TLS.Options)
}
for _, domain := range domains {
if routerHTTPConfig.TLS != nil {
tlsConf, err := m.tlsManager.Get("default", routerHTTPConfig.TLS.Options)
if err != nil {
routerHTTPConfig.Err = err.Error()
logger.Debug(err)
continue
}
router.AddRouteHTTPTLS(domain, tlsConf)
}
}
}
for routerName, routerConfig := range configs {
ctxRouter := log.With(internal.AddProviderInContext(ctx, routerName), log.Str(log.RouterName, routerName))
@ -92,7 +149,19 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
if routerConfig.TLS.Passthrough {
router.AddRoute(domain, handler)
} else {
router.AddRouteTLS(domain, handler, m.tlsConfig)
configName := "default"
if len(routerConfig.TLS.Options) > 0 {
configName = routerConfig.TLS.Options
}
tlsConf, err := m.tlsManager.Get("default", configName)
if err != nil {
routerConfig.Err = err.Error()
logger.Debug(err)
continue
}
router.AddRouteTLS(domain, handler, tlsConf)
}
case domain == "*":
router.AddCatchAllNoTLS(handler)
@ -104,39 +173,3 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
return router, nil
}
func contains(entryPoints []string, entryPointName string) bool {
for _, name := range entryPoints {
if name == entryPointName {
return true
}
}
return false
}
func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string) map[string]map[string]*config.TCPRouterInfo {
entryPointsRouters := make(map[string]map[string]*config.TCPRouterInfo)
for rtName, rt := range m.configs {
eps := rt.EntryPoints
if len(eps) == 0 {
eps = entryPoints
}
for _, entryPointName := range eps {
if !contains(entryPoints, entryPointName) {
log.FromContext(log.With(ctx, log.Str(log.EntryPointName, entryPointName))).
Errorf("entryPoint %q doesn't exist", entryPointName)
continue
}
if _, ok := entryPointsRouters[entryPointName]; !ok {
entryPointsRouters[entryPointName] = make(map[string]*config.TCPRouterInfo)
}
entryPointsRouters[entryPointName][rtName] = rt
}
}
return entryPointsRouters
}

View file

@ -6,6 +6,7 @@ import (
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/server/service/tcp"
"github.com/containous/traefik/pkg/tls"
"github.com/stretchr/testify/assert"
)
@ -42,6 +43,10 @@ func TestRuntimeConfiguration(t *testing.T) {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "HostSNI(`bar.foo`)",
TLS: &config.RouterTCPTLSConfig{
Passthrough: false,
Options: "foo",
},
},
},
"bar": {
@ -50,6 +55,10 @@ func TestRuntimeConfiguration(t *testing.T) {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "HostSNI(`foo.bar`)",
TLS: &config.RouterTCPTLSConfig{
Passthrough: false,
Options: "bar",
},
},
},
},
@ -191,8 +200,21 @@ func TestRuntimeConfiguration(t *testing.T) {
TCPRouters: test.routerConfig,
}
serviceManager := tcp.NewManager(conf)
tlsManager := tls.NewManager()
tlsManager.UpdateConfigs(
map[string]tls.Store{},
map[string]tls.TLS{
"foo": {
MinVersion: "VersionTLS12",
},
"bar": {
MinVersion: "VersionTLS11",
},
},
[]*tls.Configuration{})
routerManager := NewManager(conf, serviceManager,
nil, nil, nil)
nil, nil, tlsManager)
_ = routerManager.BuildHandlers(context.Background(), entryPoints)

View file

@ -2,7 +2,6 @@ package server
import (
"context"
"crypto/tls"
"encoding/json"
"net/http"
"reflect"
@ -71,20 +70,21 @@ func (s *Server) loadConfigurationTCP(configurations config.Configurations) map[
rtConf := config.NewRuntimeConfig(conf)
handlersNonTLS, handlersTLS := s.createHTTPHandlers(ctx, rtConf, entryPoints)
routersTCP := s.createTCPRouters(ctx, rtConf, entryPoints, handlersNonTLS, handlersTLS, s.tlsManager.Get("default", "default"))
routersTCP := s.createTCPRouters(ctx, rtConf, entryPoints, handlersNonTLS, handlersTLS)
rtConf.PopulateUsedBy()
return routersTCP
}
// the given configuration must not be nil. its fields will get mutated.
func (s *Server) createTCPRouters(ctx context.Context, configuration *config.RuntimeConfiguration, entryPoints []string, handlers map[string]http.Handler, handlersTLS map[string]http.Handler, tlsConfig *tls.Config) map[string]*tcpCore.Router {
func (s *Server) createTCPRouters(ctx context.Context, configuration *config.RuntimeConfiguration, entryPoints []string, handlers map[string]http.Handler, handlersTLS map[string]http.Handler) map[string]*tcpCore.Router {
if configuration == nil {
return make(map[string]*tcpCore.Router)
}
serviceManager := tcp.NewManager(configuration)
routerManager := routertcp.NewManager(configuration, serviceManager, handlers, handlersTLS, tlsConfig)
routerManager := routertcp.NewManager(configuration, serviceManager, handlers, handlersTLS, s.tlsManager)
return routerManager.BuildHandlers(ctx, entryPoints)
}
@ -94,7 +94,7 @@ func (s *Server) createHTTPHandlers(ctx context.Context, configuration *config.R
serviceManager := service.NewManager(configuration.Services, s.defaultRoundTripper)
middlewaresBuilder := middleware.NewBuilder(configuration.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(configuration.Middlewares)
routerManager := router.NewManager(configuration.Routers, serviceManager, middlewaresBuilder, responseModifierFactory)
routerManager := router.NewManager(configuration, serviceManager, middlewaresBuilder, responseModifierFactory)
handlersNonTLS := routerManager.BuildHandlers(ctx, entryPoints, false)
handlersTLS := routerManager.BuildHandlers(ctx, entryPoints, true)

View file

@ -14,13 +14,14 @@ import (
// Router is a TCP router
type Router struct {
routingTable map[string]Handler
httpForwarder Handler
httpsForwarder Handler
httpHandler http.Handler
httpsHandler http.Handler
httpsTLSConfig *tls.Config
catchAllNoTLS Handler
routingTable map[string]Handler
httpForwarder Handler
httpsForwarder Handler
httpHandler http.Handler
httpsHandler http.Handler
httpsTLSConfig *tls.Config // default TLS config
catchAllNoTLS Handler
hostHTTPTLSConfig map[string]*tls.Config // TLS configs keyed by SNI
}
// ServeTCP forwards the connection to the right TCP/HTTP handler
@ -84,6 +85,15 @@ func (r *Router) AddRouteTLS(sniHost string, target Handler, config *tls.Config)
})
}
// AddRouteHTTPTLS defines a handler for a given sniHost and sets the matching tlsConfig
func (r *Router) AddRouteHTTPTLS(sniHost string, config *tls.Config) {
if r.hostHTTPTLSConfig == nil {
r.hostHTTPTLSConfig = map[string]*tls.Config{}
}
log.Debugf("adding route %s with minversion %d", sniHost, config.MinVersion)
r.hostHTTPTLSConfig[sniHost] = config
}
// AddCatchAllNoTLS defines the fallback tcp handler
func (r *Router) AddCatchAllNoTLS(handler Handler) {
r.catchAllNoTLS = handler
@ -116,6 +126,10 @@ func (r *Router) HTTPForwarder(handler Handler) {
// HTTPSForwarder sets the tcp handler that will forward the TLS connections to an http handler
func (r *Router) HTTPSForwarder(handler Handler) {
for sniHost, tlsConf := range r.hostHTTPTLSConfig {
r.AddRouteTLS(sniHost, handler, tlsConf)
}
r.httpsForwarder = &TLSHandler{
Next: handler,
Config: r.httpsTLSConfig,

View file

@ -67,14 +67,19 @@ func (m *Manager) UpdateConfigs(stores map[string]Store, configs map[string]TLS,
}
}
// Get gets the tls configuration to use for a given store / configuration
func (m *Manager) Get(storeName string, configName string) *tls.Config {
// Get gets the TLS configuration to use for a given store / configuration
func (m *Manager) Get(storeName string, configName string) (*tls.Config, error) {
m.lock.RLock()
defer m.lock.RUnlock()
config, ok := m.configs[configName]
if !ok && configName != "default" {
return nil, fmt.Errorf("unknown TLS options: %s", configName)
}
store := m.getStore(storeName)
tlsConfig, err := buildTLSConfig(m.configs[configName])
tlsConfig, err := buildTLSConfig(config)
if err != nil {
log.Error(err)
tlsConfig = &tls.Config{}
@ -106,7 +111,7 @@ func (m *Manager) Get(storeName string, configName string) *tls.Config {
log.WithoutContext().Debugf("Serving default certificate for request: %q", domainToCheck)
return store.DefaultCertificate, nil
}
return tlsConfig
return tlsConfig, nil
}
func (m *Manager) getStore(storeName string) *CertificateStore {