Merge branch v2.3 into master
This commit is contained in:
commit
eebbe64b36
46 changed files with 1949 additions and 148 deletions
27
CHANGELOG.md
27
CHANGELOG.md
|
@ -1,3 +1,30 @@
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[acme]** Update go-acme/lego to v4.1.2 ([#7577](https://github.com/traefik/traefik/pull/7577) by [ldez](https://github.com/ldez))
|
||||||
|
- **[k8s,k8s/crd,k8s/ingress]** Apply labelSelector as a TweakListOptions for Kubernetes informers ([#7521](https://github.com/traefik/traefik/pull/7521) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[middleware]** Do not evaluate templated URL in redirectRegex middleware ([#7573](https://github.com/traefik/traefik/pull/7573) by [jspdown](https://github.com/jspdown))
|
||||||
|
- **[provider]** fix: invalid slice parsing. ([#7583](https://github.com/traefik/traefik/pull/7583) by [ldez](https://github.com/ldez))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[ecs]** Fix clusters option in ECS provider documentation ([#7586](https://github.com/traefik/traefik/pull/7586) by [skapin](https://github.com/skapin))
|
||||||
|
|
||||||
## [v2.3.3](https://github.com/traefik/traefik/tree/v2.3.3) (2020-11-19)
|
## [v2.3.3](https://github.com/traefik/traefik/tree/v2.3.3) (2020-11-19)
|
||||||
[All Commits](https://github.com/traefik/traefik/compare/v2.3.2...v2.3.3)
|
[All Commits](https://github.com/traefik/traefik/compare/v2.3.2...v2.3.3)
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,10 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-sidebar__scrollwrap {
|
||||||
|
max-height: calc(100% - 50px);
|
||||||
|
}
|
||||||
|
|
||||||
.md-sidebar--secondary .md-sidebar__scrollwrap {
|
.md-sidebar--secondary .md-sidebar__scrollwrap {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: var(--light-blue) !important;
|
background-color: var(--light-blue) !important;
|
||||||
|
|
|
@ -385,7 +385,7 @@ To apply a redirection:
|
||||||
|
|
||||||
entryPoints:
|
entryPoints:
|
||||||
web:
|
web:
|
||||||
address: 80
|
address: ":80"
|
||||||
http:
|
http:
|
||||||
redirections:
|
redirections:
|
||||||
entrypoint:
|
entrypoint:
|
||||||
|
@ -393,7 +393,7 @@ To apply a redirection:
|
||||||
scheme: https
|
scheme: https
|
||||||
|
|
||||||
websecure:
|
websecure:
|
||||||
address: 443
|
address: ":443"
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! example "HTTP to HTTPS redirection per domain"
|
!!! example "HTTP to HTTPS redirection per domain"
|
||||||
|
|
|
@ -87,7 +87,7 @@ _Optional, Default=["default"]_
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[providers.ecs]
|
[providers.ecs]
|
||||||
cluster = ["default"]
|
clusters = ["default"]
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -250,6 +250,34 @@ providers:
|
||||||
--providers.kubernetescrd.throttleDuration=10s
|
--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
|
## Further
|
||||||
|
|
||||||
Also see the [full example](../user-guides/crd-acme/index.md) with Let's Encrypt.
|
Also see the [full example](../user-guides/crd-acme/index.md) with Let's Encrypt.
|
||||||
|
|
|
@ -549,6 +549,9 @@ TLS key
|
||||||
`--providers.kubernetescrd`:
|
`--providers.kubernetescrd`:
|
||||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||||
|
|
||||||
|
`--providers.kubernetescrd.allowcrossnamespace`:
|
||||||
|
Allow cross namespace resource reference. (Default: ```true```)
|
||||||
|
|
||||||
`--providers.kubernetescrd.certauthfilepath`:
|
`--providers.kubernetescrd.certauthfilepath`:
|
||||||
Kubernetes certificate authority file path (not needed for in-cluster client).
|
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||||
|
|
||||||
|
|
|
@ -549,6 +549,9 @@ TLS key
|
||||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD`:
|
`TRAEFIK_PROVIDERS_KUBERNETESCRD`:
|
||||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESCRD_ALLOWCROSSNAMESPACE`:
|
||||||
|
Allow cross namespace resource reference. (Default: ```true```)
|
||||||
|
|
||||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD_CERTAUTHFILEPATH`:
|
`TRAEFIK_PROVIDERS_KUBERNETESCRD_CERTAUTHFILEPATH`:
|
||||||
Kubernetes certificate authority file path (not needed for in-cluster client).
|
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,7 @@
|
||||||
certAuthFilePath = "foobar"
|
certAuthFilePath = "foobar"
|
||||||
disablePassHostHeaders = true
|
disablePassHostHeaders = true
|
||||||
namespaces = ["foobar", "foobar"]
|
namespaces = ["foobar", "foobar"]
|
||||||
|
allowCrossNamespace = true
|
||||||
labelSelector = "foobar"
|
labelSelector = "foobar"
|
||||||
ingressClass = "foobar"
|
ingressClass = "foobar"
|
||||||
throttleDuration = 42
|
throttleDuration = 42
|
||||||
|
|
|
@ -124,6 +124,7 @@ providers:
|
||||||
namespaces:
|
namespaces:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
|
allowCrossNamespace: true
|
||||||
labelSelector: foobar
|
labelSelector: foobar
|
||||||
ingressClass: foobar
|
ingressClass: foobar
|
||||||
throttleDuration: 42s
|
throttleDuration: 42s
|
||||||
|
|
|
@ -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`
|
Setting the label `traefik.http.services.xxx.loadbalancer.server.port`
|
||||||
overrides that behavior.
|
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"
|
??? example "Configuring Docker Swarm & Deploying / Exposing Services"
|
||||||
|
|
||||||
Enabling the docker provider (Swarm Mode)
|
Enabling the docker provider (Swarm Mode)
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -33,7 +33,7 @@ require (
|
||||||
github.com/fatih/structs v1.1.0
|
github.com/fatih/structs v1.1.0
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||||
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2
|
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2
|
||||||
github.com/go-acme/lego/v4 v4.1.0
|
github.com/go-acme/lego/v4 v4.1.3
|
||||||
github.com/go-check/check v0.0.0-00010101000000-000000000000
|
github.com/go-check/check v0.0.0-00010101000000-000000000000
|
||||||
github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea
|
github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea
|
||||||
github.com/golang/protobuf v1.4.2
|
github.com/golang/protobuf v1.4.2
|
||||||
|
@ -71,7 +71,7 @@ require (
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
|
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
|
||||||
github.com/tinylib/msgp v1.0.2 // indirect
|
github.com/tinylib/msgp v1.0.2 // indirect
|
||||||
github.com/traefik/paerser v0.1.0
|
github.com/traefik/paerser v0.1.1
|
||||||
github.com/traefik/yaegi v0.9.7
|
github.com/traefik/yaegi v0.9.7
|
||||||
github.com/uber/jaeger-client-go v2.25.0+incompatible
|
github.com/uber/jaeger-client-go v2.25.0+incompatible
|
||||||
github.com/uber/jaeger-lib v2.2.0+incompatible
|
github.com/uber/jaeger-lib v2.2.0+incompatible
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -284,8 +284,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 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 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-acme/lego/v4 v4.1.0 h1:/9pMjaeaLq6m0n+io+kv2ySs2ZfrmH6eazuMoN18GHo=
|
github.com/go-acme/lego/v4 v4.1.3 h1:D8nnzrijQFUAqdNPwnbvm6tJ3AJAzQAlnROeecUNG/4=
|
||||||
github.com/go-acme/lego/v4 v4.1.0/go.mod h1:pIFm5tWkXSgiAEfJ/XQCQIvX1cEvHFwbgLZyx8OVSUE=
|
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-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 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
|
@ -786,8 +786,8 @@ github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDW
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/traefik/paerser v0.1.0 h1:B4v1tbvd8YnHsA7spwHKEWJoGrRP+2jYpIozsCMHhl0=
|
github.com/traefik/paerser v0.1.1 h1:Suj0iA4hTAV6E4Dh5/++TXAj5u6iTwydBlFssIUz+9w=
|
||||||
github.com/traefik/paerser v0.1.0/go.mod h1:yYnAgdEC2wJH5CgG75qGWC8SsFDEapg09o9RrA6FfrE=
|
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 h1:CbeKjEhy3DoSC8xC4TQF2Mhmd7u3Cjqluz1//x6Vtcs=
|
||||||
github.com/traefik/yaegi v0.9.7/go.mod h1:FAYnRlZyuVlEkvnkHq3bvJ1lW5be6XuwgLdkYgYG6Lk=
|
github.com/traefik/yaegi v0.9.7/go.mod h1:FAYnRlZyuVlEkvnkHq3bvJ1lW5be6XuwgLdkYgYG6Lk=
|
||||||
github.com/transip/gotransip/v6 v6.2.0 h1:0Z+qVsyeiQdWfcAUeJyF0IEKAPvhJwwpwPi2WGtBIiE=
|
github.com/transip/gotransip/v6 v6.2.0 h1:0Z+qVsyeiQdWfcAUeJyF0IEKAPvhJwwpwPi2WGtBIiE=
|
||||||
|
|
120
integration/fixtures/k8s/07-ingressroute-cross-namespace.yml
Normal file
120
integration/fixtures/k8s/07-ingressroute-cross-namespace.yml
Normal 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
|
|
@ -16,3 +16,4 @@
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
|
|
||||||
[providers.kubernetesCRD]
|
[providers.kubernetesCRD]
|
||||||
|
allowCrossNamespace = false
|
||||||
|
|
|
@ -53,6 +53,7 @@ func (s *K8sSuite) TearDownSuite(c *check.C) {
|
||||||
"./fixtures/k8s/coredns.yaml",
|
"./fixtures/k8s/coredns.yaml",
|
||||||
"./fixtures/k8s/rolebindings.yaml",
|
"./fixtures/k8s/rolebindings.yaml",
|
||||||
"./fixtures/k8s/traefik.yaml",
|
"./fixtures/k8s/traefik.yaml",
|
||||||
|
"./fixtures/k8s/ccm.yaml",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, filename := range generatedFiles {
|
for _, filename := range generatedFiles {
|
||||||
|
|
33
integration/testdata/rawdata-crd.json
vendored
33
integration/testdata/rawdata-crd.json
vendored
|
@ -61,6 +61,20 @@
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"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": {
|
"middlewares": {
|
||||||
|
@ -75,6 +89,14 @@
|
||||||
"default-test2-route-23c7f4c450289ee29016@kubernetescrd"
|
"default-test2-route-23c7f4c450289ee29016@kubernetescrd"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"default-stripprefix2@kubernetescrd": {
|
||||||
|
"stripPrefix": {
|
||||||
|
"prefixes": [
|
||||||
|
"/tobestripped"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"status": "enabled"
|
||||||
|
},
|
||||||
"default-stripprefix@kubernetescrd": {
|
"default-stripprefix@kubernetescrd": {
|
||||||
"stripPrefix": {
|
"stripPrefix": {
|
||||||
"prefixes": [
|
"prefixes": [
|
||||||
|
@ -205,6 +227,17 @@
|
||||||
"default-test3-route-7d0ac22d3d8db4b82618@kubernetescrd"
|
"default-test3-route-7d0ac22d3d8db4b82618@kubernetescrd"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"default-wrr2@kubernetescrd": {
|
||||||
|
"weighted": {
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"name": "default-whoami-80",
|
||||||
|
"weight": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"status": "enabled"
|
||||||
|
},
|
||||||
"noop@internal": {
|
"noop@internal": {
|
||||||
"status": "enabled"
|
"status": "enabled"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package redirect
|
package redirect
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -47,24 +44,17 @@ func (r *redirect) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||||
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
oldURL := rawURL(req)
|
oldURL := rawURL(req)
|
||||||
|
|
||||||
// If the Regexp doesn't match, skip to the next handler
|
// If the Regexp doesn't match, skip to the next handler.
|
||||||
if !r.regex.MatchString(oldURL) {
|
if !r.regex.MatchString(oldURL) {
|
||||||
r.next.ServeHTTP(rw, req)
|
r.next.ServeHTTP(rw, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply a rewrite regexp to the URL
|
// Apply a rewrite regexp to the URL.
|
||||||
newURL := r.regex.ReplaceAllString(oldURL, r.replacement)
|
newURL := r.regex.ReplaceAllString(oldURL, r.replacement)
|
||||||
|
|
||||||
// replace any variables that may be in there
|
// Parse the rewritten URL and replace request URL with it.
|
||||||
rewrittenURL := &bytes.Buffer{}
|
parsedURL, err := url.Parse(newURL)
|
||||||
if err := applyString(newURL, rewrittenURL, req); err != nil {
|
|
||||||
r.errHandler.ServeHTTP(rw, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse the rewritten URL and replace request URL with it
|
|
||||||
parsedURL, err := url.Parse(rewrittenURL.String())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.errHandler.ServeHTTP(rw, req, err)
|
r.errHandler.ServeHTTP(rw, req, err)
|
||||||
return
|
return
|
||||||
|
@ -78,7 +68,7 @@ func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
req.URL = parsedURL
|
req.URL = parsedURL
|
||||||
|
|
||||||
// make sure the request URI corresponds the rewritten URL
|
// Make sure the request URI corresponds the rewritten URL.
|
||||||
req.RequestURI = req.URL.RequestURI()
|
req.RequestURI = req.URL.RequestURI()
|
||||||
r.next.ServeHTTP(rw, req)
|
r.next.ServeHTTP(rw, req)
|
||||||
}
|
}
|
||||||
|
@ -138,14 +128,3 @@ func rawURL(req *http.Request) string {
|
||||||
|
|
||||||
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyString(in string, out io.Writer, req *http.Request) error {
|
|
||||||
t, err := template.New("t").Parse(in)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := struct{ Request *http.Request }{Request: req}
|
|
||||||
|
|
||||||
return t.Execute(out, data)
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRedirectRegexHandler(t *testing.T) {
|
func TestRedirectRegexHandler(t *testing.T) {
|
||||||
|
@ -35,16 +34,6 @@ func TestRedirectRegexHandler(t *testing.T) {
|
||||||
expectedURL: "https://foobar.com:443",
|
expectedURL: "https://foobar.com:443",
|
||||||
expectedStatus: http.StatusFound,
|
expectedStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
desc: "use request header",
|
|
||||||
config: dynamic.RedirectRegex{
|
|
||||||
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
|
||||||
Replacement: `https://${1}{{ .Request.Header.Get "X-Foo" }}$2:443$4`,
|
|
||||||
},
|
|
||||||
url: "http://foo.com:80",
|
|
||||||
expectedURL: "https://foobar.com:443",
|
|
||||||
expectedStatus: http.StatusFound,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
desc: "URL doesn't match regex",
|
desc: "URL doesn't match regex",
|
||||||
config: dynamic.RedirectRegex{
|
config: dynamic.RedirectRegex{
|
||||||
|
@ -186,7 +175,7 @@ func TestRedirectRegexHandler(t *testing.T) {
|
||||||
method = test.method
|
method = test.method
|
||||||
}
|
}
|
||||||
|
|
||||||
req := testhelpers.MustNewRequest(method, test.url, nil)
|
req := httptest.NewRequest(method, test.url, nil)
|
||||||
if test.secured {
|
if test.secured {
|
||||||
req.TLS = &tls.ConnectionState{}
|
req.TLS = &tls.ConnectionState{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,35 @@ subsets:
|
||||||
- name: websecure2
|
- name: websecure2
|
||||||
port: 8443
|
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
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
|
@ -157,5 +186,46 @@ spec:
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
port: 443
|
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
|
||||||
|
|
|
@ -132,6 +132,36 @@ subsets:
|
||||||
- name: myapp4
|
- name: myapp4
|
||||||
port: 8084
|
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
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
|
@ -167,4 +197,44 @@ spec:
|
||||||
type: ExternalName
|
type: ExternalName
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- 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
|
||||||
|
|
|
@ -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
|
17
pkg/provider/kubernetes/crd/fixtures/tcp/with_ipv6.yml
Normal file
17
pkg/provider/kubernetes/crd/fixtures/tcp/with_ipv6.yml
Normal 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
|
|
@ -101,3 +101,62 @@ subsets:
|
||||||
ports:
|
ports:
|
||||||
- name: myapp4
|
- name: myapp4
|
||||||
port: 8084
|
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
|
||||||
|
|
|
@ -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
|
14
pkg/provider/kubernetes/crd/fixtures/udp/with_ipv6.yml
Normal file
14
pkg/provider/kubernetes/crd/fixtures/udp/with_ipv6.yml
Normal 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
|
|
@ -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
|
18
pkg/provider/kubernetes/crd/fixtures/with_ipv6.yml
Normal file
18
pkg/provider/kubernetes/crd/fixtures/with_ipv6.yml
Normal 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
|
|
@ -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
|
|
@ -43,12 +43,18 @@ 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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||||
lastConfiguration safe.Safe
|
lastConfiguration safe.Safe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDefaults sets the default values.
|
||||||
|
func (p *Provider) SetDefaults() {
|
||||||
|
p.AllowCrossNamespace = func(b bool) *bool { return &b }(true)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
|
func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
|
||||||
_, err := labels.Parse(p.LabelSelector)
|
_, err := labels.Parse(p.LabelSelector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -98,6 +104,10 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||||
return err
|
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) {
|
pool.GoCtx(func(ctxPool context.Context) {
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done())
|
eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done())
|
||||||
|
@ -197,7 +207,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
||||||
continue
|
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 {
|
if err != nil {
|
||||||
log.FromContext(ctxMid).Errorf("Error while reading error page middleware: %v", err)
|
log.FromContext(ctxMid).Errorf("Error while reading error page middleware: %v", err)
|
||||||
continue
|
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() {
|
for _, service := range client.GetTraefikServices() {
|
||||||
err := cb.buildTraefikService(ctx, service, conf.HTTP.Services)
|
err := cb.buildTraefikService(ctx, service, conf.HTTP.Services)
|
||||||
|
@ -346,7 +356,7 @@ func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error
|
||||||
return &corev1.ServicePort{Port: port}, nil
|
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 {
|
if errorPage == nil {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
@ -356,7 +366,7 @@ func createErrorPageMiddleware(client Client, namespace string, errorPage *v1alp
|
||||||
Query: errorPage.Query,
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -816,3 +826,8 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s
|
||||||
|
|
||||||
return eventsChanBuffered
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
|
@ -47,7 +49,8 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
||||||
ingressName = ingressRoute.GenerateName
|
ingressName = ingressRoute.GenerateName
|
||||||
}
|
}
|
||||||
|
|
||||||
cb := configBuilder{client}
|
cb := configBuilder{client, p.AllowCrossNamespace}
|
||||||
|
|
||||||
for _, route := range ingressRoute.Spec.Routes {
|
for _, route := range ingressRoute.Spec.Routes {
|
||||||
if route.Kind != "Rule" {
|
if route.Kind != "Rule" {
|
||||||
logger.Errorf("Unsupported match kind: %s. Only \"Rule\" is supported for now.", route.Kind)
|
logger.Errorf("Unsupported match kind: %s. Only \"Rule\" is supported for now.", route.Kind)
|
||||||
|
@ -65,23 +68,10 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var mds []string
|
mds, err := p.makeMiddlewareKeys(ctx, ingressRoute.Namespace, route.Middlewares)
|
||||||
for _, mi := range route.Middlewares {
|
if err != nil {
|
||||||
if strings.Contains(mi.Name, providerNamespaceSeparator) {
|
logger.Errorf("Failed to create middleware keys: %v", err)
|
||||||
if len(mi.Namespace) > 0 {
|
continue
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
normalized := provider.Normalize(makeID(ingressRoute.Namespace, serviceKey))
|
normalized := provider.Normalize(makeID(ingressRoute.Namespace, serviceKey))
|
||||||
|
@ -152,8 +142,38 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
||||||
return conf
|
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 {
|
type configBuilder struct {
|
||||||
client Client
|
client Client
|
||||||
|
allowCrossNamespace *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildTraefikService creates the configuration for the traefik service defined in tService,
|
// buildTraefikService creates the configuration for the traefik service defined in tService,
|
||||||
|
@ -270,7 +290,7 @@ func (c configBuilder) buildServersLB(namespace string, svc v1alpha1.LoadBalance
|
||||||
return &dynamic.Service{LoadBalancer: lb}, nil
|
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
|
strategy := svc.Strategy
|
||||||
if strategy == "" {
|
if strategy == "" {
|
||||||
strategy = roundRobinStrategy
|
strategy = roundRobinStrategy
|
||||||
|
@ -279,7 +299,11 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
||||||
return nil, fmt.Errorf("load balancing strategy %s is not supported", strategy)
|
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
|
// If the service uses explicitly the provider suffix
|
||||||
sanitizedName := strings.TrimSuffix(svc.Name, providerNamespaceSeparator+providerName)
|
sanitizedName := strings.TrimSuffix(svc.Name, providerNamespaceSeparator+providerName)
|
||||||
|
@ -303,8 +327,10 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hostPort := net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port)))
|
||||||
|
|
||||||
return append(servers, dynamic.Server{
|
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
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,8 +364,10 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, addr := range subset.Addresses {
|
for _, addr := range subset.Addresses {
|
||||||
|
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port)))
|
||||||
|
|
||||||
servers = append(servers, dynamic.Server{
|
servers = append(servers, dynamic.Server{
|
||||||
URL: fmt.Sprintf("%s://%s:%d", protocol, addr.IP, port),
|
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -351,10 +379,14 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
||||||
// In addition, if the service is a Kubernetes one,
|
// In addition, if the service is a Kubernetes one,
|
||||||
// it generates and returns the configuration part for such a service,
|
// it generates and returns the configuration part for such a service,
|
||||||
// so that the caller can add it to the global config map.
|
// 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))
|
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 {
|
switch {
|
||||||
case service.Kind == "" || service.Kind == "Service":
|
case service.Kind == "" || service.Kind == "Service":
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"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)
|
serviceName := makeID(ingressRouteTCP.Namespace, key)
|
||||||
|
|
||||||
for _, service := range route.Services {
|
for _, service := range route.Services {
|
||||||
balancerServerTCP, err := createLoadBalancerServerTCP(client, ingressRouteTCP.Namespace, service)
|
balancerServerTCP, err := p.createLoadBalancerServerTCP(client, ingressRouteTCP.Namespace, service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.
|
logger.
|
||||||
WithField("serviceName", service.Name).
|
WithField("serviceName", service.Name).
|
||||||
|
@ -123,9 +125,13 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
|
||||||
return conf
|
return conf
|
||||||
}
|
}
|
||||||
|
|
||||||
func createLoadBalancerServerTCP(client Client, namespace string, service v1alpha1.ServiceTCP) (*dynamic.TCPService, error) {
|
func (p *Provider) createLoadBalancerServerTCP(client Client, parentNamespace string, service v1alpha1.ServiceTCP) (*dynamic.TCPService, error) {
|
||||||
ns := namespace
|
ns := parentNamespace
|
||||||
if len(service.Namespace) > 0 {
|
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
|
ns = service.Namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +180,7 @@ func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([
|
||||||
var servers []dynamic.TCPServer
|
var servers []dynamic.TCPServer
|
||||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||||
servers = append(servers, dynamic.TCPServer{
|
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 {
|
} else {
|
||||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
|
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
|
||||||
|
@ -205,7 +211,7 @@ func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([
|
||||||
|
|
||||||
for _, addr := range subset.Addresses {
|
for _, addr := range subset.Addresses {
|
||||||
servers = append(servers, dynamic.TCPServer{
|
servers = append(servers, dynamic.TCPServer{
|
||||||
Address: fmt.Sprintf("%s:%d", addr.IP, port),
|
Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,23 @@ package crd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/traefik/paerser/types"
|
"github.com/traefik/paerser/types"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/provider"
|
"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"
|
"github.com/traefik/traefik/v2/pkg/tls"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ provider.Provider = (*Provider)(nil)
|
var _ provider.Provider = (*Provider)(nil)
|
||||||
|
@ -1010,6 +1018,129 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
||||||
TLS: &dynamic.TLSConfiguration{},
|
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{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
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 {
|
for _, test := range testCases {
|
||||||
|
@ -1023,7 +1154,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
p := Provider{IngressClass: test.ingressClass}
|
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)
|
assert.Equal(t, test.expected, conf)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -3205,7 +3339,10 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
p := Provider{IngressClass: test.ingressClass}
|
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)
|
assert.Equal(t, test.expected, conf)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -3519,7 +3656,10 @@ func TestLoadIngressRouteUDPs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
p := Provider{IngressClass: test.ingressClass}
|
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)
|
assert.Equal(t, test.expected, conf)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -3738,3 +3878,572 @@ 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{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
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{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
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{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
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{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
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{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
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{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
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{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
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{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
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{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
|
@ -34,7 +36,7 @@ func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client
|
||||||
serviceName := makeID(ingressRouteUDP.Namespace, key)
|
serviceName := makeID(ingressRouteUDP.Namespace, key)
|
||||||
|
|
||||||
for _, service := range route.Services {
|
for _, service := range route.Services {
|
||||||
balancerServerUDP, err := createLoadBalancerServerUDP(client, ingressRouteUDP.Namespace, service)
|
balancerServerUDP, err := p.createLoadBalancerServerUDP(client, ingressRouteUDP.Namespace, service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.
|
logger.
|
||||||
WithField("serviceName", service.Name).
|
WithField("serviceName", service.Name).
|
||||||
|
@ -75,9 +77,13 @@ func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client
|
||||||
return conf
|
return conf
|
||||||
}
|
}
|
||||||
|
|
||||||
func createLoadBalancerServerUDP(client Client, namespace string, service v1alpha1.ServiceUDP) (*dynamic.UDPService, error) {
|
func (p *Provider) createLoadBalancerServerUDP(client Client, parentNamespace string, service v1alpha1.ServiceUDP) (*dynamic.UDPService, error) {
|
||||||
ns := namespace
|
ns := parentNamespace
|
||||||
if len(service.Namespace) > 0 {
|
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
|
ns = service.Namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +127,7 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([
|
||||||
var servers []dynamic.UDPServer
|
var servers []dynamic.UDPServer
|
||||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||||
servers = append(servers, dynamic.UDPServer{
|
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 {
|
} else {
|
||||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
|
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 {
|
for _, addr := range subset.Addresses {
|
||||||
servers = append(servers, dynamic.UDPServer{
|
servers = append(servers, dynamic.UDPServer{
|
||||||
Address: fmt.Sprintf("%s:%d", addr.IP, port),
|
Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
|
@ -5,8 +5,10 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -479,9 +481,10 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr
|
||||||
|
|
||||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||||
protocol := getProtocol(portSpec, portSpec.Name, svcConfig)
|
protocol := getProtocol(portSpec, portSpec.Name, svcConfig)
|
||||||
|
hostPort := net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(portSpec.Port)))
|
||||||
|
|
||||||
svc.LoadBalancer.Servers = []dynamic.Server{
|
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
|
return svc, nil
|
||||||
|
@ -516,8 +519,10 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr
|
||||||
protocol := getProtocol(portSpec, portName, svcConfig)
|
protocol := getProtocol(portSpec, portName, svcConfig)
|
||||||
|
|
||||||
for _, addr := range subset.Addresses {
|
for _, addr := range subset.Addresses {
|
||||||
|
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port)))
|
||||||
|
|
||||||
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.Server{
|
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),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
desc: "TLS support",
|
||||||
expected: &dynamic.Configuration{
|
expected: &dynamic.Configuration{
|
||||||
|
|
|
@ -109,13 +109,18 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||||
tlsOptionsForHostSNI := map[string]map[string]nameAndConfig{}
|
tlsOptionsForHostSNI := map[string]map[string]nameAndConfig{}
|
||||||
tlsOptionsForHost := map[string]string{}
|
tlsOptionsForHost := map[string]string{}
|
||||||
for routerHTTPName, routerHTTPConfig := range configsHTTP {
|
for routerHTTPName, routerHTTPConfig := range configsHTTP {
|
||||||
if len(routerHTTPConfig.TLS.Options) == 0 || routerHTTPConfig.TLS.Options == defaultTLSConfigName {
|
if routerHTTPConfig.TLS == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxRouter := log.With(provider.AddInContext(ctx, routerHTTPName), log.Str(log.RouterName, routerHTTPName))
|
ctxRouter := log.With(provider.AddInContext(ctx, routerHTTPName), log.Str(log.RouterName, routerHTTPName))
|
||||||
logger := log.FromContext(ctxRouter)
|
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)
|
domains, err := rules.ParseDomains(routerHTTPConfig.Rule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
routerErr := fmt.Errorf("invalid rule %s, error: %w", routerHTTPConfig.Rule, err)
|
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 {
|
for _, domain := range domains {
|
||||||
if routerHTTPConfig.TLS != nil {
|
tlsConf, err := m.tlsManager.Get(defaultTLSStoreName, tlsOptionsName)
|
||||||
tlsOptionsName := routerHTTPConfig.TLS.Options
|
if err != nil {
|
||||||
if tlsOptionsName != defaultTLSConfigName {
|
routerHTTPConfig.AddError(err, true)
|
||||||
tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options)
|
logger.Debug(err)
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
tlsConf, err := m.tlsManager.Get(defaultTLSStoreName, tlsOptionsName)
|
// domain is already in lower case thanks to the domain parsing
|
||||||
if err != nil {
|
if tlsOptionsForHostSNI[domain] == nil {
|
||||||
routerHTTPConfig.AddError(err, true)
|
tlsOptionsForHostSNI[domain] = make(map[string]nameAndConfig)
|
||||||
logger.Debug(err)
|
}
|
||||||
continue
|
tlsOptionsForHostSNI[domain][tlsOptionsName] = nameAndConfig{
|
||||||
}
|
routerName: routerHTTPName,
|
||||||
|
TLSConfig: tlsConf,
|
||||||
|
}
|
||||||
|
|
||||||
// domain is already in lower case thanks to the domain parsing
|
if name, ok := tlsOptionsForHost[domain]; ok && name != tlsOptionsName {
|
||||||
if tlsOptionsForHostSNI[domain] == nil {
|
// Different tlsOptions on the same domain fallback to default
|
||||||
tlsOptionsForHostSNI[domain] = make(map[string]nameAndConfig)
|
tlsOptionsForHost[domain] = defaultTLSConfigName
|
||||||
}
|
} else {
|
||||||
tlsOptionsForHostSNI[domain][routerHTTPConfig.TLS.Options] = nameAndConfig{
|
tlsOptionsForHost[domain] = tlsOptionsName
|
||||||
routerName: routerHTTPName,
|
|
||||||
TLSConfig: tlsConf,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := tlsOptionsForHost[domain]; ok {
|
|
||||||
// Multiple tlsOptions fallback to default
|
|
||||||
tlsOptionsForHost[domain] = "default"
|
|
||||||
} else {
|
|
||||||
tlsOptionsForHost[domain] = routerHTTPConfig.TLS.Options
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,5 +302,5 @@ func findTLSOptionName(tlsOptionsForHost map[string]string, host string) string
|
||||||
return tlsOptions
|
return tlsOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
return "default"
|
return defaultTLSConfigName
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,25 +2,31 @@ package tcp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||||
"github.com/traefik/traefik/v2/pkg/server/service/tcp"
|
"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) {
|
func TestRuntimeConfiguration(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
serviceConfig map[string]*runtime.TCPServiceInfo
|
httpServiceConfig map[string]*runtime.ServiceInfo
|
||||||
routerConfig map[string]*runtime.TCPRouterInfo
|
httpRouterConfig map[string]*runtime.RouterInfo
|
||||||
expectedError int
|
tcpServiceConfig map[string]*runtime.TCPServiceInfo
|
||||||
|
tcpRouterConfig map[string]*runtime.TCPRouterInfo
|
||||||
|
expectedError int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "No error",
|
desc: "No error",
|
||||||
serviceConfig: map[string]*runtime.TCPServiceInfo{
|
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
|
||||||
"foo-service": {
|
"foo-service": {
|
||||||
TCPService: &dynamic.TCPService{
|
TCPService: &dynamic.TCPService{
|
||||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||||
|
@ -38,7 +44,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
routerConfig: map[string]*runtime.TCPRouterInfo{
|
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
|
||||||
"foo": {
|
"foo": {
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
EntryPoints: []string{"web"},
|
EntryPoints: []string{"web"},
|
||||||
|
@ -65,9 +71,54 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectedError: 0,
|
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",
|
desc: "One router with wrong rule",
|
||||||
serviceConfig: map[string]*runtime.TCPServiceInfo{
|
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
|
||||||
"foo-service": {
|
"foo-service": {
|
||||||
TCPService: &dynamic.TCPService{
|
TCPService: &dynamic.TCPService{
|
||||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||||
|
@ -80,7 +131,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
routerConfig: map[string]*runtime.TCPRouterInfo{
|
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
|
||||||
"foo": {
|
"foo": {
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
EntryPoints: []string{"web"},
|
EntryPoints: []string{"web"},
|
||||||
|
@ -101,7 +152,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "All router with wrong rule",
|
desc: "All router with wrong rule",
|
||||||
serviceConfig: map[string]*runtime.TCPServiceInfo{
|
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
|
||||||
"foo-service": {
|
"foo-service": {
|
||||||
TCPService: &dynamic.TCPService{
|
TCPService: &dynamic.TCPService{
|
||||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||||
|
@ -114,7 +165,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
routerConfig: map[string]*runtime.TCPRouterInfo{
|
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
|
||||||
"foo": {
|
"foo": {
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
EntryPoints: []string{"web"},
|
EntryPoints: []string{"web"},
|
||||||
|
@ -134,7 +185,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Router with unknown service",
|
desc: "Router with unknown service",
|
||||||
serviceConfig: map[string]*runtime.TCPServiceInfo{
|
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
|
||||||
"foo-service": {
|
"foo-service": {
|
||||||
TCPService: &dynamic.TCPService{
|
TCPService: &dynamic.TCPService{
|
||||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||||
|
@ -147,7 +198,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
routerConfig: map[string]*runtime.TCPRouterInfo{
|
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
|
||||||
"foo": {
|
"foo": {
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
EntryPoints: []string{"web"},
|
EntryPoints: []string{"web"},
|
||||||
|
@ -168,14 +219,14 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Router with broken service",
|
desc: "Router with broken service",
|
||||||
serviceConfig: map[string]*runtime.TCPServiceInfo{
|
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
|
||||||
"foo-service": {
|
"foo-service": {
|
||||||
TCPService: &dynamic.TCPService{
|
TCPService: &dynamic.TCPService{
|
||||||
LoadBalancer: nil,
|
LoadBalancer: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
routerConfig: map[string]*runtime.TCPRouterInfo{
|
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
|
||||||
"bar": {
|
"bar": {
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
EntryPoints: []string{"web"},
|
EntryPoints: []string{"web"},
|
||||||
|
@ -197,15 +248,17 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
entryPoints := []string{"web"}
|
entryPoints := []string{"web"}
|
||||||
|
|
||||||
conf := &runtime.Configuration{
|
conf := &runtime.Configuration{
|
||||||
TCPServices: test.serviceConfig,
|
Services: test.httpServiceConfig,
|
||||||
TCPRouters: test.routerConfig,
|
Routers: test.httpRouterConfig,
|
||||||
|
TCPServices: test.tcpServiceConfig,
|
||||||
|
TCPRouters: test.tcpRouterConfig,
|
||||||
}
|
}
|
||||||
serviceManager := tcp.NewManager(conf)
|
serviceManager := tcp.NewManager(conf)
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := traefiktls.NewManager()
|
||||||
tlsManager.UpdateConfigs(
|
tlsManager.UpdateConfigs(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
map[string]tls.Store{},
|
map[string]traefiktls.Store{},
|
||||||
map[string]tls.Options{
|
map[string]traefiktls.Options{
|
||||||
"default": {
|
"default": {
|
||||||
MinVersion: "VersionTLS10",
|
MinVersion: "VersionTLS10",
|
||||||
},
|
},
|
||||||
|
@ -216,7 +269,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
MinVersion: "VersionTLS11",
|
MinVersion: "VersionTLS11",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[]*tls.CertAndStores{})
|
[]*traefiktls.CertAndStores{})
|
||||||
|
|
||||||
routerManager := NewManager(conf, serviceManager,
|
routerManager := NewManager(conf, serviceManager,
|
||||||
nil, nil, tlsManager)
|
nil, nil, tlsManager)
|
||||||
|
@ -237,7 +290,235 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
allErrors++
|
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)
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@ RepositoryName = "traefik"
|
||||||
OutputType = "file"
|
OutputType = "file"
|
||||||
FileName = "traefik_changelog.md"
|
FileName = "traefik_changelog.md"
|
||||||
|
|
||||||
# example new bugfix v2.3.3
|
# example new bugfix v2.3.5
|
||||||
CurrentRef = "v2.3"
|
CurrentRef = "v2.3"
|
||||||
PreviousRef = "v2.3.2"
|
PreviousRef = "v2.3.4"
|
||||||
BaseBranch = "v2.3"
|
BaseBranch = "v2.3"
|
||||||
FutureCurrentRefName = "v2.3.3"
|
FutureCurrentRefName = "v2.3.5"
|
||||||
|
|
||||||
ThresholdPreviousRef = 10
|
ThresholdPreviousRef = 10
|
||||||
ThresholdCurrentRef = 10
|
ThresholdCurrentRef = 10
|
||||||
|
|
|
@ -231,8 +231,9 @@
|
||||||
<div class="text-subtitle2">Service</div>
|
<div class="text-subtitle2">Service</div>
|
||||||
<q-chip
|
<q-chip
|
||||||
dense
|
dense
|
||||||
class="app-chip app-chip-green">
|
class="app-chip app-chip-green app-chip-overflow">
|
||||||
{{ exData(middleware).service }}
|
{{ exData(middleware).service }}
|
||||||
|
<q-tooltip>{{ exData(middleware).service }}</q-tooltip>
|
||||||
</q-chip>
|
</q-chip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -21,8 +21,9 @@
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<q-chip
|
<q-chip
|
||||||
dense
|
dense
|
||||||
class="app-chip app-chip-rule">
|
class="app-chip app-chip-rule app-chip-overflow">
|
||||||
{{ service.name }}
|
{{ service.name }}
|
||||||
|
<q-tooltip>{{service.name}}</q-tooltip>
|
||||||
</q-chip>
|
</q-chip>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 text-right">
|
<div class="col-3 text-right">
|
||||||
|
|
|
@ -66,8 +66,9 @@
|
||||||
dense
|
dense
|
||||||
clickable
|
clickable
|
||||||
@click.native="$router.push({ path: `/${protocol}/services/${getServiceId()}`})"
|
@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 }}
|
{{ data.service }}
|
||||||
|
<q-tooltip>{{ data.service }}</q-tooltip>
|
||||||
</q-chip>
|
</q-chip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -21,8 +21,9 @@
|
||||||
<div class="col-7">
|
<div class="col-7">
|
||||||
<q-chip
|
<q-chip
|
||||||
dense
|
dense
|
||||||
class="app-chip app-chip-rule">
|
class="app-chip app-chip-rule app-chip-overflow">
|
||||||
{{ service.name }}
|
{{ service.name }}
|
||||||
|
<q-tooltip>{{service.name}}</q-tooltip>
|
||||||
</q-chip>
|
</q-chip>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
|
|
|
@ -131,6 +131,16 @@ body {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&-overflow {
|
||||||
|
max-width: 90%;
|
||||||
|
|
||||||
|
.q-chip__content{
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
&-accent, &-rule {
|
&-accent, &-rule {
|
||||||
color: $accent;
|
color: $accent;
|
||||||
background-color: rgba($accent, 0.1);
|
background-color: rgba($accent, 0.1);
|
||||||
|
|
Loading…
Reference in a new issue