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

View file

@ -109,6 +109,7 @@ labels:
- "traefik.HTTP.Routers.Router0.Rule=foobar" - "traefik.HTTP.Routers.Router0.Rule=foobar"
- "traefik.HTTP.Routers.Router0.Service=foobar" - "traefik.HTTP.Routers.Router0.Service=foobar"
- "traefik.HTTP.Routers.Router0.TLS=true" - "traefik.HTTP.Routers.Router0.TLS=true"
- "traefik.HTTP.Routers.Router0.TLS.options=foo"
- "traefik.HTTP.Routers.Router1.EntryPoints=foobar, fiibar" - "traefik.HTTP.Routers.Router1.EntryPoints=foobar, fiibar"
- "traefik.HTTP.Routers.Router1.Middlewares=foobar, fiibar" - "traefik.HTTP.Routers.Router1.Middlewares=foobar, fiibar"
- "traefik.HTTP.Routers.Router1.Priority=42" - "traefik.HTTP.Routers.Router1.Priority=42"
@ -143,9 +144,11 @@ labels:
- "traefik.TCP.Routers.Router0.EntryPoints=foobar, fiibar" - "traefik.TCP.Routers.Router0.EntryPoints=foobar, fiibar"
- "traefik.TCP.Routers.Router0.Service=foobar" - "traefik.TCP.Routers.Router0.Service=foobar"
- "traefik.TCP.Routers.Router0.TLS.Passthrough=false" - "traefik.TCP.Routers.Router0.TLS.Passthrough=false"
- "traefik.TCP.Routers.Router0.TLS.options=bar"
- "traefik.TCP.Routers.Router1.Rule=foobar" - "traefik.TCP.Routers.Router1.Rule=foobar"
- "traefik.TCP.Routers.Router1.EntryPoints=foobar, fiibar" - "traefik.TCP.Routers.Router1.EntryPoints=foobar, fiibar"
- "traefik.TCP.Routers.Router1.Service=foobar" - "traefik.TCP.Routers.Router1.Service=foobar"
- "traefik.TCP.Routers.Router1.TLS.Passthrough=false" - "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.Service0.LoadBalancer.server.Port=42"
- "traefik.TCP.Services.Service1.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 ### 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). 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" ??? 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" !!! 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 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" !!! note "Passthrough"
On TCP routers, you can configure a passthrough option so that Traefik doesn't terminate the TLS connection. 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" 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 ## Configuring TCP Routers
### General ### General
@ -269,8 +295,10 @@ Services are the target for the router.
### TLS ### 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). #### General
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".
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" ??? example "Configuring TLS Termination"
@ -296,4 +324,28 @@ By default, Traefik will terminate the SSL connections (meaning that it will sen
!!! note "TLS & ACME" !!! 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 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 checkNewVersion = false
sendAnonymousUsage = false sendAnonymousUsage = false
[api]
entrypoint="api"
[log] [log]
level = "DEBUG" level = "DEBUG"
[entryPoints] [entryPoints]
[entryPoints.web] [entryPoints.web]
address = ":80" address = ":8081"
[entryPoints.api]
address = ":8080"
[providers] [providers]
[providers.file] [providers.file]

View file

@ -38,4 +38,3 @@ level = "DEBUG"
[http.services.whoami.loadbalancer] [http.services.whoami.loadbalancer]
[[http.services.whoami.loadbalancer.servers]] [[http.services.whoami.loadbalancer.servers]]
url = "http://localhost:8085" 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) 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 // TestWithSNIStrictNotMatchedRequest involves a client sending a SNI hostname of
// "snitest.org", which does not match the CN of 'snitest.com.crt'. The test // "snitest.org", which does not match the CN of 'snitest.com.crt'. The test
// verifies that traefik closes the connection. // verifies that traefik closes the connection.

View file

@ -28,34 +28,38 @@ func (s *RateLimitSuite) TestSimpleConfiguration(c *check.C) {
}{s.ServerIP}) }{s.ServerIP})
defer os.Remove(file) defer os.Remove(file)
cmd, _ := s.cmdTraefik(withConfigFile(file)) cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start() err := cmd.Start()
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() 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) 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) 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) c.Assert(err, checker.IsNil)
// sleep for 4 seconds to be certain the configured time period has elapsed // sleep for 4 seconds to be certain the configured time period has elapsed
// then test another request and verify a 200 status code // then test another request and verify a 200 status code
time.Sleep(4 * time.Second) 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) c.Assert(err, checker.IsNil)
// continue requests at 3 second intervals to test the other rate limit time period // continue requests at 3 second intervals to test the other rate limit time period
time.Sleep(3 * time.Second) 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) c.Assert(err, checker.IsNil)
time.Sleep(3 * time.Second) 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) c.Assert(err, checker.IsNil)
time.Sleep(3 * time.Second) 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) c.Assert(err, checker.IsNil)
} }

View file

@ -70,6 +70,36 @@ func (s *TCPSuite) TestMixed(c *check.C) {
c.Assert(err, checker.IsNil) 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) { func (s *TCPSuite) TestNonTLSFallback(c *check.C) {
file := s.adaptFile(c, "fixtures/tcp/non-tls-fallback.toml", struct{}{}) file := s.adaptFile(c, "fixtures/tcp/non-tls-fallback.toml", struct{}{})
defer os.Remove(file) defer os.Remove(file)
@ -191,11 +221,21 @@ func welcome(addr string) (string, error) {
} }
func guessWho(addr, serverName string, tlsCall bool) (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 conn net.Conn
var err error var err error
if tlsCall { 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 { } else {
tcpAddr, err2 := net.ResolveTCPAddr("tcp", addr) tcpAddr, err2 := net.ResolveTCPAddr("tcp", addr)
if err2 != nil { if err2 != nil {

View file

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

View file

@ -22,7 +22,9 @@ type Router struct {
} }
// RouterTLSConfig holds the TLS configuration for a router // 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. // TCPRouter holds the router configuration.
type TCPRouter struct { type TCPRouter struct {
@ -34,7 +36,8 @@ type TCPRouter struct {
// RouterTCPTLSConfig holds the TLS configuration for a router // RouterTCPTLSConfig holds the TLS configuration for a router
type RouterTCPTLSConfig struct { 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. // 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.entrypoints": "foobar, fiibar",
"traefik.tcp.routers.Router0.service": "foobar", "traefik.tcp.routers.Router0.service": "foobar",
"traefik.tcp.routers.Router0.tls.passthrough": "false", "traefik.tcp.routers.Router0.tls.passthrough": "false",
"traefik.tcp.routers.Router0.tls.options": "foo",
"traefik.tcp.routers.Router1.rule": "foobar", "traefik.tcp.routers.Router1.rule": "foobar",
"traefik.tcp.routers.Router1.entrypoints": "foobar, fiibar", "traefik.tcp.routers.Router1.entrypoints": "foobar, fiibar",
"traefik.tcp.routers.Router1.service": "foobar", "traefik.tcp.routers.Router1.service": "foobar",
"traefik.tcp.routers.Router1.tls.options": "foo",
"traefik.tcp.routers.Router1.tls.passthrough": "false", "traefik.tcp.routers.Router1.tls.passthrough": "false",
"traefik.tcp.services.Service0.loadbalancer.server.Port": "42", "traefik.tcp.services.Service0.loadbalancer.server.Port": "42",
"traefik.tcp.services.Service1.loadbalancer.server.Port": "42", "traefik.tcp.services.Service1.loadbalancer.server.Port": "42",
@ -185,6 +187,7 @@ func TestDecodeConfiguration(t *testing.T) {
Rule: "foobar", Rule: "foobar",
TLS: &config.RouterTCPTLSConfig{ TLS: &config.RouterTCPTLSConfig{
Passthrough: false, Passthrough: false,
Options: "foo",
}, },
}, },
"Router1": { "Router1": {
@ -196,6 +199,7 @@ func TestDecodeConfiguration(t *testing.T) {
Rule: "foobar", Rule: "foobar",
TLS: &config.RouterTCPTLSConfig{ TLS: &config.RouterTCPTLSConfig{
Passthrough: false, Passthrough: false,
Options: "foo",
}, },
}, },
}, },
@ -580,6 +584,7 @@ func TestEncodeConfiguration(t *testing.T) {
Rule: "foobar", Rule: "foobar",
TLS: &config.RouterTCPTLSConfig{ TLS: &config.RouterTCPTLSConfig{
Passthrough: false, Passthrough: false,
Options: "foo",
}, },
}, },
"Router1": { "Router1": {
@ -591,6 +596,7 @@ func TestEncodeConfiguration(t *testing.T) {
Rule: "foobar", Rule: "foobar",
TLS: &config.RouterTCPTLSConfig{ TLS: &config.RouterTCPTLSConfig{
Passthrough: false, Passthrough: false,
Options: "foo",
}, },
}, },
}, },
@ -1110,10 +1116,12 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.TCP.Routers.Router0.EntryPoints": "foobar, fiibar", "traefik.TCP.Routers.Router0.EntryPoints": "foobar, fiibar",
"traefik.TCP.Routers.Router0.Service": "foobar", "traefik.TCP.Routers.Router0.Service": "foobar",
"traefik.TCP.Routers.Router0.TLS.Passthrough": "false", "traefik.TCP.Routers.Router0.TLS.Passthrough": "false",
"traefik.TCP.Routers.Router0.TLS.Options": "foo",
"traefik.TCP.Routers.Router1.Rule": "foobar", "traefik.TCP.Routers.Router1.Rule": "foobar",
"traefik.TCP.Routers.Router1.EntryPoints": "foobar, fiibar", "traefik.TCP.Routers.Router1.EntryPoints": "foobar, fiibar",
"traefik.TCP.Routers.Router1.Service": "foobar", "traefik.TCP.Routers.Router1.Service": "foobar",
"traefik.TCP.Routers.Router1.TLS.Passthrough": "false", "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.Service0.LoadBalancer.server.Port": "42",
"traefik.TCP.Services.Service1.LoadBalancer.server.Port": "42", "traefik.TCP.Services.Service1.LoadBalancer.server.Port": "42",
} }

View file

@ -1,6 +1,7 @@
package config package config
import ( import (
"context"
"sort" "sort"
"strings" "strings"
"sync" "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 // RouterInfo holds information about a currently running HTTP router
type RouterInfo struct { type RouterInfo struct {
*Router // dynamic configuration *Router // dynamic configuration

View file

@ -1,6 +1,7 @@
package config_test package config_test
import ( import (
"context"
"testing" "testing"
"github.com/containous/traefik/pkg/config" "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", Name: "Test",
Labels: map[string]string{ Labels: map[string]string{
"traefik.tcp.routers.foo.rule": "HostSNI(`foo.bar`)", "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", "traefik.tcp.services.foo.loadbalancer.server.port": "8080",
}, },
NetworkSettings: networkSettings{ NetworkSettings: networkSettings{
@ -2186,7 +2186,9 @@ func Test_buildConfiguration(t *testing.T) {
"foo": { "foo": {
Service: "foo", Service: "foo",
Rule: "HostSNI(`foo.bar`)", Rule: "HostSNI(`foo.bar`)",
TLS: &config.RouterTCPTLSConfig{}, TLS: &config.RouterTCPTLSConfig{
Options: "foo",
},
}, },
}, },
Services: map[string]*config.TCPService{ Services: map[string]*config.TCPService{

View file

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

View file

@ -2,7 +2,6 @@ package router
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"github.com/containous/alice" "github.com/containous/alice"
@ -23,33 +22,42 @@ const (
) )
// NewManager Creates a new Manager // NewManager Creates a new Manager
func NewManager(routers map[string]*config.RouterInfo, func NewManager(conf *config.RuntimeConfiguration,
serviceManager *service.Manager, middlewaresBuilder *middleware.Builder, modifierBuilder *responsemodifiers.Builder, serviceManager *service.Manager,
middlewaresBuilder *middleware.Builder,
modifierBuilder *responsemodifiers.Builder,
) *Manager { ) *Manager {
return &Manager{ return &Manager{
routerHandlers: make(map[string]http.Handler), routerHandlers: make(map[string]http.Handler),
configs: routers,
serviceManager: serviceManager, serviceManager: serviceManager,
middlewaresBuilder: middlewaresBuilder, middlewaresBuilder: middlewaresBuilder,
modifierBuilder: modifierBuilder, modifierBuilder: modifierBuilder,
conf: conf,
} }
} }
// Manager A route/router manager // Manager A route/router manager
type Manager struct { type Manager struct {
routerHandlers map[string]http.Handler routerHandlers map[string]http.Handler
configs map[string]*config.RouterInfo
serviceManager *service.Manager serviceManager *service.Manager
middlewaresBuilder *middleware.Builder middlewaresBuilder *middleware.Builder
modifierBuilder *responsemodifiers.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 // BuildHandlers Builds handler for all entry points
func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, tls bool) map[string]http.Handler { 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) entryPointHandlers := make(map[string]http.Handler)
for entryPointName, routers := range entryPointsRouters {
for entryPointName, routers := range m.getHTTPRouters(rootCtx, entryPoints, tls) {
entryPointName := entryPointName entryPointName := entryPointName
ctx := log.With(rootCtx, log.Str(log.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 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) { func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.RouterInfo) (http.Handler, error) {
router, err := rules.NewRouter() router, err := rules.NewRouter()
if err != nil { 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)) ctxRouter := log.With(internal.AddProviderInContext(ctx, routerName), log.Str(log.RouterName, routerName))
logger := log.FromContext(ctxRouter) logger := log.FromContext(ctxRouter)
handler, err := m.buildRouterHandler(ctxRouter, routerName) handler, err := m.buildRouterHandler(ctxRouter, routerName, routerConfig)
if err != nil { if err != nil {
routerConfig.Err = err.Error() routerConfig.Err = err.Error()
logger.Error(err) logger.Error(err)
@ -149,17 +118,12 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
return chain.Then(router) 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 { if handler, ok := m.routerHandlers[routerName]; ok {
return handler, nil return handler, nil
} }
configRouter, ok := m.configs[routerName] handler, err := m.buildHTTPHandler(ctx, routerConfig, routerName)
if !ok {
return nil, fmt.Errorf("no configuration for %s", routerName)
}
handler, err := m.buildHTTPHandler(ctx, configRouter, routerName)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

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

View file

@ -2,7 +2,6 @@ package tcp
import ( import (
"context" "context"
"crypto/tls"
"fmt" "fmt"
"net/http" "net/http"
@ -12,6 +11,7 @@ import (
"github.com/containous/traefik/pkg/server/internal" "github.com/containous/traefik/pkg/server/internal"
tcpservice "github.com/containous/traefik/pkg/server/service/tcp" tcpservice "github.com/containous/traefik/pkg/server/service/tcp"
"github.com/containous/traefik/pkg/tcp" "github.com/containous/traefik/pkg/tcp"
"github.com/containous/traefik/pkg/tls"
) )
// NewManager Creates a new Manager // NewManager Creates a new Manager
@ -19,29 +19,46 @@ func NewManager(conf *config.RuntimeConfiguration,
serviceManager *tcpservice.Manager, serviceManager *tcpservice.Manager,
httpHandlers map[string]http.Handler, httpHandlers map[string]http.Handler,
httpsHandlers map[string]http.Handler, httpsHandlers map[string]http.Handler,
tlsConfig *tls.Config, tlsManager *tls.Manager,
) *Manager { ) *Manager {
return &Manager{ return &Manager{
configs: conf.TCPRouters,
serviceManager: serviceManager, serviceManager: serviceManager,
httpHandlers: httpHandlers, httpHandlers: httpHandlers,
httpsHandlers: httpsHandlers, httpsHandlers: httpsHandlers,
tlsConfig: tlsConfig, tlsManager: tlsManager,
conf: conf,
} }
} }
// Manager is a route/router manager // Manager is a route/router manager
type Manager struct { type Manager struct {
configs map[string]*config.TCPRouterInfo
serviceManager *tcpservice.Manager serviceManager *tcpservice.Manager
httpHandlers map[string]http.Handler httpHandlers map[string]http.Handler
httpsHandlers 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 // BuildHandlers builds the handlers for the given entrypoints
func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) map[string]*tcp.Router { 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) entryPointHandlers := make(map[string]*tcp.Router)
for _, entryPointName := range entryPoints { 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)) 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 { if err != nil {
log.FromContext(ctx).Error(err) log.FromContext(ctx).Error(err)
continue continue
@ -61,10 +78,50 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m
return entryPointHandlers 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 := &tcp.Router{}
router.HTTPHandler(handlerHTTP) 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 { for routerName, routerConfig := range configs {
ctxRouter := log.With(internal.AddProviderInContext(ctx, routerName), log.Str(log.RouterName, routerName)) 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 { if routerConfig.TLS.Passthrough {
router.AddRoute(domain, handler) router.AddRoute(domain, handler)
} else { } 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 == "*": case domain == "*":
router.AddCatchAllNoTLS(handler) router.AddCatchAllNoTLS(handler)
@ -104,39 +173,3 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
return router, nil 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/config"
"github.com/containous/traefik/pkg/server/service/tcp" "github.com/containous/traefik/pkg/server/service/tcp"
"github.com/containous/traefik/pkg/tls"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -42,6 +43,10 @@ func TestRuntimeConfiguration(t *testing.T) {
EntryPoints: []string{"web"}, EntryPoints: []string{"web"},
Service: "foo-service", Service: "foo-service",
Rule: "HostSNI(`bar.foo`)", Rule: "HostSNI(`bar.foo`)",
TLS: &config.RouterTCPTLSConfig{
Passthrough: false,
Options: "foo",
},
}, },
}, },
"bar": { "bar": {
@ -50,6 +55,10 @@ func TestRuntimeConfiguration(t *testing.T) {
EntryPoints: []string{"web"}, EntryPoints: []string{"web"},
Service: "foo-service", Service: "foo-service",
Rule: "HostSNI(`foo.bar`)", Rule: "HostSNI(`foo.bar`)",
TLS: &config.RouterTCPTLSConfig{
Passthrough: false,
Options: "bar",
},
}, },
}, },
}, },
@ -191,8 +200,21 @@ func TestRuntimeConfiguration(t *testing.T) {
TCPRouters: test.routerConfig, TCPRouters: test.routerConfig,
} }
serviceManager := tcp.NewManager(conf) 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, routerManager := NewManager(conf, serviceManager,
nil, nil, nil) nil, nil, tlsManager)
_ = routerManager.BuildHandlers(context.Background(), entryPoints) _ = routerManager.BuildHandlers(context.Background(), entryPoints)

View file

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

View file

@ -14,13 +14,14 @@ import (
// Router is a TCP router // Router is a TCP router
type Router struct { type Router struct {
routingTable map[string]Handler routingTable map[string]Handler
httpForwarder Handler httpForwarder Handler
httpsForwarder Handler httpsForwarder Handler
httpHandler http.Handler httpHandler http.Handler
httpsHandler http.Handler httpsHandler http.Handler
httpsTLSConfig *tls.Config httpsTLSConfig *tls.Config // default TLS config
catchAllNoTLS Handler catchAllNoTLS Handler
hostHTTPTLSConfig map[string]*tls.Config // TLS configs keyed by SNI
} }
// ServeTCP forwards the connection to the right TCP/HTTP handler // 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 // AddCatchAllNoTLS defines the fallback tcp handler
func (r *Router) AddCatchAllNoTLS(handler Handler) { func (r *Router) AddCatchAllNoTLS(handler Handler) {
r.catchAllNoTLS = 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 // HTTPSForwarder sets the tcp handler that will forward the TLS connections to an http handler
func (r *Router) HTTPSForwarder(handler Handler) { func (r *Router) HTTPSForwarder(handler Handler) {
for sniHost, tlsConf := range r.hostHTTPTLSConfig {
r.AddRouteTLS(sniHost, handler, tlsConf)
}
r.httpsForwarder = &TLSHandler{ r.httpsForwarder = &TLSHandler{
Next: handler, Next: handler,
Config: r.httpsTLSConfig, 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 // Get gets the TLS configuration to use for a given store / configuration
func (m *Manager) Get(storeName string, configName string) *tls.Config { func (m *Manager) Get(storeName string, configName string) (*tls.Config, error) {
m.lock.RLock() m.lock.RLock()
defer m.lock.RUnlock() 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) store := m.getStore(storeName)
tlsConfig, err := buildTLSConfig(m.configs[configName]) tlsConfig, err := buildTLSConfig(config)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
tlsConfig = &tls.Config{} 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) log.WithoutContext().Debugf("Serving default certificate for request: %q", domainToCheck)
return store.DefaultCertificate, nil return store.DefaultCertificate, nil
} }
return tlsConfig return tlsConfig, nil
} }
func (m *Manager) getStore(storeName string) *CertificateStore { func (m *Manager) getStore(storeName string) *CertificateStore {