Compare commits

...

15 Commits

Author SHA1 Message Date
Jean-Baptiste Doumenjou
3ff83fc1f8 Prepare release v2.3.6 2020-12-17 17:02:04 +01:00
Ludovic Fernandez
63f65e5b2a Disable router when a rule has an error 2020-12-17 10:06:03 +01:00
Icelyn Jennings
ac8e47579b Add missing quotes in errorpages k8s example yaml 2020-12-16 15:20:04 +01:00
Harold Ozouf
5487015a83 Update Logrus to v1.7.0 2020-12-14 12:56:03 +01:00
Frederic Werner
418cccd307 Add configuration example for access log filePath 2020-12-14 12:34:05 +01:00
Ludovic Fernandez
2a0760412c Update Yaegi to v0.9.8 2020-12-14 12:00:04 +01:00
Romain
42d8e6d60d Prepare release v2.3.5 2020-12-10 16:48:04 +01:00
Romain
7ba907f261 IngressRoute: add an option to disable cross-namespace routing
Co-authored-by: Jean-Baptiste Doumenjou <925513+jbdoumenjou@users.noreply.github.com>
2020-12-10 14:58:04 +01:00
Harold Ozouf
c72769e2ea Fix TLS options fallback when domain and options are the same
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
2020-12-09 14:16:03 +01:00
Paulo Júnior
02d856b8a5 Documentation: Add spacing to sidebars so the last item is always visible 2020-12-07 18:24:04 +01:00
Ioannis Pinakoulakis
0d15ac8861 Fix UI bug on long service name 2020-12-07 14:14:03 +01:00
Ludovic Fernandez
134a767a7f Update go-acme/lego to v4.1.3 2020-12-04 23:40:03 +01:00
Harold Ozouf
7403b6fb82 Fix concatenation of IPv6 addresses and ports 2020-12-04 20:56:04 +01:00
Kevin Crawley
121eaced49 Add example for multiple service per container 2020-12-03 09:36:03 +01:00
Sergiu Marsavela
512ed086bd Fix typos in migration guide 2020-11-27 11:18:04 +01:00
48 changed files with 1992 additions and 116 deletions

View File

@@ -1,3 +1,30 @@
## [v2.3.6](https://github.com/traefik/traefik/tree/v2.3.6) (2020-12-17)
[All Commits](https://github.com/traefik/traefik/compare/v2.3.5...v2.3.6)
**Bug fixes:**
- **[logs]** Update Logrus to v1.7.0 ([#7663](https://github.com/traefik/traefik/pull/7663) by [jspdown](https://github.com/jspdown))
- **[plugins]** Update Yaegi to v0.9.8 ([#7659](https://github.com/traefik/traefik/pull/7659) by [ldez](https://github.com/ldez))
- **[rules]** Disable router when a rule has an error ([#7680](https://github.com/traefik/traefik/pull/7680) by [ldez](https://github.com/ldez))
**Documentation:**
- **[logs]** Add configuration example for access log filePath ([#7655](https://github.com/traefik/traefik/pull/7655) by [wernerfred](https://github.com/wernerfred))
- **[middleware]** Add missing quotes in errorpages k8s example yaml ([#7675](https://github.com/traefik/traefik/pull/7675) by [icelynjennings](https://github.com/icelynjennings))
## [v2.3.5](https://github.com/traefik/traefik/tree/v2.3.5) (2020-12-10)
[All Commits](https://github.com/traefik/traefik/compare/v2.3.4...v2.3.5)
**Bug fixes:**
- **[acme]** Update go-acme/lego to v4.1.3 ([#7625](https://github.com/traefik/traefik/pull/7625) by [ldez](https://github.com/ldez))
- **[k8s,k8s/crd]** IngressRoute: add an option to disable cross-namespace routing ([#7595](https://github.com/traefik/traefik/pull/7595) by [rtribotte](https://github.com/rtribotte))
- **[k8s/crd,k8s/ingress]** Fix concatenation of IPv6 addresses and ports ([#7620](https://github.com/traefik/traefik/pull/7620) by [jspdown](https://github.com/jspdown))
- **[tcp,tls]** Fix TLS options fallback when domain and options are the same ([#7609](https://github.com/traefik/traefik/pull/7609) by [jspdown](https://github.com/jspdown))
- **[webui]** Fix UI bug on long service name ([#7535](https://github.com/traefik/traefik/pull/7535) by [ipinak](https://github.com/ipinak))
**Documentation:**
- **[docker]** Add example for multiple service per container ([#7610](https://github.com/traefik/traefik/pull/7610) by [notsureifkevin](https://github.com/notsureifkevin))
- Documentation: Add spacing to sidebars so the last item is always visible ([#7616](https://github.com/traefik/traefik/pull/7616) by [paulocfjunior](https://github.com/paulocfjunior))
- Fix typos in migration guide ([#7596](https://github.com/traefik/traefik/pull/7596) by [marsavela](https://github.com/marsavela))
## [v2.3.4](https://github.com/traefik/traefik/tree/v2.3.4) (2020-11-24)
[All Commits](https://github.com/traefik/traefik/compare/v2.3.3...v2.3.4)

View File

@@ -35,6 +35,10 @@
padding: 0;
}
.md-sidebar__scrollwrap {
max-height: calc(100% - 50px);
}
.md-sidebar--secondary .md-sidebar__scrollwrap {
border-radius: 8px;
background-color: var(--light-blue) !important;

View File

@@ -28,7 +28,7 @@ metadata:
spec:
errors:
status:
- 500-599
- "500-599"
query: /{status}.html
service:
name: whoami

View File

@@ -385,7 +385,7 @@ To apply a redirection:
entryPoints:
web:
address: 80
address: ":80"
http:
redirections:
entrypoint:
@@ -393,7 +393,7 @@ To apply a redirection:
scheme: https
websecure:
address: 443
address: ":443"
```
!!! example "HTTP to HTTPS redirection per domain"

View File

@@ -26,6 +26,20 @@ accessLog: {}
By default access logs are written to the standard output.
To write the logs into a log file, use the `filePath` option.
```toml tab="File (TOML)"
[accessLog]
filePath = "/path/to/access.log"
```
```yaml tab="File (YAML)"
accessLog:
filePath: "/path/to/access.log"
```
```bash tab="CLI"
--accesslog.filepath=/path/to/access.log
```
### `format`
By default, logs are written using the Common Log Format (CLF).
@@ -60,7 +74,6 @@ accessLog:
```bash tab="CLI"
# Configuring a buffer of 100 lines
--accesslog=true
--accesslog.filepath=/path/to/access.log
--accesslog.bufferingsize=100
```
@@ -103,7 +116,6 @@ accessLog:
```bash tab="CLI"
# Configuring Multiple Filters
--accesslog=true
--accesslog.filepath=/path/to/access.log
--accesslog.format=json
--accesslog.filters.statuscodes=200,300-302
@@ -163,7 +175,6 @@ accessLog:
```bash tab="CLI"
# Limiting the Logs to Specific Fields
--accesslog=true
--accesslog.filepath=/path/to/access.log
--accesslog.format=json
--accesslog.fields.defaultmode=keep
@@ -238,7 +249,6 @@ services:
environment:
- TZ=US/Alaska
command:
- --accesslog
- --accesslog.fields.names.StartUTC=drop
- --providers.docker
ports:

View File

@@ -250,6 +250,34 @@ providers:
--providers.kubernetescrd.throttleDuration=10s
```
### `allowCrossNamespace`
_Optional, Default: true_
```toml tab="File (TOML)"
[providers.kubernetesCRD]
allowCrossNamespace = false
# ...
```
```yaml tab="File (YAML)"
providers:
kubernetesCRD:
allowCrossNamespace: false
# ...
```
```bash tab="CLI"
--providers.kubernetescrd.allowCrossNamespace=false
```
If the parameter is set to `false`, an IngressRoute will not be able to reference any resources
in another namespace than the IngressRoute namespace.
!!! warning "Deprecation"
Please notice that the default value for this option will be set to `false` in a future version.
## Further
Also see the [full example](../user-guides/crd-acme/index.md) with Let's Encrypt.

View File

@@ -540,6 +540,9 @@ TLS key
`--providers.kubernetescrd`:
Enable Kubernetes backend with default settings. (Default: ```false```)
`--providers.kubernetescrd.allowcrossnamespace`:
Allow cross namespace resource reference. (Default: ```true```)
`--providers.kubernetescrd.certauthfilepath`:
Kubernetes certificate authority file path (not needed for in-cluster client).

View File

@@ -540,6 +540,9 @@ TLS key
`TRAEFIK_PROVIDERS_KUBERNETESCRD`:
Enable Kubernetes backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_KUBERNETESCRD_ALLOWCROSSNAMESPACE`:
Allow cross namespace resource reference. (Default: ```true```)
`TRAEFIK_PROVIDERS_KUBERNETESCRD_CERTAUTHFILEPATH`:
Kubernetes certificate authority file path (not needed for in-cluster client).

View File

@@ -113,6 +113,7 @@
certAuthFilePath = "foobar"
disablePassHostHeaders = true
namespaces = ["foobar", "foobar"]
allowCrossNamespace = true
labelSelector = "foobar"
ingressClass = "foobar"
throttleDuration = 42

View File

@@ -123,6 +123,7 @@ providers:
namespaces:
- foobar
- foobar
allowCrossNamespace: true
labelSelector: foobar
ingressClass: foobar
throttleDuration: 42s

View File

@@ -58,6 +58,26 @@ Attach labels to your containers and let Traefik do the rest!
Setting the label `traefik.http.services.xxx.loadbalancer.server.port`
overrides that behavior.
??? example "Specifying more than one router and service per container"
Forwarding requests to more than one port on a container requires referencing the service loadbalancer port definition using the service parameter on the router.
In this example, requests are forwarded for `http://example-a.com` to `http://<private IP of container>:8000` in addition to `http://example-b.com` forwarding to `http://<private IP of container>:9000`:
```yaml
version: "3"
services:
my-container:
# ...
labels:
- traefik.http.routers.www-router.rule=Host(`example-a.com`)
- traefik.http.routers.www-router.service=www-service
- traefik.http.services.www-service.loadbalancer.server.port=8000
- traefik.http.routers.admin-router.rule=Host(`example-b.com`)
- traefik.http.routers.admin-router.service=admin-service
- traefik.http.services.admin-service.loadbalancer.server.port=9000
```
??? example "Configuring Docker Swarm & Deploying / Exposing Services"
Enabling the docker provider (Swarm Mode)

6
go.mod
View File

@@ -35,7 +35,7 @@ require (
github.com/fatih/structs v1.1.0
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2
github.com/go-acme/lego/v4 v4.1.2
github.com/go-acme/lego/v4 v4.1.3
github.com/go-check/check v0.0.0-00010101000000-000000000000
github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea
github.com/golang/protobuf v1.3.4
@@ -68,12 +68,12 @@ require (
github.com/prometheus/client_golang v1.3.0
github.com/prometheus/client_model v0.1.0
github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac
github.com/sirupsen/logrus v1.4.2
github.com/sirupsen/logrus v1.7.0
github.com/stretchr/testify v1.6.1
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
github.com/tinylib/msgp v1.0.2 // indirect
github.com/traefik/paerser v0.1.1
github.com/traefik/yaegi v0.9.7
github.com/traefik/yaegi v0.9.8
github.com/uber/jaeger-client-go v2.25.0+incompatible
github.com/uber/jaeger-lib v2.2.0+incompatible
github.com/unrolled/render v1.0.2

11
go.sum
View File

@@ -275,8 +275,8 @@ github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2/go.mod h1:GLy
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-acme/lego/v4 v4.1.2 h1:1zROppXkTbAIh7J7AydGD3dFICLIocucJY1NTH/wB64=
github.com/go-acme/lego/v4 v4.1.2/go.mod h1:pIFm5tWkXSgiAEfJ/XQCQIvX1cEvHFwbgLZyx8OVSUE=
github.com/go-acme/lego/v4 v4.1.3 h1:D8nnzrijQFUAqdNPwnbvm6tJ3AJAzQAlnROeecUNG/4=
github.com/go-acme/lego/v4 v4.1.3/go.mod h1:pIFm5tWkXSgiAEfJ/XQCQIvX1cEvHFwbgLZyx8OVSUE=
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
@@ -725,6 +725,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@@ -766,8 +768,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/traefik/paerser v0.1.1 h1:Suj0iA4hTAV6E4Dh5/++TXAj5u6iTwydBlFssIUz+9w=
github.com/traefik/paerser v0.1.1/go.mod h1:yYnAgdEC2wJH5CgG75qGWC8SsFDEapg09o9RrA6FfrE=
github.com/traefik/yaegi v0.9.7 h1:CbeKjEhy3DoSC8xC4TQF2Mhmd7u3Cjqluz1//x6Vtcs=
github.com/traefik/yaegi v0.9.7/go.mod h1:FAYnRlZyuVlEkvnkHq3bvJ1lW5be6XuwgLdkYgYG6Lk=
github.com/traefik/yaegi v0.9.8 h1:sYGKV2d911B9olVsSO/nRmjIh+CQTffAUSzoyv4a4/4=
github.com/traefik/yaegi v0.9.8/go.mod h1:FAYnRlZyuVlEkvnkHq3bvJ1lW5be6XuwgLdkYgYG6Lk=
github.com/transip/gotransip/v6 v6.2.0 h1:0Z+qVsyeiQdWfcAUeJyF0IEKAPvhJwwpwPi2WGtBIiE=
github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
@@ -972,6 +974,7 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@@ -0,0 +1,120 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: other-ns
---
apiVersion: v1
kind: Service
metadata:
name: whoami
namespace: other-ns
spec:
ports:
- name: http
port: 80
selector:
app: traefiklabs
task: whoami
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test6.route
namespace: other-ns
spec:
entryPoints:
- web
routes:
- match: Host(`foo.com`) && PathPrefix(`/a`)
kind: Rule
services:
- name: whoami
namespace: default
port: 80
- match: Host(`foo.com`) && PathPrefix(`/b`)
kind: Rule
services:
- name: wrr2
namespace: default
kind: TraefikService
- match: Host(`foo.com`) && PathPrefix(`/c`)
kind: Rule
services:
- name: wrr3
kind: TraefikService
- match: Host(`foo.com`) && PathPrefix(`/d`)
kind: Rule
services:
- name: whoami
namespace: other-ns
port: 80
middlewares:
- name: stripprefix2
namespace: default
- match: Host(`foo.com`) && PathPrefix(`/e`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: test-errorpage
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr2
namespace: default
spec:
weighted:
services:
- name: whoami
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr3
namespace: other-ns
spec:
weighted:
services:
- name: whoami
namespace: default
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: stripprefix2
namespace: default
spec:
stripPrefix:
prefixes:
- /tobestripped
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-errorpage
spec:
errors:
status:
- 500-599
query: /{status}.html
service:
name: whoami
namespace: other-ns
port: 80

View File

@@ -16,3 +16,4 @@
address = ":8000"
[providers.kubernetesCRD]
allowCrossNamespace = false

View File

@@ -53,6 +53,7 @@ func (s *K8sSuite) TearDownSuite(c *check.C) {
"./fixtures/k8s/coredns.yaml",
"./fixtures/k8s/rolebindings.yaml",
"./fixtures/k8s/traefik.yaml",
"./fixtures/k8s/ccm.yaml",
}
for _, filename := range generatedFiles {

View File

@@ -50,6 +50,20 @@
"using": [
"web"
]
},
"other-ns-test6-route-482e4988e134701d8cc8@kubernetescrd": {
"entryPoints": [
"web"
],
"service": "other-ns-wrr3",
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/c`)",
"error": [
"the service \"other-ns-wrr3@kubernetescrd\" does not exist"
],
"status": "disabled",
"using": [
"web"
]
}
},
"middlewares": {
@@ -64,6 +78,14 @@
"default-test2-route-23c7f4c450289ee29016@kubernetescrd"
]
},
"default-stripprefix2@kubernetescrd": {
"stripPrefix": {
"prefixes": [
"/tobestripped"
]
},
"status": "enabled"
},
"default-stripprefix@kubernetescrd": {
"stripPrefix": {
"prefixes": [
@@ -172,6 +194,17 @@
"default-test3-route-7d0ac22d3d8db4b82618@kubernetescrd"
]
},
"default-wrr2@kubernetescrd": {
"weighted": {
"services": [
{
"name": "default-whoami-80",
"weight": 1
}
]
},
"status": "enabled"
},
"noop@internal": {
"status": "enabled"
}

View File

@@ -119,6 +119,35 @@ subsets:
- name: websecure2
port: 8443
---
apiVersion: v1
kind: Service
metadata:
name: whoami-ipv6
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: traefiklabs
task: whoami-ipv6
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami-ipv6
namespace: default
subsets:
- addresses:
- ip: "2001:db8:85a3:8d3:1319:8a2e:370:7348"
ports:
- name: web
port: 8080
---
apiVersion: v1
kind: Service
@@ -157,5 +186,46 @@ spec:
protocol: TCP
port: 443
---
apiVersion: v1
kind: Service
metadata:
name: external-svc-with-ipv6
namespace: default
spec:
externalName: "2001:db8:85a3:8d3:1319:8a2e:370:7347"
type: ExternalName
ports:
- name: http
protocol: TCP
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoami-svc
namespace: cross-ns
spec:
ports:
- name: web
port: 80
selector:
app: traefiklabs
task: whoami
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami-svc
namespace: cross-ns
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 80

View File

@@ -132,6 +132,36 @@ subsets:
- name: myapp4
port: 8084
---
apiVersion: v1
kind: Service
metadata:
name: whoamitcp-ipv6
namespace: default
spec:
ports:
- name: myapp-ipv6
port: 8080
selector:
app: traefiklabs
task: whoamitcp-ipv6
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoamitcp-ipv6
namespace: default
subsets:
- addresses:
- ip: "fd00:10:244:0:1::3"
- ip: "2001:db8:85a3:8d3:1319:8a2e:370:7348"
ports:
- name: myapp-ipv6
port: 8080
---
apiVersion: v1
kind: Service
@@ -167,4 +197,44 @@ spec:
type: ExternalName
ports:
- name: http
protocol: TCP
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: external.service.with.ipv6
namespace: default
spec:
externalName: "fe80::200:5aee:feaa:20a2"
type: ExternalName
---
apiVersion: v1
kind: Service
metadata:
name: whoamitcp-cross-ns
namespace: cross-ns
spec:
ports:
- name: myapp
port: 8000
selector:
app: traefiklabs
task: whoamitcp
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoamitcp-cross-ns
namespace: cross-ns
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: myapp
port: 8000

View File

@@ -0,0 +1,16 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`foo.com`)
services:
- name: whoamitcp-cross-ns
namespace: cross-ns
port: 8000

View File

@@ -0,0 +1,17 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`*`)
services:
- name: whoamitcp-ipv6
port: 8080
- name: external.service.with.ipv6
port: 8080

View File

@@ -101,3 +101,62 @@ subsets:
ports:
- name: myapp4
port: 8084
---
apiVersion: v1
kind: Service
metadata:
name: whoamiudp-ipv6
namespace: default
spec:
ports:
- name: myapp-ipv6
port: 8080
selector:
app: traefiklabs
task: whoamiudp-ipv6
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoamiudp-ipv6
namespace: default
subsets:
- addresses:
- ip: "fd00:10:244:0:1::3"
ports:
- name: myapp-ipv6
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoamiudp-cross-ns
namespace: cross-ns
spec:
ports:
- name: myapp
port: 8000
selector:
app: traefiklabs
task: whoamiudp
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoamiudp-cross-ns
namespace: cross-ns
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: myapp
port: 8000

View File

@@ -0,0 +1,15 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteUDP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- services:
- name: whoamiudp-cross-ns
namespace: cross-ns
port: 8000

View File

@@ -0,0 +1,14 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteUDP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- services:
- name: whoamiudp-ipv6
port: 8080

View File

@@ -0,0 +1,91 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: cross-ns-route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`) && PathPrefix(`/bar`)
kind: Rule
priority: 12
services:
- name: whoami-svc
namespace: cross-ns
port: 80
- name: tr-svc-wrr1
kind: TraefikService
- name: tr-svc-wrr2
namespace: cross-ns
kind: TraefikService
- name: tr-svc-mirror1
kind: TraefikService
- name: tr-svc-mirror2
namespace: cross-ns
kind: TraefikService
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: tr-svc-wrr1
namespace: default
spec:
weighted:
services:
- name: whoami-svc
namespace: cross-ns
weight: 1
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: tr-svc-wrr2
namespace: cross-ns
spec:
weighted:
services:
- name: whoami-svc
weight: 1
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: tr-svc-mirror1
namespace: default
spec:
mirroring:
name: whoami
port: 80
mirrors:
- name: whoami-svc
namespace: cross-ns
percent: 20
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: tr-svc-mirror2
namespace: cross-ns
spec:
mirroring:
name: whoami-svc
port: 80
mirrors:
- name: whoami-svc
namespace: cross-ns
percent: 20
port: 80

View File

@@ -0,0 +1,18 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`) && PathPrefix(`/bar`)
kind: Rule
services:
- name: whoami-ipv6
port: 8080
- name: external-svc-with-ipv6
port: 8080

View File

@@ -0,0 +1,58 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test-crossnamespace.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`) && PathPrefix(`/bar`)
kind: Rule
priority: 12
services:
- name: whoami
namespace: default
port: 80
middlewares:
- name: stripprefix
namespace: cross-ns
- match: Host(`foo.com`) && PathPrefix(`/bir`)
kind: Rule
priority: 12
services:
- name: whoami
namespace: default
port: 80
middlewares:
- name: test-errorpage
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: stripprefix
namespace: cross-ns
spec:
stripPrefix:
prefixes:
- /stripit
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-errorpage
namespace: default
spec:
errors:
status:
- 500-599
query: /{status}.html
service:
name: whoami-svc
namespace: cross-ns
port: 80

View File

@@ -43,6 +43,7 @@ type Provider struct {
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers." json:"disablePassHostHeaders,omitempty" toml:"disablePassHostHeaders,omitempty" yaml:"disablePassHostHeaders,omitempty" export:"true"`
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
AllowCrossNamespace *bool `description:"Allow cross namespace resource reference." json:"allowCrossNamespace,omitempty" toml:"allowCrossNamespace,omitempty" yaml:"allowCrossNamespace,omitempty" export:"true"`
LabelSelector string `description:"Kubernetes label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"`
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
@@ -82,6 +83,11 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
return client, nil
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.AllowCrossNamespace = func(b bool) *bool { return &b }(true)
}
// Init the provider.
func (p *Provider) Init() error {
return nil
@@ -98,6 +104,10 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
return err
}
if p.AllowCrossNamespace == nil || *p.AllowCrossNamespace {
logger.Warn("Cross-namespace reference between IngressRoutes and resources is enabled, please ensure that this is expected (see AllowCrossNamespace option)")
}
pool.GoCtx(func(ctxPool context.Context) {
operation := func() error {
eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done())
@@ -197,7 +207,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
continue
}
errorPage, errorPageService, err := createErrorPageMiddleware(client, middleware.Namespace, middleware.Spec.Errors)
errorPage, errorPageService, err := p.createErrorPageMiddleware(client, middleware.Namespace, middleware.Spec.Errors)
if err != nil {
log.FromContext(ctxMid).Errorf("Error while reading error page middleware: %v", err)
continue
@@ -236,7 +246,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
}
}
cb := configBuilder{client}
cb := configBuilder{client, p.AllowCrossNamespace}
for _, service := range client.GetTraefikServices() {
err := cb.buildTraefikService(ctx, service, conf.HTTP.Services)
if err != nil {
@@ -281,7 +291,7 @@ func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error
return &corev1.ServicePort{Port: port}, nil
}
func createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) {
func (p *Provider) createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) {
if errorPage == nil {
return nil, nil, nil
}
@@ -291,7 +301,7 @@ func createErrorPageMiddleware(client Client, namespace string, errorPage *v1alp
Query: errorPage.Query,
}
balancerServerHTTP, err := configBuilder{client}.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
balancerServerHTTP, err := configBuilder{client, p.AllowCrossNamespace}.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
if err != nil {
return nil, nil, err
}
@@ -749,3 +759,8 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s
return eventsChanBuffered
}
func isNamespaceAllowed(allowCrossNamespace *bool, parentNamespace, namespace string) bool {
// If allowCrossNamespace option is not defined the default behavior is to allow cross namespace references.
return allowCrossNamespace == nil || *allowCrossNamespace || parentNamespace == namespace
}

View File

@@ -4,6 +4,8 @@ import (
"context"
"errors"
"fmt"
"net"
"strconv"
"strings"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
@@ -46,7 +48,8 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
ingressName = ingressRoute.GenerateName
}
cb := configBuilder{client}
cb := configBuilder{client, p.AllowCrossNamespace}
for _, route := range ingressRoute.Spec.Routes {
if route.Kind != "Rule" {
logger.Errorf("Unsupported match kind: %s. Only \"Rule\" is supported for now.", route.Kind)
@@ -64,23 +67,10 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
continue
}
var mds []string
for _, mi := range route.Middlewares {
if strings.Contains(mi.Name, providerNamespaceSeparator) {
if len(mi.Namespace) > 0 {
logger.
WithField(log.MiddlewareName, mi.Name).
Warnf("namespace %q is ignored in cross-provider context", mi.Namespace)
}
mds = append(mds, mi.Name)
continue
}
ns := mi.Namespace
if len(ns) == 0 {
ns = ingressRoute.Namespace
}
mds = append(mds, makeID(ns, mi.Name))
mds, err := p.makeMiddlewareKeys(ctx, ingressRoute.Namespace, route.Middlewares)
if err != nil {
logger.Errorf("Failed to create middleware keys: %v", err)
continue
}
normalized := provider.Normalize(makeID(ingressRoute.Namespace, serviceKey))
@@ -151,8 +141,38 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
return conf
}
func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace string, middlewares []v1alpha1.MiddlewareRef) ([]string, error) {
var mds []string
for _, mi := range middlewares {
if strings.Contains(mi.Name, providerNamespaceSeparator) {
if len(mi.Namespace) > 0 {
log.FromContext(ctx).
WithField(log.MiddlewareName, mi.Name).
Warnf("namespace %q is ignored in cross-provider context", mi.Namespace)
}
mds = append(mds, mi.Name)
continue
}
ns := ingRouteNamespace
if len(mi.Namespace) > 0 {
if !isNamespaceAllowed(p.AllowCrossNamespace, ingRouteNamespace, mi.Namespace) {
return nil, fmt.Errorf("middleware %s/%s is not in the IngressRoute namespace %s", mi.Namespace, mi.Name, ingRouteNamespace)
}
ns = mi.Namespace
}
mds = append(mds, makeID(ns, mi.Name))
}
return mds, nil
}
type configBuilder struct {
client Client
client Client
allowCrossNamespace *bool
}
// buildTraefikService creates the configuration for the traefik service defined in tService,
@@ -268,7 +288,7 @@ func (c configBuilder) buildServersLB(namespace string, svc v1alpha1.LoadBalance
return &dynamic.Service{LoadBalancer: lb}, nil
}
func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBalancerSpec) ([]dynamic.Server, error) {
func (c configBuilder) loadServers(parentNamespace string, svc v1alpha1.LoadBalancerSpec) ([]dynamic.Server, error) {
strategy := svc.Strategy
if strategy == "" {
strategy = roundRobinStrategy
@@ -277,7 +297,11 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
return nil, fmt.Errorf("load balancing strategy %s is not supported", strategy)
}
namespace := namespaceOrFallback(svc, fallbackNamespace)
namespace := namespaceOrFallback(svc, parentNamespace)
if !isNamespaceAllowed(c.allowCrossNamespace, parentNamespace, namespace) {
return nil, fmt.Errorf("load balancer service %s/%s is not in the parent resource namespace %s", svc.Namespace, svc.Name, parentNamespace)
}
// If the service uses explicitly the provider suffix
sanitizedName := strings.TrimSuffix(svc.Name, providerNamespaceSeparator+providerName)
@@ -301,8 +325,10 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
return nil, err
}
hostPort := net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port)))
return append(servers, dynamic.Server{
URL: fmt.Sprintf("%s://%s:%d", protocol, service.Spec.ExternalName, svcPort.Port),
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
}), nil
}
@@ -336,8 +362,10 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
}
for _, addr := range subset.Addresses {
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port)))
servers = append(servers, dynamic.Server{
URL: fmt.Sprintf("%s://%s:%d", protocol, addr.IP, port),
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
})
}
}
@@ -349,10 +377,14 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
// In addition, if the service is a Kubernetes one,
// it generates and returns the configuration part for such a service,
// so that the caller can add it to the global config map.
func (c configBuilder) nameAndService(ctx context.Context, namespaceService string, service v1alpha1.LoadBalancerSpec) (string, *dynamic.Service, error) {
func (c configBuilder) nameAndService(ctx context.Context, parentNamespace string, service v1alpha1.LoadBalancerSpec) (string, *dynamic.Service, error) {
svcCtx := log.With(ctx, log.Str(log.ServiceName, service.Name))
namespace := namespaceOrFallback(service, namespaceService)
namespace := namespaceOrFallback(service, parentNamespace)
if !isNamespaceAllowed(c.allowCrossNamespace, parentNamespace, namespace) {
return "", nil, fmt.Errorf("service %s/%s not in the parent resource namespace %s", service.Namespace, service.Name, parentNamespace)
}
switch {
case service.Kind == "" || service.Kind == "Service":

View File

@@ -4,6 +4,8 @@ import (
"context"
"errors"
"fmt"
"net"
"strconv"
"strings"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
@@ -53,7 +55,7 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
serviceName := makeID(ingressRouteTCP.Namespace, key)
for _, service := range route.Services {
balancerServerTCP, err := createLoadBalancerServerTCP(client, ingressRouteTCP.Namespace, service)
balancerServerTCP, err := p.createLoadBalancerServerTCP(client, ingressRouteTCP.Namespace, service)
if err != nil {
logger.
WithField("serviceName", service.Name).
@@ -123,9 +125,13 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
return conf
}
func createLoadBalancerServerTCP(client Client, namespace string, service v1alpha1.ServiceTCP) (*dynamic.TCPService, error) {
ns := namespace
func (p *Provider) createLoadBalancerServerTCP(client Client, parentNamespace string, service v1alpha1.ServiceTCP) (*dynamic.TCPService, error) {
ns := parentNamespace
if len(service.Namespace) > 0 {
if !isNamespaceAllowed(p.AllowCrossNamespace, parentNamespace, service.Namespace) {
return nil, fmt.Errorf("tcp service %s/%s is not in the parent resource namespace %s", service.Namespace, service.Name, parentNamespace)
}
ns = service.Namespace
}
@@ -165,7 +171,7 @@ func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([
var servers []dynamic.TCPServer
if service.Spec.Type == corev1.ServiceTypeExternalName {
servers = append(servers, dynamic.TCPServer{
Address: fmt.Sprintf("%s:%d", service.Spec.ExternalName, svcPort.Port),
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
})
} else {
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
@@ -196,7 +202,7 @@ func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([
for _, addr := range subset.Addresses {
servers = append(servers, dynamic.TCPServer{
Address: fmt.Sprintf("%s:%d", addr.IP, port),
Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))),
})
}
}

View File

@@ -2,13 +2,21 @@ package crd
import (
"context"
"io/ioutil"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/provider"
crdfake "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake"
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/k8s"
"github.com/traefik/traefik/v2/pkg/tls"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
kubefake "k8s.io/client-go/kubernetes/fake"
)
var _ provider.Provider = (*Provider)(nil)
@@ -987,6 +995,128 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Ingress Route with IPv6 backends",
paths: []string{
"services.yml", "with_ipv6.yml",
"tcp/services.yml", "tcp/with_ipv6.yml",
"udp/services.yml", "udp/with_ipv6.yml",
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"default-test.route-0": {
EntryPoints: []string{"foo"},
Service: "default-test.route-0",
},
},
Services: map[string]*dynamic.UDPService{
"default-test.route-0": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "[fd00:10:244:0:1::3]:8080",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"default-test.route-673acf455cb2dab0b43a": {
EntryPoints: []string{"foo"},
Service: "default-test.route-673acf455cb2dab0b43a",
Rule: "HostSNI(`*`)",
},
},
Services: map[string]*dynamic.TCPService{
"default-test.route-673acf455cb2dab0b43a": {
Weighted: &dynamic.TCPWeightedRoundRobin{
Services: []dynamic.TCPWRRService{
{
Name: "default-test.route-673acf455cb2dab0b43a-whoamitcp-ipv6-8080",
Weight: func(i int) *int { return &i }(1),
},
{
Name: "default-test.route-673acf455cb2dab0b43a-external.service.with.ipv6-8080",
Weight: func(i int) *int { return &i }(1),
},
},
},
},
"default-test.route-673acf455cb2dab0b43a-whoamitcp-ipv6-8080": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "[fd00:10:244:0:1::3]:8080",
},
{
Address: "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080",
},
},
},
},
"default-test.route-673acf455cb2dab0b43a-external.service.with.ipv6-8080": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "[fe80::200:5aee:feaa:20a2]:8080",
},
},
},
},
},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-test-route-6b204d94623b3df4370c": {
EntryPoints: []string{"foo"},
Service: "default-test-route-6b204d94623b3df4370c",
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-whoami-ipv6-8080": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080",
},
},
PassHostHeader: func(i bool) *bool { return &i }(true),
},
},
"default-external-svc-with-ipv6-8080": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://[2001:db8:85a3:8d3:1319:8a2e:370:7347]:8080",
},
},
PassHostHeader: func(i bool) *bool { return &i }(true),
},
},
"default-test-route-6b204d94623b3df4370c": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "default-whoami-ipv6-8080",
Weight: func(i int) *int { return &i }(1),
},
{
Name: "default-external-svc-with-ipv6-8080",
Weight: func(i int) *int { return &i }(1),
},
},
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
}
for _, test := range testCases {
@@ -1000,7 +1130,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
}
p := Provider{IngressClass: test.ingressClass}
conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...))
p.SetDefaults()
clientMock := newClientMock(test.paths...)
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
assert.Equal(t, test.expected, conf)
})
}
@@ -3065,7 +3198,10 @@ func TestLoadIngressRoutes(t *testing.T) {
}
p := Provider{IngressClass: test.ingressClass}
conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...))
p.SetDefaults()
clientMock := newClientMock(test.paths...)
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
assert.Equal(t, test.expected, conf)
})
}
@@ -3373,7 +3509,10 @@ func TestLoadIngressRouteUDPs(t *testing.T) {
}
p := Provider{IngressClass: test.ingressClass}
conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...))
p.SetDefaults()
clientMock := newClientMock(test.paths...)
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
assert.Equal(t, test.expected, conf)
})
}
@@ -3592,3 +3731,563 @@ func TestGetServicePort(t *testing.T) {
})
}
}
func TestCrossNamespace(t *testing.T) {
testCases := []struct {
desc string
allowCrossNamespace bool
ingressClass string
paths []string
expected *dynamic.Configuration
}{
{
desc: "Empty",
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "HTTP middleware cross namespace disallowed",
paths: []string{"services.yml", "with_middleware_cross_namespace.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
EntryPoints: []string{"foo"},
Service: "default-test-crossnamespace-route-9313b71dbe6a649d5049",
Rule: "Host(`foo.com`) && PathPrefix(`/bir`)",
Priority: 12,
Middlewares: []string{"default-test-errorpage"},
},
},
Middlewares: map[string]*dynamic.Middleware{
"cross-ns-stripprefix": {
StripPrefix: &dynamic.StripPrefix{
Prefixes: []string{"/stripit"},
ForceSlash: false,
},
},
},
Services: map[string]*dynamic.Service{
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "HTTP middleware cross namespace allowed",
paths: []string{"services.yml", "with_middleware_cross_namespace.yml"},
allowCrossNamespace: true,
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-test-crossnamespace-route-6b204d94623b3df4370c": {
EntryPoints: []string{"foo"},
Service: "default-test-crossnamespace-route-6b204d94623b3df4370c",
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
Priority: 12,
Middlewares: []string{
"cross-ns-stripprefix",
},
},
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
EntryPoints: []string{"foo"},
Service: "default-test-crossnamespace-route-9313b71dbe6a649d5049",
Rule: "Host(`foo.com`) && PathPrefix(`/bir`)",
Priority: 12,
Middlewares: []string{"default-test-errorpage"},
},
},
Middlewares: map[string]*dynamic.Middleware{
"cross-ns-stripprefix": {
StripPrefix: &dynamic.StripPrefix{
Prefixes: []string{"/stripit"},
ForceSlash: false,
},
},
"default-test-errorpage": {
Errors: &dynamic.ErrorPage{
Status: []string{"500-599"},
Service: "default-test-errorpage-errorpage-service",
Query: "/{status}.html",
},
},
},
Services: map[string]*dynamic.Service{
"default-test-crossnamespace-route-6b204d94623b3df4370c": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
"default-test-errorpage-errorpage-service": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "HTTP cross namespace allowed",
paths: []string{"services.yml", "with_cross_namespace.yml"},
allowCrossNamespace: true,
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-cross-ns-route-6b204d94623b3df4370c": {
EntryPoints: []string{"foo"},
Service: "default-cross-ns-route-6b204d94623b3df4370c",
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
Priority: 12,
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-cross-ns-route-6b204d94623b3df4370c": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "cross-ns-whoami-svc-80",
Weight: func(i int) *int { return &i }(1),
},
{
Name: "default-tr-svc-wrr1",
Weight: func(i int) *int { return &i }(1),
},
{
Name: "cross-ns-tr-svc-wrr2",
Weight: func(i int) *int { return &i }(1),
},
{
Name: "default-tr-svc-mirror1",
Weight: func(i int) *int { return &i }(1),
},
{
Name: "cross-ns-tr-svc-mirror2",
Weight: func(i int) *int { return &i }(1),
},
},
},
},
"cross-ns-whoami-svc-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
"default-tr-svc-wrr1": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "cross-ns-whoami-svc-80",
Weight: func(i int) *int { return &i }(1),
},
},
},
},
"cross-ns-tr-svc-wrr2": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "cross-ns-whoami-svc-80",
Weight: func(i int) *int { return &i }(1),
},
},
},
},
"default-tr-svc-mirror1": {
Mirroring: &dynamic.Mirroring{
Service: "default-whoami-80",
Mirrors: []dynamic.MirrorService{
{
Name: "cross-ns-whoami-svc-80",
Percent: 20,
},
},
},
},
"cross-ns-tr-svc-mirror2": {
Mirroring: &dynamic.Mirroring{
Service: "cross-ns-whoami-svc-80",
Mirrors: []dynamic.MirrorService{
{
Name: "cross-ns-whoami-svc-80",
Percent: 20,
},
},
},
},
"default-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "HTTP cross namespace disallowed",
paths: []string{"services.yml", "with_cross_namespace.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"cross-ns-tr-svc-wrr2": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "cross-ns-whoami-svc-80",
Weight: func(i int) *int { return &i }(1),
},
},
},
},
"cross-ns-whoami-svc-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
"cross-ns-tr-svc-mirror2": {
Mirroring: &dynamic.Mirroring{
Service: "cross-ns-whoami-svc-80",
Mirrors: []dynamic.MirrorService{
{
Name: "cross-ns-whoami-svc-80",
Percent: 20,
},
},
},
},
"default-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "TCP cross namespace allowed",
paths: []string{"tcp/services.yml", "tcp/with_cross_namespace.yml"},
allowCrossNamespace: true,
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"default-test.route-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default-test.route-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
},
Services: map[string]*dynamic.TCPService{
"default-test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "TCP cross namespace disallowed",
paths: []string{"tcp/services.yml", "tcp/with_cross_namespace.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
// The router that references the invalid service will be discarded.
Routers: map[string]*dynamic.TCPRouter{
"default-test.route-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default-test.route-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "UDP cross namespace allowed",
paths: []string{"udp/services.yml", "udp/with_cross_namespace.yml"},
allowCrossNamespace: true,
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"default-test.route-0": {
EntryPoints: []string{"foo"},
Service: "default-test.route-0",
},
},
Services: map[string]*dynamic.UDPService{
"default-test.route-0": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
},
},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "UDP cross namespace disallowed",
paths: []string{"udp/services.yml", "udp/with_cross_namespace.yml"},
expected: &dynamic.Configuration{
// The router that references the invalid service will be discarded.
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"default-test.route-0": {
EntryPoints: []string{"foo"},
Service: "default-test.route-0",
},
},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var k8sObjects []runtime.Object
var crdObjects []runtime.Object
for _, path := range test.paths {
yamlContent, err := ioutil.ReadFile(filepath.FromSlash("./fixtures/" + path))
if err != nil {
panic(err)
}
objects := k8s.MustParseYaml(yamlContent)
for _, obj := range objects {
switch o := obj.(type) {
case *corev1.Service, *corev1.Endpoints, *corev1.Secret:
k8sObjects = append(k8sObjects, o)
case *v1alpha1.IngressRoute:
crdObjects = append(crdObjects, o)
case *v1alpha1.IngressRouteTCP:
crdObjects = append(crdObjects, o)
case *v1alpha1.IngressRouteUDP:
crdObjects = append(crdObjects, o)
case *v1alpha1.Middleware:
crdObjects = append(crdObjects, o)
case *v1alpha1.TraefikService:
crdObjects = append(crdObjects, o)
case *v1alpha1.TLSOption:
crdObjects = append(crdObjects, o)
case *v1alpha1.TLSStore:
crdObjects = append(crdObjects, o)
default:
}
}
}
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
crdClient := crdfake.NewSimpleClientset(crdObjects...)
client := newClientImpl(kubeClient, crdClient)
stopCh := make(chan struct{})
eventCh, err := client.WatchAll([]string{"default", "cross-ns"}, stopCh)
require.NoError(t, err)
if k8sObjects != nil || crdObjects != nil {
// just wait for the first event
<-eventCh
}
p := Provider{}
p.SetDefaults()
p.AllowCrossNamespace = func(b bool) *bool { return &b }(test.allowCrossNamespace)
conf := p.loadConfigurationFromCRD(context.Background(), client)
assert.Equal(t, test.expected, conf)
})
}
}

View File

@@ -4,6 +4,8 @@ import (
"context"
"errors"
"fmt"
"net"
"strconv"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
@@ -34,7 +36,7 @@ func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client
serviceName := makeID(ingressRouteUDP.Namespace, key)
for _, service := range route.Services {
balancerServerUDP, err := createLoadBalancerServerUDP(client, ingressRouteUDP.Namespace, service)
balancerServerUDP, err := p.createLoadBalancerServerUDP(client, ingressRouteUDP.Namespace, service)
if err != nil {
logger.
WithField("serviceName", service.Name).
@@ -75,9 +77,13 @@ func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client
return conf
}
func createLoadBalancerServerUDP(client Client, namespace string, service v1alpha1.ServiceUDP) (*dynamic.UDPService, error) {
ns := namespace
func (p *Provider) createLoadBalancerServerUDP(client Client, parentNamespace string, service v1alpha1.ServiceUDP) (*dynamic.UDPService, error) {
ns := parentNamespace
if len(service.Namespace) > 0 {
if !isNamespaceAllowed(p.AllowCrossNamespace, parentNamespace, service.Namespace) {
return nil, fmt.Errorf("udp service %s/%s is not in the parent resource namespace %s", service.Namespace, service.Name, ns)
}
ns = service.Namespace
}
@@ -121,7 +127,7 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([
var servers []dynamic.UDPServer
if service.Spec.Type == corev1.ServiceTypeExternalName {
servers = append(servers, dynamic.UDPServer{
Address: fmt.Sprintf("%s:%d", service.Spec.ExternalName, portSpec.Port),
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(portSpec.Port))),
})
} else {
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
@@ -152,7 +158,7 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([
for _, addr := range subset.Addresses {
servers = append(servers, dynamic.UDPServer{
Address: fmt.Sprintf("%s:%d", addr.IP, port),
Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))),
})
}
}

View File

@@ -0,0 +1,12 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service-bar
namespace: testing
subsets:
- addresses:
- ip: "2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b"
ports:
- name: http
port: 8080

View File

@@ -0,0 +1,18 @@
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: example.com
namespace: testing
spec:
rules:
- http:
paths:
- path: /bar
backend:
serviceName: service-bar
servicePort: 8080
- path: /foo
backend:
serviceName: service-foo
servicePort: 8080

View File

@@ -0,0 +1,26 @@
kind: Service
apiVersion: v1
metadata:
name: service-bar
namespace: testing
spec:
ports:
- name: http
port: 8080
clusterIp: "fc00:f853:ccd:e793::1"
type: ClusterIP
---
kind: Service
apiVersion: v1
metadata:
name: service-foo
namespace: testing
spec:
ports:
- name: http
port: 8080
type: ExternalName
externalName: "2001:0db8:3c4d:0015:0000:0000:1a2f:2a3b"

View File

@@ -5,8 +5,10 @@ import (
"errors"
"fmt"
"math"
"net"
"os"
"sort"
"strconv"
"strings"
"time"
@@ -479,9 +481,10 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr
if service.Spec.Type == corev1.ServiceTypeExternalName {
protocol := getProtocol(portSpec, portSpec.Name, svcConfig)
hostPort := net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(portSpec.Port)))
svc.LoadBalancer.Servers = []dynamic.Server{
{URL: fmt.Sprintf("%s://%s:%d", protocol, service.Spec.ExternalName, portSpec.Port)},
{URL: fmt.Sprintf("%s://%s", protocol, hostPort)},
}
return svc, nil
@@ -516,8 +519,10 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr
protocol := getProtocol(portSpec, portName, svcConfig)
for _, addr := range subset.Addresses {
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port)))
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.Server{
URL: fmt.Sprintf("%s://%s:%d", protocol, addr.IP, port),
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
})
}
}

View File

@@ -661,6 +661,47 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
},
},
},
{
desc: "Ingress with IPv6 endpoints",
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"example-com-testing-bar": {
Rule: "PathPrefix(`/bar`)",
Service: "testing-service-bar-8080",
},
"example-com-testing-foo": {
Rule: "PathPrefix(`/foo`)",
Service: "testing-service-foo-8080",
},
},
Services: map[string]*dynamic.Service{
"testing-service-bar-8080": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://[2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b]:8080",
},
},
PassHostHeader: Bool(true),
},
},
"testing-service-foo-8080": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://[2001:0db8:3c4d:0015:0000:0000:1a2f:2a3b]:8080",
},
},
PassHostHeader: Bool(true),
},
},
},
},
},
},
{
desc: "TLS support",
expected: &dynamic.Configuration{

View File

@@ -59,7 +59,14 @@ func (r *Router) AddRoute(rule string, priority int, handler http.Handler) error
}
route := r.NewRoute().Handler(handler).Priority(priority)
return addRuleOnRoute(route, buildTree())
err = addRuleOnRoute(route, buildTree())
if err != nil {
route.BuildOnly()
return err
}
return nil
}
type tree struct {

View File

@@ -29,6 +29,16 @@ func Test_addRoute(t *testing.T) {
rule: "rulewithnotmatcher",
expectedError: true,
},
{
desc: "Host empty",
rule: "Host(``)",
expectedError: true,
},
{
desc: "PathPrefix empty",
rule: "PathPrefix(``)",
expectedError: true,
},
{
desc: "PathPrefix",
rule: "PathPrefix(`/foo`)",

View File

@@ -62,6 +62,29 @@ func TestRouterManager_Get(t *testing.T) {
entryPoints: []string{"web"},
expected: expectedResult{StatusCode: http.StatusOK},
},
{
desc: "empty host",
routersConfig: map[string]*dynamic.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(``)",
},
},
serviceConfig: map[string]*dynamic.Service{
"foo-service": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: server.URL,
},
},
},
},
},
entryPoints: []string{"web"},
expected: expectedResult{StatusCode: http.StatusNotFound},
},
{
desc: "no load balancer",
routersConfig: map[string]*dynamic.Router{

View File

@@ -109,13 +109,18 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
tlsOptionsForHostSNI := map[string]map[string]nameAndConfig{}
tlsOptionsForHost := map[string]string{}
for routerHTTPName, routerHTTPConfig := range configsHTTP {
if len(routerHTTPConfig.TLS.Options) == 0 || routerHTTPConfig.TLS.Options == defaultTLSConfigName {
if routerHTTPConfig.TLS == nil {
continue
}
ctxRouter := log.With(provider.AddInContext(ctx, routerHTTPName), log.Str(log.RouterName, routerHTTPName))
logger := log.FromContext(ctxRouter)
tlsOptionsName := defaultTLSConfigName
if len(routerHTTPConfig.TLS.Options) > 0 && routerHTTPConfig.TLS.Options != defaultTLSConfigName {
tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options)
}
domains, err := rules.ParseDomains(routerHTTPConfig.Rule)
if err != nil {
routerErr := fmt.Errorf("invalid rule %s, error: %w", routerHTTPConfig.Rule, err)
@@ -129,34 +134,27 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
}
for _, domain := range domains {
if routerHTTPConfig.TLS != nil {
tlsOptionsName := routerHTTPConfig.TLS.Options
if tlsOptionsName != defaultTLSConfigName {
tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options)
}
tlsConf, err := m.tlsManager.Get(defaultTLSStoreName, tlsOptionsName)
if err != nil {
routerHTTPConfig.AddError(err, true)
logger.Debug(err)
continue
}
tlsConf, err := m.tlsManager.Get(defaultTLSStoreName, tlsOptionsName)
if err != nil {
routerHTTPConfig.AddError(err, true)
logger.Debug(err)
continue
}
// domain is already in lower case thanks to the domain parsing
if tlsOptionsForHostSNI[domain] == nil {
tlsOptionsForHostSNI[domain] = make(map[string]nameAndConfig)
}
tlsOptionsForHostSNI[domain][tlsOptionsName] = nameAndConfig{
routerName: routerHTTPName,
TLSConfig: tlsConf,
}
// domain is already in lower case thanks to the domain parsing
if tlsOptionsForHostSNI[domain] == nil {
tlsOptionsForHostSNI[domain] = make(map[string]nameAndConfig)
}
tlsOptionsForHostSNI[domain][routerHTTPConfig.TLS.Options] = nameAndConfig{
routerName: routerHTTPName,
TLSConfig: tlsConf,
}
if _, ok := tlsOptionsForHost[domain]; ok {
// Multiple tlsOptions fallback to default
tlsOptionsForHost[domain] = "default"
} else {
tlsOptionsForHost[domain] = routerHTTPConfig.TLS.Options
}
if name, ok := tlsOptionsForHost[domain]; ok && name != tlsOptionsName {
// Different tlsOptions on the same domain fallback to default
tlsOptionsForHost[domain] = defaultTLSConfigName
} else {
tlsOptionsForHost[domain] = tlsOptionsName
}
}
}
@@ -304,5 +302,5 @@ func findTLSOptionName(tlsOptionsForHost map[string]string, host string) string
return tlsOptions
}
return "default"
return defaultTLSConfigName
}

View File

@@ -2,25 +2,31 @@ package tcp
import (
"context"
"crypto/tls"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/config/runtime"
"github.com/traefik/traefik/v2/pkg/server/service/tcp"
"github.com/traefik/traefik/v2/pkg/tls"
traefiktls "github.com/traefik/traefik/v2/pkg/tls"
)
func TestRuntimeConfiguration(t *testing.T) {
testCases := []struct {
desc string
serviceConfig map[string]*runtime.TCPServiceInfo
routerConfig map[string]*runtime.TCPRouterInfo
expectedError int
desc string
httpServiceConfig map[string]*runtime.ServiceInfo
httpRouterConfig map[string]*runtime.RouterInfo
tcpServiceConfig map[string]*runtime.TCPServiceInfo
tcpRouterConfig map[string]*runtime.TCPRouterInfo
expectedError int
}{
{
desc: "No error",
serviceConfig: map[string]*runtime.TCPServiceInfo{
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
"foo-service": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
@@ -38,7 +44,7 @@ func TestRuntimeConfiguration(t *testing.T) {
},
},
},
routerConfig: map[string]*runtime.TCPRouterInfo{
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
"foo": {
TCPRouter: &dynamic.TCPRouter{
EntryPoints: []string{"web"},
@@ -65,9 +71,54 @@ func TestRuntimeConfiguration(t *testing.T) {
},
expectedError: 0,
},
{
desc: "HTTP routers with same domain but different TLS options",
httpServiceConfig: map[string]*runtime.ServiceInfo{
"foo-service": {
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
Port: "8085",
URL: "127.0.0.1:8085",
},
{
URL: "127.0.0.1:8086",
Port: "8086",
},
},
},
},
},
},
httpRouterConfig: map[string]*runtime.RouterInfo{
"foo": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`bar.foo`)",
TLS: &dynamic.RouterTLSConfig{
Options: "foo",
},
},
},
"bar": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`bar.foo`) && PathPrefix(`/path`)",
TLS: &dynamic.RouterTLSConfig{
Options: "bar",
},
},
},
},
expectedError: 2,
},
{
desc: "One router with wrong rule",
serviceConfig: map[string]*runtime.TCPServiceInfo{
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
"foo-service": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
@@ -80,7 +131,7 @@ func TestRuntimeConfiguration(t *testing.T) {
},
},
},
routerConfig: map[string]*runtime.TCPRouterInfo{
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
"foo": {
TCPRouter: &dynamic.TCPRouter{
EntryPoints: []string{"web"},
@@ -101,7 +152,7 @@ func TestRuntimeConfiguration(t *testing.T) {
},
{
desc: "All router with wrong rule",
serviceConfig: map[string]*runtime.TCPServiceInfo{
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
"foo-service": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
@@ -114,7 +165,7 @@ func TestRuntimeConfiguration(t *testing.T) {
},
},
},
routerConfig: map[string]*runtime.TCPRouterInfo{
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
"foo": {
TCPRouter: &dynamic.TCPRouter{
EntryPoints: []string{"web"},
@@ -134,7 +185,7 @@ func TestRuntimeConfiguration(t *testing.T) {
},
{
desc: "Router with unknown service",
serviceConfig: map[string]*runtime.TCPServiceInfo{
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
"foo-service": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
@@ -147,7 +198,7 @@ func TestRuntimeConfiguration(t *testing.T) {
},
},
},
routerConfig: map[string]*runtime.TCPRouterInfo{
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
"foo": {
TCPRouter: &dynamic.TCPRouter{
EntryPoints: []string{"web"},
@@ -168,14 +219,14 @@ func TestRuntimeConfiguration(t *testing.T) {
},
{
desc: "Router with broken service",
serviceConfig: map[string]*runtime.TCPServiceInfo{
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
"foo-service": {
TCPService: &dynamic.TCPService{
LoadBalancer: nil,
},
},
},
routerConfig: map[string]*runtime.TCPRouterInfo{
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
"bar": {
TCPRouter: &dynamic.TCPRouter{
EntryPoints: []string{"web"},
@@ -197,15 +248,17 @@ func TestRuntimeConfiguration(t *testing.T) {
entryPoints := []string{"web"}
conf := &runtime.Configuration{
TCPServices: test.serviceConfig,
TCPRouters: test.routerConfig,
Services: test.httpServiceConfig,
Routers: test.httpRouterConfig,
TCPServices: test.tcpServiceConfig,
TCPRouters: test.tcpRouterConfig,
}
serviceManager := tcp.NewManager(conf)
tlsManager := tls.NewManager()
tlsManager := traefiktls.NewManager()
tlsManager.UpdateConfigs(
context.Background(),
map[string]tls.Store{},
map[string]tls.Options{
map[string]traefiktls.Store{},
map[string]traefiktls.Options{
"default": {
MinVersion: "VersionTLS10",
},
@@ -216,7 +269,7 @@ func TestRuntimeConfiguration(t *testing.T) {
MinVersion: "VersionTLS11",
},
},
[]*tls.CertAndStores{})
[]*traefiktls.CertAndStores{})
routerManager := NewManager(conf, serviceManager,
nil, nil, tlsManager)
@@ -237,7 +290,235 @@ func TestRuntimeConfiguration(t *testing.T) {
allErrors++
}
}
for _, v := range conf.Services {
if v.Err != nil {
allErrors++
}
}
for _, v := range conf.Routers {
if len(v.Err) > 0 {
allErrors++
}
}
assert.Equal(t, test.expectedError, allErrors)
})
}
}
func TestDomainFronting(t *testing.T) {
tests := []struct {
desc string
routers map[string]*runtime.RouterInfo
expectedStatus int
}{
{
desc: "Request is misdirected when TLS options are different",
routers: map[string]*runtime.RouterInfo{
"router-1@file": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1",
},
},
},
"router-2@file": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{},
},
},
},
expectedStatus: http.StatusMisdirectedRequest,
},
{
desc: "Request is OK when TLS options are the same",
routers: map[string]*runtime.RouterInfo{
"router-1@file": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1",
},
},
},
"router-2@file": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1",
},
},
},
},
expectedStatus: http.StatusOK,
},
{
desc: "Default TLS options is used when options are ambiguous for the same host",
routers: map[string]*runtime.RouterInfo{
"router-1@file": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1",
},
},
},
"router-2@file": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host1.local`) && PathPrefix(`/foo`)",
TLS: &dynamic.RouterTLSConfig{
Options: "default",
},
},
},
"router-3@file": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1",
},
},
},
},
expectedStatus: http.StatusMisdirectedRequest,
},
{
desc: "Default TLS options should not be used when options are the same for the same host",
routers: map[string]*runtime.RouterInfo{
"router-1@file": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1",
},
},
},
"router-2@file": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host1.local`) && PathPrefix(`/bar`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1",
},
},
},
"router-3@file": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1",
},
},
},
},
expectedStatus: http.StatusOK,
},
{
desc: "Request is misdirected when TLS options have the same name but from different providers",
routers: map[string]*runtime.RouterInfo{
"router-1@file": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1",
},
},
},
"router-2@crd": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1",
},
},
},
},
expectedStatus: http.StatusMisdirectedRequest,
},
{
desc: "Request is OK when TLS options reference from a different provider is the same",
routers: map[string]*runtime.RouterInfo{
"router-1@file": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1@crd",
},
},
},
"router-2@crd": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{
Options: "host1@crd",
},
},
},
},
expectedStatus: http.StatusOK,
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
entryPoints := []string{"web"}
tlsOptions := map[string]traefiktls.Options{
"default": {
MinVersion: "VersionTLS10",
},
"host1@file": {
MinVersion: "VersionTLS12",
},
"host1@crd": {
MinVersion: "VersionTLS12",
},
}
conf := &runtime.Configuration{
Routers: test.routers,
}
serviceManager := tcp.NewManager(conf)
tlsManager := traefiktls.NewManager()
tlsManager.UpdateConfigs(context.Background(), map[string]traefiktls.Store{}, tlsOptions, []*traefiktls.CertAndStores{})
httpsHandler := map[string]http.Handler{
"web": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}),
}
routerManager := NewManager(conf, serviceManager, nil, httpsHandler, tlsManager)
routers := routerManager.BuildHandlers(context.Background(), entryPoints)
router, ok := routers["web"]
require.True(t, ok)
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Host = "host1.local"
req.TLS = &tls.ConnectionState{
ServerName: "host2.local",
}
rw := httptest.NewRecorder()
router.GetHTTPSHandler().ServeHTTP(rw, req)
assert.Equal(t, test.expectedStatus, rw.Code)
})
}
}

View File

@@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file"
FileName = "traefik_changelog.md"
# example new bugfix v2.3.4
# example new bugfix v2.3.5
CurrentRef = "v2.3"
PreviousRef = "v2.3.3"
PreviousRef = "v2.3.5"
BaseBranch = "v2.3"
FutureCurrentRefName = "v2.3.4"
FutureCurrentRefName = "v2.3.6"
ThresholdPreviousRef = 10
ThresholdCurrentRef = 10

View File

@@ -231,8 +231,9 @@
<div class="text-subtitle2">Service</div>
<q-chip
dense
class="app-chip app-chip-green">
class="app-chip app-chip-green app-chip-overflow">
{{ exData(middleware).service }}
<q-tooltip>{{ exData(middleware).service }}</q-tooltip>
</q-chip>
</div>
</div>

View File

@@ -21,8 +21,9 @@
<div class="col-6">
<q-chip
dense
class="app-chip app-chip-rule">
class="app-chip app-chip-rule app-chip-overflow">
{{ service.name }}
<q-tooltip>{{service.name}}</q-tooltip>
</q-chip>
</div>
<div class="col-3 text-right">

View File

@@ -66,8 +66,9 @@
dense
clickable
@click.native="$router.push({ path: `/${protocol}/services/${getServiceId()}`})"
class="app-chip app-chip-wrap app-chip-service">
class="app-chip app-chip-wrap app-chip-service app-chip-overflow">
{{ data.service }}
<q-tooltip>{{ data.service }}</q-tooltip>
</q-chip>
</div>
</div>

View File

@@ -21,8 +21,9 @@
<div class="col-7">
<q-chip
dense
class="app-chip app-chip-rule">
class="app-chip app-chip-rule app-chip-overflow">
{{ service.name }}
<q-tooltip>{{service.name}}</q-tooltip>
</q-chip>
</div>
<div class="col-3">

View File

@@ -131,6 +131,16 @@ body {
white-space: normal;
}
}
&-overflow {
max-width: 90%;
.q-chip__content{
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
&-accent, &-rule {
color: $accent;
background-color: rgba($accent, 0.1);