Entry point redirection and default routers configuration

Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
This commit is contained in:
Traefiker Bot 2020-03-05 12:46:05 +01:00 committed by GitHub
parent 93a7af270f
commit a6040c623b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 1016 additions and 126 deletions

View file

@ -191,7 +191,25 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry) managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry)
routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder) routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder)
watcher := server.NewConfigurationWatcher(routinesPool, providerAggregator, time.Duration(staticConfiguration.Providers.ProvidersThrottleDuration)) var eps []string
for name, cfg := range staticConfiguration.EntryPoints {
protocol, err := cfg.GetProtocol()
if err != nil {
// Should never happen because Traefik should not start if protocol is invalid.
log.WithoutContext().Errorf("Invalid protocol: %v", err)
}
if protocol != "udp" {
eps = append(eps, name)
}
}
watcher := server.NewConfigurationWatcher(
routinesPool,
providerAggregator,
time.Duration(staticConfiguration.Providers.ProvidersThrottleDuration),
eps,
)
watcher.AddListener(func(conf dynamic.Configuration) { watcher.AddListener(func(conf dynamic.Configuration) {
ctx := context.Background() ctx := context.Background()

View file

@ -387,10 +387,8 @@ To apply a redirection, one of the redirect middlewares, [RedirectRegex](../midd
- match: HostRegexp(`{any:.+}`) - match: HostRegexp(`{any:.+}`)
kind: Rule kind: Rule
services: services:
# any service in the namespace # the noop service will be never called
# the service will be never called - name: noop@internal
- name: noop
port: 80
middlewares: middlewares:
- name: https_redirect - name: https_redirect
# if the Middleware has distinct namespace # if the Middleware has distinct namespace
@ -431,13 +429,8 @@ To apply a redirection, one of the redirect middlewares, [RedirectRegex](../midd
entryPoints = ["web"] entryPoints = ["web"]
middlewares = ["https_redirect"] middlewares = ["https_redirect"]
rule = "HostRegexp(`{any:.+}`)" rule = "HostRegexp(`{any:.+}`)"
service = "noop" # the noop service will be never called
service = "noop@internal"
[http.services]
# noop service, the URL will be never called
[http.services.noop.loadBalancer]
[[http.services.noop.loadBalancer.servers]]
url = "http://192.168.0.1:1337"
[http.middlewares] [http.middlewares]
[http.middlewares.https_redirect.redirectScheme] [http.middlewares.https_redirect.redirectScheme]
@ -472,14 +465,8 @@ To apply a redirection, one of the redirect middlewares, [RedirectRegex](../midd
middlewares: middlewares:
- https_redirect - https_redirect
rule: "HostRegexp(`{any:.+}`)" rule: "HostRegexp(`{any:.+}`)"
service: noop # the noop service will be never called
service: noop@internal
services:
# noop service, the URL will be never called
noop:
loadBalancer:
servers:
- url: http://192.168.0.1:1337
middlewares: middlewares:
https_redirect: https_redirect:

View file

@ -99,6 +99,36 @@ Trust all forwarded headers. (Default: ```false```)
`--entrypoints.<name>.forwardedheaders.trustedips`: `--entrypoints.<name>.forwardedheaders.trustedips`:
Trust only forwarded headers from selected IPs. Trust only forwarded headers from selected IPs.
`--entrypoints.<name>.http`:
HTTP configuration.
`--entrypoints.<name>.http.middlewares`:
Default middlewares for the routers linked to the entry point.
`--entrypoints.<name>.http.redirections.entrypoint.scheme`:
Scheme used for the redirection. Defaults to https. (Default: ```https```)
`--entrypoints.<name>.http.redirections.entrypoint.to`:
Targeted entry point of the redirection.
`--entrypoints.<name>.http.tls`:
Default TLS configuration for the routers linked to the entry point. (Default: ```false```)
`--entrypoints.<name>.http.tls.certresolver`:
Default certificate resolver for the routers linked to the entry point.
`--entrypoints.<name>.http.tls.domains`:
Default TLS domains for the routers linked to the entry point.
`--entrypoints.<name>.http.tls.domains[n].main`:
Default subject name.
`--entrypoints.<name>.http.tls.domains[n].sans`:
Subject alternative names.
`--entrypoints.<name>.http.tls.options`:
Default TLS options for the routers linked to the entry point.
`--entrypoints.<name>.proxyprotocol`: `--entrypoints.<name>.proxyprotocol`:
Proxy-Protocol configuration. (Default: ```false```) Proxy-Protocol configuration. (Default: ```false```)

View file

@ -99,6 +99,36 @@ Trust all forwarded headers. (Default: ```false```)
`TRAEFIK_ENTRYPOINTS_<NAME>_FORWARDEDHEADERS_TRUSTEDIPS`: `TRAEFIK_ENTRYPOINTS_<NAME>_FORWARDEDHEADERS_TRUSTEDIPS`:
Trust only forwarded headers from selected IPs. Trust only forwarded headers from selected IPs.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP`:
HTTP configuration.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_MIDDLEWARES`:
Default middlewares for the routers linked to the entry point.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_REDIRECTIONS_ENTRYPOINT_SCHEME`:
Scheme used for the redirection. Defaults to https. (Default: ```https```)
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_REDIRECTIONS_ENTRYPOINT_TO`:
Targeted entry point of the redirection.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS`:
Default TLS configuration for the routers linked to the entry point. (Default: ```false```)
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS_CERTRESOLVER`:
Default certificate resolver for the routers linked to the entry point.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS_DOMAINS`:
Default TLS domains for the routers linked to the entry point.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS_DOMAINS[n]_MAIN`:
Default subject name.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS_DOMAINS[n]_SANS`:
Subject alternative names.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS_OPTIONS`:
Default TLS options for the routers linked to the entry point.
`TRAEFIK_ENTRYPOINTS_<NAME>_PROXYPROTOCOL`: `TRAEFIK_ENTRYPOINTS_<NAME>_PROXYPROTOCOL`:
Proxy-Protocol configuration. (Default: ```false```) Proxy-Protocol configuration. (Default: ```false```)

View file

@ -28,6 +28,23 @@
[entryPoints.EntryPoint0.forwardedHeaders] [entryPoints.EntryPoint0.forwardedHeaders]
insecure = true insecure = true
trustedIPs = ["foobar", "foobar"] trustedIPs = ["foobar", "foobar"]
[entryPoints.EntryPoint0.http]
middlewares = ["foobar", "foobar"]
[entryPoints.EntryPoint0.http.redirections]
[entryPoints.EntryPoint0.http.redirections.entryPoint]
to = "foobar"
scheme = "foobar"
[entryPoints.EntryPoint0.http.tls]
options = "foobar"
certResolver = "foobar"
[[entryPoints.EntryPoint0.http.tls.domains]]
main = "foobar"
sans = ["foobar", "foobar"]
[[entryPoints.EntryPoint0.http.tls.domains]]
main = "foobar"
sans = ["foobar", "foobar"]
[providers] [providers]
providersThrottleDuration = 42 providersThrottleDuration = 42

View file

@ -32,6 +32,26 @@ entryPoints:
trustedIPs: trustedIPs:
- foobar - foobar
- foobar - foobar
http:
redirections:
entryPoint:
to: foobar
scheme: foobar
middlewares:
- foobar
- foobar
tls:
options: foobar
certResolver: foobar
domains:
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
providers: providers:
providersThrottleDuration: 42 providersThrottleDuration: 42
docker: docker:

View file

@ -529,3 +529,211 @@ If the Proxy Protocol header is passed, then the version is determined automatic
When queuing Traefik behind another load-balancer, make sure to configure Proxy Protocol on both sides. When queuing Traefik behind another load-balancer, make sure to configure Proxy Protocol on both sides.
Not doing so could introduce a security risk in your system (enabling request forgery). Not doing so could introduce a security risk in your system (enabling request forgery).
## HTTP Options
This whole section is dedicated to options, keyed by entry point, that will apply only to HTTP routing.
### Redirection
??? example "HTTPS redirection (80 to 443)"
```toml tab="File (TOML)"
[entryPoints.web]
address = ":80"
[entryPoints.web.http]
[entryPoints.web.http.redirections]
[entryPoints.web.http.redirections.entryPoint]
to = "websecure"
scheme = "https"
[entryPoints.websecure]
address = ":443"
```
```yaml tab="File (YAML)"
entryPoints:
web:
address: :80
http:
redirections:
entryPoint:
to: websecure
https: true
websecure:
address: :443
```
```bash tab="CLI"
--entrypoints.web.address=:80
--entrypoints.web.http.redirections.entryPoint.to=websecure
--entrypoints.web.http.redirections.entryPoint.https=true
--entrypoints.websecure.address=:443
```
#### `entryPoint`
This section is a convenience to enable (permanent) redirecting of all incoming requests on an entry point (e.g. port `80`) to another entry point (e.g. port `443`).
??? info "`entryPoint.to`"
_Required_
The target entry point.
```toml tab="File (TOML)"
[entryPoints.foo]
# ...
[entryPoints.foo.http.redirections]
[entryPoints.foo.http.redirections.entryPoint]
to = "bar"
```
```yaml tab="File (YAML)"
entryPoints:
foo:
# ...
http:
redirections:
entryPoint:
to: bar
```
```bash tab="CLI"
--entrypoints.foo.http.redirections.entryPoint.to=websecure
```
??? info "`entryPoint.scheme`"
_Optional, Default="http"_
The redirection target scheme.
```toml tab="File (TOML)"
[entryPoints.foo]
# ...
[entryPoints.foo.http.redirections]
[entryPoints.foo.http.redirections.entryPoint]
# ...
scheme = "https"
```
```yaml tab="File (YAML)"
entryPoints:
foo:
# ...
http:
redirections:
entryPoint:
# ...
scheme: https
```
```bash tab="CLI"
--entrypoints.foo.http.redirections.entryPoint.scheme=https
```
### Middlewares
The list of middlewares that are prepended by default to the list of middlewares of each router associated to the named entry point.
```toml tab="File (TOML)"
[entryPoints.websecure]
address = ":443"
[entryPoints.websecure.http]
middlewares = ["auth@file", "strip@file"]
```
```yaml tab="File (YAML)"
entryPoints:
websecure:
address: ':443'
http:
middlewares:
- auth@file
- strip@file
```
```bash tab="CLI"
entrypoints.websecure.address=:443
entrypoints.websecure.http.middlewares=auth@file,strip@file
```
### TLS
This section is about the default TLS configuration applied to all routers associated with the named entry point.
If a TLS section (i.e. any of its fields) is user-defined, then the default configuration does not apply at all.
The TLS section is the same as the [TLS section on HTTP routers](./routers/index.md#tls).
```toml tab="File (TOML)"
[entryPoints.websecure]
address = ":443"
[entryPoints.websecure.http.tls]
options = "foobar"
certResolver = "leresolver"
[[entryPoints.websecure.http.tls.domains]]
main = "example.com"
sans = ["foo.example.com", "bar.example.com"]
[[entryPoints.websecure.http.tls.domains]]
main = "test.com"
sans = ["foo.test.com", "bar.test.com"]
```
```yaml tab="File (YAML)"
entryPoints:
websecure:
address: ':443'
http:
tls:
options: foobar
certResolver: leresolver
domains:
- main: example.com
sans:
- foo.example.com
- bar.example.com
- main: test.com
sans:
- foo.test.com
- bar.test.com
```
```bash tab="CLI"
entrypoints.websecure.address=:443
entrypoints.websecure.http.tls.options=foobar
entrypoints.websecure.http.tls.certResolver=leresolver
entrypoints.websecure.http.tls.domains[0].main=example.com
entrypoints.websecure.http.tls.domains[0].sans=foo.example.com,bar.example.com
entrypoints.websecure.http.tls.domains[1].main=test.com
entrypoints.websecure.http.tls.domains[1].sans=foo.test.com,bar.test.com
```
??? example "Let's Encrypt"
```toml tab="File (TOML)"
[entryPoints.websecure]
address = ":443"
[entryPoints.websecure.http.tls]
certResolver = "leresolver"
```
```yaml tab="File (YAML)"
entryPoints:
websecure:
address: ':443'
http:
tls:
certResolver: leresolver
```
```bash tab="CLI"
entrypoints.websecure.address=:443
entrypoints.websecure.http.tls.certResolver=leresolver
```

View file

@ -76,7 +76,7 @@ func (s *DockerComposeSuite) TestComposeScale(c *check.C) {
// check that we have only one service (not counting the internal ones) with n servers // check that we have only one service (not counting the internal ones) with n servers
services := rtconf.Services services := rtconf.Services
c.Assert(services, checker.HasLen, 3) c.Assert(services, checker.HasLen, 4)
for name, service := range services { for name, service := range services {
if strings.HasSuffix(name, "@internal") { if strings.HasSuffix(name, "@internal") {
continue continue

View file

@ -165,6 +165,9 @@
}, },
"status": "enabled" "status": "enabled"
}, },
"noop@internal": {
"status": "enabled"
},
"simplesvc@consul": { "simplesvc@consul": {
"loadBalancer": { "loadBalancer": {
"servers": [ "servers": [

View file

@ -98,10 +98,10 @@
"loadBalancer": { "loadBalancer": {
"servers": [ "servers": [
{ {
"url": "http://10.42.0.3:80" "url": "http://10.42.0.2:80"
}, },
{ {
"url": "http://10.42.0.5:80" "url": "http://10.42.0.3:80"
} }
], ],
"passHostHeader": true "passHostHeader": true
@ -111,18 +111,18 @@
"default-test-route-6b204d94623b3df4370c@kubernetescrd" "default-test-route-6b204d94623b3df4370c@kubernetescrd"
], ],
"serverStatus": { "serverStatus": {
"http://10.42.0.3:80": "UP", "http://10.42.0.2:80": "UP",
"http://10.42.0.5:80": "UP" "http://10.42.0.3:80": "UP"
} }
}, },
"default-test2-route-23c7f4c450289ee29016@kubernetescrd": { "default-test2-route-23c7f4c450289ee29016@kubernetescrd": {
"loadBalancer": { "loadBalancer": {
"servers": [ "servers": [
{ {
"url": "http://10.42.0.3:80" "url": "http://10.42.0.2:80"
}, },
{ {
"url": "http://10.42.0.5:80" "url": "http://10.42.0.3:80"
} }
], ],
"passHostHeader": true "passHostHeader": true
@ -132,26 +132,26 @@
"default-test2-route-23c7f4c450289ee29016@kubernetescrd" "default-test2-route-23c7f4c450289ee29016@kubernetescrd"
], ],
"serverStatus": { "serverStatus": {
"http://10.42.0.3:80": "UP", "http://10.42.0.2:80": "UP",
"http://10.42.0.5:80": "UP" "http://10.42.0.3:80": "UP"
} }
}, },
"default-whoami-80@kubernetescrd": { "default-whoami-80@kubernetescrd": {
"loadBalancer": { "loadBalancer": {
"servers": [ "servers": [
{ {
"url": "http://10.42.0.3:80" "url": "http://10.42.0.2:80"
}, },
{ {
"url": "http://10.42.0.5:80" "url": "http://10.42.0.3:80"
} }
], ],
"passHostHeader": true "passHostHeader": true
}, },
"status": "enabled", "status": "enabled",
"serverStatus": { "serverStatus": {
"http://10.42.0.3:80": "UP", "http://10.42.0.2:80": "UP",
"http://10.42.0.5:80": "UP" "http://10.42.0.3:80": "UP"
} }
}, },
"default-wrr1@kubernetescrd": { "default-wrr1@kubernetescrd": {
@ -171,6 +171,9 @@
"usedBy": [ "usedBy": [
"default-test3-route-7d0ac22d3d8db4b82618@kubernetescrd" "default-test3-route-7d0ac22d3d8db4b82618@kubernetescrd"
] ]
},
"noop@internal": {
"status": "enabled"
} }
}, },
"tcpRouters": { "tcpRouters": {
@ -199,7 +202,7 @@
"address": "10.42.0.4:8080" "address": "10.42.0.4:8080"
}, },
{ {
"address": "10.42.0.6:8080" "address": "10.42.0.8:8080"
} }
] ]
}, },
@ -226,10 +229,10 @@
"loadBalancer": { "loadBalancer": {
"servers": [ "servers": [
{ {
"address": "10.42.0.4:8090" "address": "10.42.0.10:8090"
}, },
{ {
"address": "10.42.0.6:8090" "address": "10.42.0.9:8090"
} }
] ]
}, },

View file

@ -165,6 +165,9 @@
}, },
"status": "enabled" "status": "enabled"
}, },
"noop@internal": {
"status": "enabled"
},
"simplesvc@etcd": { "simplesvc@etcd": {
"loadBalancer": { "loadBalancer": {
"servers": [ "servers": [

View file

@ -29,6 +29,10 @@
] ]
}, },
"test-ingress-default-whoami-test-whoami@kubernetes": { "test-ingress-default-whoami-test-whoami@kubernetes": {
"entryPoints": [
"web",
"traefik"
],
"service": "default-whoami-http", "service": "default-whoami-http",
"rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)", "rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)",
"status": "enabled", "status": "enabled",
@ -38,6 +42,10 @@
] ]
}, },
"test-ingress-https-default-whoami-test-https-whoami@kubernetes": { "test-ingress-https-default-whoami-test-https-whoami@kubernetes": {
"entryPoints": [
"web",
"traefik"
],
"service": "default-whoami-http", "service": "default-whoami-http",
"rule": "Host(`whoami.test.https`) \u0026\u0026 PathPrefix(`/whoami`)", "rule": "Host(`whoami.test.https`) \u0026\u0026 PathPrefix(`/whoami`)",
"tls": {}, "tls": {},
@ -107,6 +115,9 @@
"http://10.42.0.3:80": "UP", "http://10.42.0.3:80": "UP",
"http://10.42.0.5:80": "UP" "http://10.42.0.5:80": "UP"
} }
},
"noop@internal": {
"status": "enabled"
} }
} }
} }

View file

@ -165,6 +165,9 @@
}, },
"status": "enabled" "status": "enabled"
}, },
"noop@internal": {
"status": "enabled"
},
"simplesvc@redis": { "simplesvc@redis": {
"loadBalancer": { "loadBalancer": {
"servers": [ "servers": [

View file

@ -165,6 +165,9 @@
}, },
"status": "enabled" "status": "enabled"
}, },
"noop@internal": {
"status": "enabled"
},
"simplesvc@zookeeper": { "simplesvc@zookeeper": {
"loadBalancer": { "loadBalancer": {
"servers": [ "servers": [

View file

@ -1,4 +1,5 @@
{ {
"address": ":81", "address": ":81",
"http": {},
"name": "bar" "name": "bar"
} }

View file

@ -1,22 +1,27 @@
[ [
{ {
"address": ":14", "address": ":14",
"http": {},
"name": "ep14" "name": "ep14"
}, },
{ {
"address": ":15", "address": ":15",
"http": {},
"name": "ep15" "name": "ep15"
}, },
{ {
"address": ":16", "address": ":16",
"http": {},
"name": "ep16" "name": "ep16"
}, },
{ {
"address": ":17", "address": ":17",
"http": {},
"name": "ep17" "name": "ep17"
}, },
{ {
"address": ":18", "address": ":18",
"http": {},
"name": "ep18" "name": "ep18"
} }
] ]

View file

@ -1,6 +1,7 @@
[ [
{ {
"address": ":82", "address": ":82",
"http": {},
"name": "web2" "name": "web2"
} }
] ]

View file

@ -8,6 +8,7 @@
"192.168.1.4" "192.168.1.4"
] ]
}, },
"http": {},
"name": "web", "name": "web",
"proxyProtocol": { "proxyProtocol": {
"insecure": true, "insecure": true,
@ -37,6 +38,7 @@
"192.168.1.40" "192.168.1.40"
] ]
}, },
"http": {},
"name": "websecure", "name": "websecure",
"proxyProtocol": { "proxyProtocol": {
"insecure": true, "insecure": true,

View file

@ -11,8 +11,17 @@ import (
// HTTPConfiguration contains all the HTTP configuration parameters. // HTTPConfiguration contains all the HTTP configuration parameters.
type HTTPConfiguration struct { type HTTPConfiguration struct {
Routers map[string]*Router `json:"routers,omitempty" toml:"routers,omitempty" yaml:"routers,omitempty"` Routers map[string]*Router `json:"routers,omitempty" toml:"routers,omitempty" yaml:"routers,omitempty"`
Middlewares map[string]*Middleware `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty"`
Services map[string]*Service `json:"services,omitempty" toml:"services,omitempty" yaml:"services,omitempty"` Services map[string]*Service `json:"services,omitempty" toml:"services,omitempty" yaml:"services,omitempty"`
Middlewares map[string]*Middleware `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty"`
Models map[string]*Model `json:"models,omitempty" toml:"models,omitempty" yaml:"models,omitempty"`
}
// +k8s:deepcopy-gen=true
// Model is a set of default router's values.
type Model struct {
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty"`
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty"`
} }
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true

View file

@ -375,6 +375,21 @@ func (in *HTTPConfiguration) DeepCopyInto(out *HTTPConfiguration) {
(*out)[key] = outVal (*out)[key] = outVal
} }
} }
if in.Services != nil {
in, out := &in.Services, &out.Services
*out = make(map[string]*Service, len(*in))
for key, val := range *in {
var outVal *Service
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = new(Service)
(*in).DeepCopyInto(*out)
}
(*out)[key] = outVal
}
}
if in.Middlewares != nil { if in.Middlewares != nil {
in, out := &in.Middlewares, &out.Middlewares in, out := &in.Middlewares, &out.Middlewares
*out = make(map[string]*Middleware, len(*in)) *out = make(map[string]*Middleware, len(*in))
@ -390,16 +405,16 @@ func (in *HTTPConfiguration) DeepCopyInto(out *HTTPConfiguration) {
(*out)[key] = outVal (*out)[key] = outVal
} }
} }
if in.Services != nil { if in.Models != nil {
in, out := &in.Services, &out.Services in, out := &in.Models, &out.Models
*out = make(map[string]*Service, len(*in)) *out = make(map[string]*Model, len(*in))
for key, val := range *in { for key, val := range *in {
var outVal *Service var outVal *Model
if val == nil { if val == nil {
(*out)[key] = nil (*out)[key] = nil
} else { } else {
in, out := &val, &outVal in, out := &val, &outVal
*out = new(Service) *out = new(Model)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
(*out)[key] = outVal (*out)[key] = outVal
@ -760,6 +775,32 @@ func (in *Mirroring) DeepCopy() *Mirroring {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Model) DeepCopyInto(out *Model) {
*out = *in
if in.Middlewares != nil {
in, out := &in.Middlewares, &out.Middlewares
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(RouterTLSConfig)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Model.
func (in *Model) DeepCopy() *Model {
if in == nil {
return nil
}
out := new(Model)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PassTLSClientCert) DeepCopyInto(out *PassTLSClientCert) { func (in *PassTLSClientCert) DeepCopyInto(out *PassTLSClientCert) {
*out = *in *out = *in

View file

@ -21,14 +21,8 @@ func (c *Configuration) GetRoutersByEntryPoints(ctx context.Context, entryPoints
logger := log.FromContext(log.With(ctx, log.Str(log.RouterName, rtName))) logger := log.FromContext(log.With(ctx, log.Str(log.RouterName, rtName)))
eps := rt.EntryPoints
if len(eps) == 0 {
logger.Debugf("No entryPoint defined for this router, using the default one(s) instead: %+v", entryPoints)
eps = entryPoints
}
entryPointsCount := 0 entryPointsCount := 0
for _, entryPointName := range eps { for _, entryPointName := range rt.EntryPoints {
if !contains(entryPoints, entryPointName) { if !contains(entryPoints, entryPointName) {
rt.AddError(fmt.Errorf("entryPoint %q doesn't exist", entryPointName), false) rt.AddError(fmt.Errorf("entryPoint %q doesn't exist", entryPointName), false)
logger.WithField(log.EntryPointName, entryPointName). logger.WithField(log.EntryPointName, entryPointName).

View file

@ -3,6 +3,8 @@ package static
import ( import (
"fmt" "fmt"
"strings" "strings"
"github.com/containous/traefik/v2/pkg/types"
) )
// EntryPoint holds the entry point configuration. // EntryPoint holds the entry point configuration.
@ -11,6 +13,7 @@ type EntryPoint struct {
Transport *EntryPointsTransport `description:"Configures communication between clients and Traefik." json:"transport,omitempty" toml:"transport,omitempty" yaml:"transport,omitempty"` Transport *EntryPointsTransport `description:"Configures communication between clients and Traefik." json:"transport,omitempty" toml:"transport,omitempty" yaml:"transport,omitempty"`
ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty"` ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty"`
ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty"` ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty"`
HTTP HTTPConfig `description:"HTTP configuration." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty"`
} }
// GetAddress strips any potential protocol part of the address field of the // GetAddress strips any potential protocol part of the address field of the
@ -43,6 +46,36 @@ func (ep *EntryPoint) SetDefaults() {
ep.ForwardedHeaders = &ForwardedHeaders{} ep.ForwardedHeaders = &ForwardedHeaders{}
} }
// HTTPConfig is the HTTP configuration of an entry point.
type HTTPConfig struct {
Redirections *Redirections `description:"Set of redirection" json:"redirections,omitempty" toml:"redirections,omitempty" yaml:"redirections,omitempty"`
Middlewares []string `description:"Default middlewares for the routers linked to the entry point." json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty"`
TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty"`
}
// Redirections is a set of redirection for an entry point.
type Redirections struct {
EntryPoint *RedirectEntryPoint `description:"Set of redirection for an entry point." json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty"`
}
// RedirectEntryPoint is the definition of an entry point redirection.
type RedirectEntryPoint struct {
To string `description:"Targeted entry point of the redirection." json:"to,omitempty" toml:"to,omitempty" yaml:"to,omitempty"`
Scheme string `description:"Scheme used for the redirection. Defaults to https." json:"https,omitempty" toml:"https,omitempty" yaml:"https,omitempty"`
}
// SetDefaults sets the default values.
func (r *RedirectEntryPoint) SetDefaults() {
r.Scheme = "https"
}
// TLSConfig is the default TLS configuration for all the routers associated to the concerned entry point.
type TLSConfig struct {
Options string `description:"Default TLS options for the routers linked to the entry point." json:"options,omitempty" toml:"options,omitempty" yaml:"options,omitempty"`
CertResolver string `description:"Default certificate resolver for the routers linked to the entry point." json:"certResolver,omitempty" toml:"certResolver,omitempty" yaml:"certResolver,omitempty"`
Domains []types.Domain `description:"Default TLS domains for the routers linked to the entry point." json:"domains,omitempty" toml:"domains,omitempty" yaml:"domains,omitempty"`
}
// ForwardedHeaders Trust client forwarding headers. // ForwardedHeaders Trust client forwarding headers.
type ForwardedHeaders struct { type ForwardedHeaders struct {
Insecure bool `description:"Trust all forwarded headers." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` Insecure bool `description:"Trust all forwarded headers." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`

View file

@ -41,7 +41,8 @@
}, },
"services": { "services": {
"api": {}, "api": {},
"dashboard": {} "dashboard": {},
"noop": {}
} }
}, },
"tcp": {}, "tcp": {},

View file

@ -11,7 +11,8 @@
} }
}, },
"services": { "services": {
"api": {} "api": {},
"noop": {}
} }
}, },
"tcp": {}, "tcp": {},

View file

@ -2,7 +2,8 @@
"http": { "http": {
"services": { "services": {
"api": {}, "api": {},
"dashboard": {} "dashboard": {},
"noop": {}
} }
}, },
"tcp": {}, "tcp": {},

View file

@ -1,7 +1,8 @@
{ {
"http": { "http": {
"services": { "services": {
"api": {} "api": {},
"noop": {}
} }
}, },
"tcp": {}, "tcp": {},

View file

@ -74,6 +74,7 @@
"services": { "services": {
"api": {}, "api": {},
"dashboard": {}, "dashboard": {},
"noop": {},
"ping": {}, "ping": {},
"prometheus": {}, "prometheus": {},
"rest": {} "rest": {}

View file

@ -3,6 +3,7 @@
"services": { "services": {
"api": {}, "api": {},
"dashboard": {}, "dashboard": {},
"noop": {},
"ping": {}, "ping": {},
"prometheus": {}, "prometheus": {},
"rest": {} "rest": {}

View file

@ -0,0 +1,36 @@
{
"http": {
"services": {
"noop": {}
},
"models": {
"websecure": {
"middlewares": [
"test"
],
"tls": {
"options": "opt",
"certResolver": "le",
"domains": [
{
"main": "mainA",
"sans": [
"sanA1",
"sanA2"
]
},
{
"main": "mainB",
"sans": [
"sanB1",
"sanB2"
]
}
]
}
}
}
},
"tcp": {},
"tls": {}
}

View file

@ -1,6 +1,7 @@
{ {
"http": { "http": {
"services": { "services": {
"noop": {},
"ping": {} "ping": {}
} }
}, },

View file

@ -11,6 +11,7 @@
} }
}, },
"services": { "services": {
"noop": {},
"ping": {} "ping": {}
} }
}, },

View file

@ -1,6 +1,7 @@
{ {
"http": { "http": {
"services": { "services": {
"noop": {},
"prometheus": {} "prometheus": {}
} }
}, },

View file

@ -11,6 +11,7 @@
} }
}, },
"services": { "services": {
"noop": {},
"prometheus": {} "prometheus": {}
} }
}, },

View file

@ -0,0 +1,30 @@
{
"http": {
"routers": {
"web-to-websecure": {
"entryPoints": [
"web"
],
"middlewares": [
"redirect-web-to-websecure"
],
"service": "noop@internal",
"rule": "HostRegexp(`{host:.+}`)"
}
},
"middlewares": {
"redirect-web-to-websecure": {
"redirectScheme": {
"scheme": "https",
"port": "443",
"permanent": true
}
}
},
"services": {
"noop": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -11,6 +11,7 @@
} }
}, },
"services": { "services": {
"noop": {},
"rest": {} "rest": {}
} }
}, },

View file

@ -1,6 +1,7 @@
{ {
"http": { "http": {
"services": { "services": {
"noop": {},
"rest": {} "rest": {}
} }
}, },

View file

@ -1,10 +1,14 @@
package traefik package traefik
import ( import (
"context"
"fmt"
"math" "math"
"net"
"github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/static" "github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/provider" "github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/safe"
"github.com/containous/traefik/v2/pkg/tls" "github.com/containous/traefik/v2/pkg/tls"
@ -43,6 +47,7 @@ func (i *Provider) createConfiguration() *dynamic.Configuration {
Routers: make(map[string]*dynamic.Router), Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware), Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service), Services: make(map[string]*dynamic.Service),
Models: make(map[string]*dynamic.Model),
}, },
TCP: &dynamic.TCPConfiguration{ TCP: &dynamic.TCPConfiguration{
Routers: make(map[string]*dynamic.TCPRouter), Routers: make(map[string]*dynamic.TCPRouter),
@ -58,10 +63,73 @@ func (i *Provider) createConfiguration() *dynamic.Configuration {
i.pingConfiguration(cfg) i.pingConfiguration(cfg)
i.restConfiguration(cfg) i.restConfiguration(cfg)
i.prometheusConfiguration(cfg) i.prometheusConfiguration(cfg)
i.entryPointModels(cfg)
i.redirection(cfg)
cfg.HTTP.Services["noop"] = &dynamic.Service{}
return cfg return cfg
} }
func (i *Provider) redirection(cfg *dynamic.Configuration) {
for name, ep := range i.staticCfg.EntryPoints {
if ep.HTTP.Redirections == nil || ep.HTTP.Redirections.EntryPoint == nil {
continue
}
def := ep.HTTP.Redirections
rtName := provider.Normalize(name + "-to-" + def.EntryPoint.To)
mdName := "redirect-" + rtName
rt := &dynamic.Router{
Rule: "HostRegexp(`{host:.+}`)",
EntryPoints: []string{name},
Middlewares: []string{mdName},
Service: "noop@internal",
}
port, err := i.getEntryPointPort(name, def)
if err != nil {
log.FromContext(context.Background()).WithField(log.EntryPointName, name).Error(err)
continue
}
cfg.HTTP.Routers[rtName] = rt
rs := &dynamic.Middleware{
RedirectScheme: &dynamic.RedirectScheme{
Scheme: def.EntryPoint.Scheme,
Port: port,
Permanent: true,
},
}
cfg.HTTP.Middlewares[mdName] = rs
}
}
func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
for name, ep := range i.staticCfg.EntryPoints {
if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil {
continue
}
m := &dynamic.Model{
Middlewares: ep.HTTP.Middlewares,
}
if ep.HTTP.TLS != nil {
m.TLS = &dynamic.RouterTLSConfig{
Options: ep.HTTP.TLS.Options,
CertResolver: ep.HTTP.TLS.CertResolver,
Domains: ep.HTTP.TLS.Domains,
}
}
cfg.HTTP.Models[name] = m
}
}
func (i *Provider) apiConfiguration(cfg *dynamic.Configuration) { func (i *Provider) apiConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.API == nil { if i.staticCfg.API == nil {
return return
@ -163,3 +231,18 @@ func (i *Provider) prometheusConfiguration(cfg *dynamic.Configuration) {
cfg.HTTP.Services["prometheus"] = &dynamic.Service{} cfg.HTTP.Services["prometheus"] = &dynamic.Service{}
} }
func (i *Provider) getEntryPointPort(name string, def *static.Redirections) (string, error) {
dst, ok := i.staticCfg.EntryPoints[def.EntryPoint.To]
if !ok {
return "", fmt.Errorf("'to' entry point field references a non-existing entry point: %s", name)
}
_, port, err := net.SplitHostPort(dst.Address)
if err != nil {
return "", fmt.Errorf("invalid entry point %q address %q: %v",
name, i.staticCfg.EntryPoints[def.EntryPoint.To].Address, err)
}
return port, nil
}

View file

@ -167,6 +167,47 @@ func Test_createConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "models.json",
staticCfg: static.Configuration{
EntryPoints: map[string]*static.EntryPoint{
"websecure": {
HTTP: static.HTTPConfig{
Middlewares: []string{"test"},
TLS: &static.TLSConfig{
Options: "opt",
CertResolver: "le",
Domains: []types.Domain{
{Main: "mainA", SANs: []string{"sanA1", "sanA2"}},
{Main: "mainB", SANs: []string{"sanB1", "sanB2"}},
},
},
},
},
},
},
},
{
desc: "redirection.json",
staticCfg: static.Configuration{
EntryPoints: map[string]*static.EntryPoint{
"web": {
Address: ":80",
HTTP: static.HTTPConfig{
Redirections: &static.Redirections{
EntryPoint: &static.RedirectEntryPoint{
To: "websecure",
Scheme: "https",
},
},
},
},
"websecure": {
Address: ":443",
},
},
},
},
} }
for _, test := range testCases { for _, test := range testCases {

View file

@ -7,12 +7,13 @@ import (
"github.com/containous/traefik/v2/pkg/tls" "github.com/containous/traefik/v2/pkg/tls"
) )
func mergeConfiguration(configurations dynamic.Configurations) dynamic.Configuration { func mergeConfiguration(configurations dynamic.Configurations, entryPoints []string) dynamic.Configuration {
conf := dynamic.Configuration{ conf := dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router), Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware), Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service), Services: make(map[string]*dynamic.Service),
Models: make(map[string]*dynamic.Model),
}, },
TCP: &dynamic.TCPConfiguration{ TCP: &dynamic.TCPConfiguration{
Routers: make(map[string]*dynamic.TCPRouter), Routers: make(map[string]*dynamic.TCPRouter),
@ -33,6 +34,13 @@ func mergeConfiguration(configurations dynamic.Configurations) dynamic.Configura
for pvd, configuration := range configurations { for pvd, configuration := range configurations {
if configuration.HTTP != nil { if configuration.HTTP != nil {
for routerName, router := range configuration.HTTP.Routers { for routerName, router := range configuration.HTTP.Routers {
if len(router.EntryPoints) == 0 {
log.WithoutContext().
WithField(log.RouterName, routerName).
Debugf("No entryPoint defined for this router, using the default one(s) instead: %+v", entryPoints)
router.EntryPoints = entryPoints
}
conf.HTTP.Routers[provider.MakeQualifiedName(pvd, routerName)] = router conf.HTTP.Routers[provider.MakeQualifiedName(pvd, routerName)] = router
} }
for middlewareName, middleware := range configuration.HTTP.Middlewares { for middlewareName, middleware := range configuration.HTTP.Middlewares {
@ -41,6 +49,9 @@ func mergeConfiguration(configurations dynamic.Configurations) dynamic.Configura
for serviceName, service := range configuration.HTTP.Services { for serviceName, service := range configuration.HTTP.Services {
conf.HTTP.Services[provider.MakeQualifiedName(pvd, serviceName)] = service conf.HTTP.Services[provider.MakeQualifiedName(pvd, serviceName)] = service
} }
for modelName, model := range configuration.HTTP.Models {
conf.HTTP.Models[provider.MakeQualifiedName(pvd, modelName)] = model
}
} }
if configuration.TCP != nil { if configuration.TCP != nil {
@ -101,3 +112,45 @@ func mergeConfiguration(configurations dynamic.Configurations) dynamic.Configura
return conf return conf
} }
func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
if cfg.HTTP == nil || len(cfg.HTTP.Models) == 0 {
return cfg
}
rts := make(map[string]*dynamic.Router)
for name, router := range cfg.HTTP.Routers {
eps := router.EntryPoints
router.EntryPoints = nil
for _, epName := range eps {
m, ok := cfg.HTTP.Models[epName+"@internal"]
if ok {
cp := router.DeepCopy()
cp.EntryPoints = []string{epName}
if cp.TLS == nil {
cp.TLS = m.TLS
}
cp.Middlewares = append(m.Middlewares, cp.Middlewares...)
rtName := name
if len(eps) > 1 {
rtName = epName + "-" + name
}
rts[rtName] = cp
} else {
router.EntryPoints = append(router.EntryPoints, epName)
rts[name] = router
}
}
}
cfg.HTTP.Routers = rts
return cfg
}

View file

@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestAggregator(t *testing.T) { func Test_mergeConfiguration(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
given dynamic.Configurations given dynamic.Configurations
@ -21,6 +21,7 @@ func TestAggregator(t *testing.T) {
Routers: make(map[string]*dynamic.Router), Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware), Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service), Services: make(map[string]*dynamic.Service),
Models: make(map[string]*dynamic.Model),
}, },
}, },
{ {
@ -42,7 +43,9 @@ func TestAggregator(t *testing.T) {
}, },
expected: &dynamic.HTTPConfiguration{ expected: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
"router-1@provider-1": {}, "router-1@provider-1": {
EntryPoints: []string{"defaultEP"},
},
}, },
Middlewares: map[string]*dynamic.Middleware{ Middlewares: map[string]*dynamic.Middleware{
"middleware-1@provider-1": {}, "middleware-1@provider-1": {},
@ -50,6 +53,7 @@ func TestAggregator(t *testing.T) {
Services: map[string]*dynamic.Service{ Services: map[string]*dynamic.Service{
"service-1@provider-1": {}, "service-1@provider-1": {},
}, },
Models: make(map[string]*dynamic.Model),
}, },
}, },
{ {
@ -84,8 +88,12 @@ func TestAggregator(t *testing.T) {
}, },
expected: &dynamic.HTTPConfiguration{ expected: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
"router-1@provider-1": {}, "router-1@provider-1": {
"router-1@provider-2": {}, EntryPoints: []string{"defaultEP"},
},
"router-1@provider-2": {
EntryPoints: []string{"defaultEP"},
},
}, },
Middlewares: map[string]*dynamic.Middleware{ Middlewares: map[string]*dynamic.Middleware{
"middleware-1@provider-1": {}, "middleware-1@provider-1": {},
@ -95,6 +103,7 @@ func TestAggregator(t *testing.T) {
"service-1@provider-1": {}, "service-1@provider-1": {},
"service-1@provider-2": {}, "service-1@provider-2": {},
}, },
Models: make(map[string]*dynamic.Model),
}, },
}, },
} }
@ -104,13 +113,13 @@ func TestAggregator(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
actual := mergeConfiguration(test.given) actual := mergeConfiguration(test.given, []string{"defaultEP"})
assert.Equal(t, test.expected, actual.HTTP) assert.Equal(t, test.expected, actual.HTTP)
}) })
} }
} }
func TestAggregator_tlsoptions(t *testing.T) { func Test_mergeConfiguration_tlsOptions(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
given dynamic.Configurations given dynamic.Configurations
@ -289,13 +298,13 @@ func TestAggregator_tlsoptions(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
actual := mergeConfiguration(test.given) actual := mergeConfiguration(test.given, []string{"defaultEP"})
assert.Equal(t, test.expected, actual.TLS.Options) assert.Equal(t, test.expected, actual.TLS.Options)
}) })
} }
} }
func TestAggregator_tlsStore(t *testing.T) { func Test_mergeConfiguration_tlsStore(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
given dynamic.Configurations given dynamic.Configurations
@ -381,8 +390,202 @@ func TestAggregator_tlsStore(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
actual := mergeConfiguration(test.given) actual := mergeConfiguration(test.given, []string{"defaultEP"})
assert.Equal(t, test.expected, actual.TLS.Stores) assert.Equal(t, test.expected, actual.TLS.Stores)
}) })
} }
} }
func Test_applyModel(t *testing.T) {
testCases := []struct {
desc string
input dynamic.Configuration
expected dynamic.Configuration
}{
{
desc: "empty configuration",
input: dynamic.Configuration{},
expected: dynamic.Configuration{},
},
{
desc: "without model",
input: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: make(map[string]*dynamic.Model),
},
},
expected: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: make(map[string]*dynamic.Model),
},
},
},
{
desc: "with model, not used",
input: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: map[string]*dynamic.Model{
"ep@internal": {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
},
},
},
},
expected: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: map[string]*dynamic.Model{
"ep@internal": {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
},
},
},
},
},
{
desc: "with model, one entry point",
input: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"test": {
EntryPoints: []string{"websecure"},
},
},
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: map[string]*dynamic.Model{
"websecure@internal": {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
},
},
},
},
expected: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"test": {
EntryPoints: []string{"websecure"},
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
},
},
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: map[string]*dynamic.Model{
"websecure@internal": {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
},
},
},
},
},
{
desc: "with model, one entry point, and router with tls",
input: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"test": {
EntryPoints: []string{"websecure"},
TLS: &dynamic.RouterTLSConfig{CertResolver: "router"},
},
},
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: map[string]*dynamic.Model{
"websecure@internal": {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{CertResolver: "ep"},
},
},
},
},
expected: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"test": {
EntryPoints: []string{"websecure"},
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{CertResolver: "router"},
},
},
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: map[string]*dynamic.Model{
"websecure@internal": {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{CertResolver: "ep"},
},
},
},
},
},
{
desc: "with model, two entry points",
input: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"test": {
EntryPoints: []string{"websecure", "web"},
},
},
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: map[string]*dynamic.Model{
"websecure@internal": {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
},
},
},
},
expected: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"test": {
EntryPoints: []string{"web"},
},
"websecure-test": {
EntryPoints: []string{"websecure"},
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
},
},
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: map[string]*dynamic.Model{
"websecure@internal": {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
},
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := applyModel(test.input)
assert.Equal(t, test.expected, actual)
})
}
}

View file

@ -18,6 +18,8 @@ import (
type ConfigurationWatcher struct { type ConfigurationWatcher struct {
provider provider.Provider provider provider.Provider
entryPoints []string
providersThrottleDuration time.Duration providersThrottleDuration time.Duration
currentConfigurations safe.Safe currentConfigurations safe.Safe
@ -32,7 +34,12 @@ type ConfigurationWatcher struct {
} }
// NewConfigurationWatcher creates a new ConfigurationWatcher. // NewConfigurationWatcher creates a new ConfigurationWatcher.
func NewConfigurationWatcher(routinesPool *safe.Pool, pvd provider.Provider, providersThrottleDuration time.Duration) *ConfigurationWatcher { func NewConfigurationWatcher(
routinesPool *safe.Pool,
pvd provider.Provider,
providersThrottleDuration time.Duration,
entryPoints []string,
) *ConfigurationWatcher {
watcher := &ConfigurationWatcher{ watcher := &ConfigurationWatcher{
provider: pvd, provider: pvd,
configurationChan: make(chan dynamic.Message, 100), configurationChan: make(chan dynamic.Message, 100),
@ -40,6 +47,7 @@ func NewConfigurationWatcher(routinesPool *safe.Pool, pvd provider.Provider, pro
providerConfigUpdateMap: make(map[string]chan dynamic.Message), providerConfigUpdateMap: make(map[string]chan dynamic.Message),
providersThrottleDuration: providersThrottleDuration, providersThrottleDuration: providersThrottleDuration,
routinesPool: routinesPool, routinesPool: routinesPool,
entryPoints: entryPoints,
} }
currentConfigurations := make(dynamic.Configurations) currentConfigurations := make(dynamic.Configurations)
@ -135,7 +143,8 @@ func (c *ConfigurationWatcher) loadMessage(configMsg dynamic.Message) {
c.currentConfigurations.Set(newConfigurations) c.currentConfigurations.Set(newConfigurations)
conf := mergeConfiguration(newConfigurations) conf := mergeConfiguration(newConfigurations, c.entryPoints)
conf = applyModel(conf)
for _, listener := range c.configurationListeners { for _, listener := range c.configurationListeners {
listener(conf) listener(conf)

View file

@ -55,7 +55,7 @@ func TestNewConfigurationWatcher(t *testing.T) {
}}, }},
} }
watcher := NewConfigurationWatcher(routinesPool, pvd, time.Second) watcher := NewConfigurationWatcher(routinesPool, pvd, time.Second, []string{})
run := make(chan struct{}) run := make(chan struct{})
@ -112,7 +112,7 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) {
}) })
} }
watcher := NewConfigurationWatcher(routinesPool, pvd, 30*time.Millisecond) watcher := NewConfigurationWatcher(routinesPool, pvd, 30*time.Millisecond, []string{})
publishedConfigCount := 0 publishedConfigCount := 0
watcher.AddListener(func(_ dynamic.Configuration) { watcher.AddListener(func(_ dynamic.Configuration) {
@ -136,7 +136,7 @@ func TestListenProvidersSkipsEmptyConfigs(t *testing.T) {
messages: []dynamic.Message{{ProviderName: "mock"}}, messages: []dynamic.Message{{ProviderName: "mock"}},
} }
watcher := NewConfigurationWatcher(routinesPool, pvd, time.Second) watcher := NewConfigurationWatcher(routinesPool, pvd, time.Second, []string{})
watcher.AddListener(func(_ dynamic.Configuration) { watcher.AddListener(func(_ dynamic.Configuration) {
t.Error("An empty configuration was published but it should not") t.Error("An empty configuration was published but it should not")
}) })
@ -162,7 +162,7 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) {
messages: []dynamic.Message{message, message}, messages: []dynamic.Message{message, message},
} }
watcher := NewConfigurationWatcher(routinesPool, pvd, 0) watcher := NewConfigurationWatcher(routinesPool, pvd, 0, []string{})
alreadyCalled := false alreadyCalled := false
watcher.AddListener(func(_ dynamic.Configuration) { watcher.AddListener(func(_ dynamic.Configuration) {
@ -205,7 +205,7 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
}, },
} }
watcher := NewConfigurationWatcher(routinesPool, pvd, 15*time.Millisecond) watcher := NewConfigurationWatcher(routinesPool, pvd, 15*time.Millisecond, []string{"defaultEP"})
var lastConfig dynamic.Configuration var lastConfig dynamic.Configuration
watcher.AddListener(func(conf dynamic.Configuration) { watcher.AddListener(func(conf dynamic.Configuration) {
@ -220,7 +220,7 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
expected := dynamic.Configuration{ expected := dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo@mock")), th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("defaultEP"))),
th.WithLoadBalancerServices(th.WithService("bar@mock")), th.WithLoadBalancerServices(th.WithService("bar@mock")),
th.WithMiddlewares(), th.WithMiddlewares(),
), ),
@ -260,7 +260,7 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
}, },
} }
watcher := NewConfigurationWatcher(routinesPool, pvd, 0) watcher := NewConfigurationWatcher(routinesPool, pvd, 0, []string{"defaultEP"})
var publishedProviderConfig dynamic.Configuration var publishedProviderConfig dynamic.Configuration
@ -276,7 +276,10 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
expected := dynamic.Configuration{ expected := dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo@mock"), th.WithRouter("foo@mock2")), th.WithRouters(
th.WithRouter("foo@mock", th.WithEntryPoints("defaultEP")),
th.WithRouter("foo@mock2", th.WithEntryPoints("defaultEP")),
),
th.WithLoadBalancerServices(th.WithService("bar@mock"), th.WithService("bar@mock2")), th.WithLoadBalancerServices(th.WithService("bar@mock"), th.WithService("bar@mock2")),
th.WithMiddlewares(), th.WithMiddlewares(),
), ),

View file

@ -76,28 +76,6 @@ func TestRouterManager_Get(t *testing.T) {
entryPoints: []string{"web"}, entryPoints: []string{"web"},
expected: expectedResult{StatusCode: http.StatusNotFound}, expected: expectedResult{StatusCode: http.StatusNotFound},
}, },
{
desc: "no middleware, default entry point",
routersConfig: map[string]*dynamic.Router{
"foo": {
Service: "foo-service",
Rule: "Host(`foo.bar`)",
},
},
serviceConfig: map[string]*dynamic.Service{
"foo-service": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: server.URL,
},
},
},
},
},
entryPoints: []string{"web"},
expected: expectedResult{StatusCode: http.StatusOK},
},
{ {
desc: "no middleware, no matching", desc: "no middleware, no matching",
routersConfig: map[string]*dynamic.Router{ routersConfig: map[string]*dynamic.Router{
@ -735,6 +713,14 @@ func TestRuntimeConfiguration(t *testing.T) {
func TestProviderOnMiddlewares(t *testing.T) { func TestProviderOnMiddlewares(t *testing.T) {
entryPoints := []string{"web"} entryPoints := []string{"web"}
staticCfg := static.Configuration{
EntryPoints: map[string]*static.EntryPoint{
"web": {
Address: ":80",
},
},
}
rtConf := runtime.NewConfig(dynamic.Configuration{ rtConf := runtime.NewConfig(dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Services: map[string]*dynamic.Service{ Services: map[string]*dynamic.Service{
@ -746,11 +732,13 @@ func TestProviderOnMiddlewares(t *testing.T) {
}, },
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
"router@file": { "router@file": {
EntryPoints: []string{"web"},
Rule: "Host(`test`)", Rule: "Host(`test`)",
Service: "test@file", Service: "test@file",
Middlewares: []string{"chain@file", "m1"}, Middlewares: []string{"chain@file", "m1"},
}, },
"router@docker": { "router@docker": {
EntryPoints: []string{"web"},
Rule: "Host(`test`)", Rule: "Host(`test`)",
Service: "test@file", Service: "test@file",
Middlewares: []string{"chain", "m1@file"}, Middlewares: []string{"chain", "m1@file"},
@ -774,7 +762,7 @@ func TestProviderOnMiddlewares(t *testing.T) {
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil) serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{}) responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{})
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil) chainBuilder := middleware.NewChainBuilder(staticCfg, nil, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)

View file

@ -23,17 +23,18 @@ func TestReuseService(t *testing.T) {
staticConfig := static.Configuration{ staticConfig := static.Configuration{
EntryPoints: map[string]*static.EntryPoint{ EntryPoints: map[string]*static.EntryPoint{
"http": {}, "web": {},
}, },
} }
dynamicConfigs := th.BuildConfiguration( dynamicConfigs := th.BuildConfiguration(
th.WithRouters( th.WithRouters(
th.WithRouter("foo", th.WithRouter("foo",
th.WithEntryPoints("web"),
th.WithServiceName("bar"), th.WithServiceName("bar"),
th.WithRule("Path(`/ok`)")), th.WithRule("Path(`/ok`)")),
th.WithRouter("foo2", th.WithRouter("foo2",
th.WithEntryPoints("http"), th.WithEntryPoints("web"),
th.WithRule("Path(`/unauthorized`)"), th.WithRule("Path(`/unauthorized`)"),
th.WithServiceName("bar"), th.WithServiceName("bar"),
th.WithRouterMiddlewares("basicauth")), th.WithRouterMiddlewares("basicauth")),
@ -56,7 +57,7 @@ func TestReuseService(t *testing.T) {
// Test that the /ok path returns a status 200. // Test that the /ok path returns a status 200.
responseRecorderOk := &httptest.ResponseRecorder{} responseRecorderOk := &httptest.ResponseRecorder{}
requestOk := httptest.NewRequest(http.MethodGet, testServer.URL+"/ok", nil) requestOk := httptest.NewRequest(http.MethodGet, testServer.URL+"/ok", nil)
entryPointsHandlers["http"].GetHTTPHandler().ServeHTTP(responseRecorderOk, requestOk) entryPointsHandlers["web"].GetHTTPHandler().ServeHTTP(responseRecorderOk, requestOk)
assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code") assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code")
@ -64,7 +65,7 @@ func TestReuseService(t *testing.T) {
// the basic authentication defined on the frontend. // the basic authentication defined on the frontend.
responseRecorderUnauthorized := &httptest.ResponseRecorder{} responseRecorderUnauthorized := &httptest.ResponseRecorder{}
requestUnauthorized := httptest.NewRequest(http.MethodGet, testServer.URL+"/unauthorized", nil) requestUnauthorized := httptest.NewRequest(http.MethodGet, testServer.URL+"/unauthorized", nil)
entryPointsHandlers["http"].GetHTTPHandler().ServeHTTP(responseRecorderUnauthorized, requestUnauthorized) entryPointsHandlers["web"].GetHTTPHandler().ServeHTTP(responseRecorderUnauthorized, requestUnauthorized)
assert.Equal(t, http.StatusUnauthorized, responseRecorderUnauthorized.Result().StatusCode, "status code") assert.Equal(t, http.StatusUnauthorized, responseRecorderUnauthorized.Result().StatusCode, "status code")
} }
@ -83,7 +84,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
config: func(testServerURL string) *dynamic.HTTPConfiguration { config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration( return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"), th.WithEntryPoints("web"),
th.WithServiceName("bar"), th.WithServiceName("bar"),
th.WithRule(routeRule)), th.WithRule(routeRule)),
), ),
@ -106,7 +107,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
config: func(testServerURL string) *dynamic.HTTPConfiguration { config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration( return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"), th.WithEntryPoints("web"),
th.WithServiceName("bar"), th.WithServiceName("bar"),
th.WithRule(routeRule)), th.WithRule(routeRule)),
), ),
@ -120,7 +121,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
config: func(testServerURL string) *dynamic.HTTPConfiguration { config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration( return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"), th.WithEntryPoints("web"),
th.WithServiceName("bar"), th.WithServiceName("bar"),
th.WithRule(routeRule)), th.WithRule(routeRule)),
), ),
@ -136,7 +137,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
config: func(testServerURL string) *dynamic.HTTPConfiguration { config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration( return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"), th.WithEntryPoints("web"),
th.WithServiceName("bar"), th.WithServiceName("bar"),
th.WithRule(routeRule)), th.WithRule(routeRule)),
), ),
@ -150,7 +151,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
config: func(testServerURL string) *dynamic.HTTPConfiguration { config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration( return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"), th.WithEntryPoints("web"),
th.WithServiceName("bar"), th.WithServiceName("bar"),
th.WithRule(routeRule)), th.WithRule(routeRule)),
), ),
@ -176,7 +177,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
staticConfig := static.Configuration{ staticConfig := static.Configuration{
EntryPoints: map[string]*static.EntryPoint{ EntryPoints: map[string]*static.EntryPoint{
"http": {}, "web": {},
}, },
} }
@ -190,7 +191,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
responseRecorder := &httptest.ResponseRecorder{} responseRecorder := &httptest.ResponseRecorder{}
request := httptest.NewRequest(http.MethodGet, testServer.URL+requestPath, nil) request := httptest.NewRequest(http.MethodGet, testServer.URL+requestPath, nil)
entryPointsHandlers["http"].GetHTTPHandler().ServeHTTP(responseRecorder, request) entryPointsHandlers["web"].GetHTTPHandler().ServeHTTP(responseRecorder, request)
assert.Equal(t, test.expectedStatusCode, responseRecorder.Result().StatusCode, "status code") assert.Equal(t, test.expectedStatusCode, responseRecorder.Result().StatusCode, "status code")
}) })
@ -206,13 +207,14 @@ func TestInternalServices(t *testing.T) {
staticConfig := static.Configuration{ staticConfig := static.Configuration{
API: &static.API{}, API: &static.API{},
EntryPoints: map[string]*static.EntryPoint{ EntryPoints: map[string]*static.EntryPoint{
"http": {}, "web": {},
}, },
} }
dynamicConfigs := th.BuildConfiguration( dynamicConfigs := th.BuildConfiguration(
th.WithRouters( th.WithRouters(
th.WithRouter("foo", th.WithRouter("foo",
th.WithEntryPoints("web"),
th.WithServiceName("api@internal"), th.WithServiceName("api@internal"),
th.WithRule("PathPrefix(`/api`)")), th.WithRule("PathPrefix(`/api`)")),
), ),
@ -228,7 +230,7 @@ func TestInternalServices(t *testing.T) {
// Test that the /ok path returns a status 200. // Test that the /ok path returns a status 200.
responseRecorderOk := &httptest.ResponseRecorder{} responseRecorderOk := &httptest.ResponseRecorder{}
requestOk := httptest.NewRequest(http.MethodGet, testServer.URL+"/api/rawdata", nil) requestOk := httptest.NewRequest(http.MethodGet, testServer.URL+"/api/rawdata", nil)
entryPointsHandlers["http"].GetHTTPHandler().ServeHTTP(responseRecorderOk, requestOk) entryPointsHandlers["web"].GetHTTPHandler().ServeHTTP(responseRecorderOk, requestOk)
assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code") assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code")
} }

View file

@ -53,6 +53,11 @@ func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string
func (m *InternalHandlers) get(serviceName string) (http.Handler, error) { func (m *InternalHandlers) get(serviceName string) (http.Handler, error) {
switch serviceName { switch serviceName {
case "noop@internal":
return http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.WriteHeader(http.StatusTeapot)
}), nil
case "api@internal": case "api@internal":
if m.api == nil { if m.api == nil {
return nil, errors.New("api is not enabled") return nil, errors.New("api is not enabled")

View file

@ -6,7 +6,10 @@ import (
// BuildConfiguration is a helper to create a configuration. // BuildConfiguration is a helper to create a configuration.
func BuildConfiguration(dynamicConfigBuilders ...func(*dynamic.HTTPConfiguration)) *dynamic.HTTPConfiguration { func BuildConfiguration(dynamicConfigBuilders ...func(*dynamic.HTTPConfiguration)) *dynamic.HTTPConfiguration {
conf := &dynamic.HTTPConfiguration{} conf := &dynamic.HTTPConfiguration{
Models: map[string]*dynamic.Model{},
}
for _, build := range dynamicConfigBuilders { for _, build := range dynamicConfigBuilders {
build(conf) build(conf)
} }