forked from Ivasoft/traefik
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ff83fc1f8 | ||
|
|
63f65e5b2a | ||
|
|
ac8e47579b | ||
|
|
5487015a83 | ||
|
|
418cccd307 | ||
|
|
2a0760412c | ||
|
|
42d8e6d60d | ||
|
|
7ba907f261 | ||
|
|
c72769e2ea | ||
|
|
02d856b8a5 | ||
|
|
0d15ac8861 | ||
|
|
134a767a7f | ||
|
|
7403b6fb82 | ||
|
|
121eaced49 | ||
|
|
512ed086bd |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,3 +1,30 @@
|
||||
## [v2.3.6](https://github.com/traefik/traefik/tree/v2.3.6) (2020-12-17)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.3.5...v2.3.6)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[logs]** Update Logrus to v1.7.0 ([#7663](https://github.com/traefik/traefik/pull/7663) by [jspdown](https://github.com/jspdown))
|
||||
- **[plugins]** Update Yaegi to v0.9.8 ([#7659](https://github.com/traefik/traefik/pull/7659) by [ldez](https://github.com/ldez))
|
||||
- **[rules]** Disable router when a rule has an error ([#7680](https://github.com/traefik/traefik/pull/7680) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[logs]** Add configuration example for access log filePath ([#7655](https://github.com/traefik/traefik/pull/7655) by [wernerfred](https://github.com/wernerfred))
|
||||
- **[middleware]** Add missing quotes in errorpages k8s example yaml ([#7675](https://github.com/traefik/traefik/pull/7675) by [icelynjennings](https://github.com/icelynjennings))
|
||||
|
||||
## [v2.3.5](https://github.com/traefik/traefik/tree/v2.3.5) (2020-12-10)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.3.4...v2.3.5)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Update go-acme/lego to v4.1.3 ([#7625](https://github.com/traefik/traefik/pull/7625) by [ldez](https://github.com/ldez))
|
||||
- **[k8s,k8s/crd]** IngressRoute: add an option to disable cross-namespace routing ([#7595](https://github.com/traefik/traefik/pull/7595) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[k8s/crd,k8s/ingress]** Fix concatenation of IPv6 addresses and ports ([#7620](https://github.com/traefik/traefik/pull/7620) by [jspdown](https://github.com/jspdown))
|
||||
- **[tcp,tls]** Fix TLS options fallback when domain and options are the same ([#7609](https://github.com/traefik/traefik/pull/7609) by [jspdown](https://github.com/jspdown))
|
||||
- **[webui]** Fix UI bug on long service name ([#7535](https://github.com/traefik/traefik/pull/7535) by [ipinak](https://github.com/ipinak))
|
||||
|
||||
**Documentation:**
|
||||
- **[docker]** Add example for multiple service per container ([#7610](https://github.com/traefik/traefik/pull/7610) by [notsureifkevin](https://github.com/notsureifkevin))
|
||||
- Documentation: Add spacing to sidebars so the last item is always visible ([#7616](https://github.com/traefik/traefik/pull/7616) by [paulocfjunior](https://github.com/paulocfjunior))
|
||||
- Fix typos in migration guide ([#7596](https://github.com/traefik/traefik/pull/7596) by [marsavela](https://github.com/marsavela))
|
||||
|
||||
## [v2.3.4](https://github.com/traefik/traefik/tree/v2.3.4) (2020-11-24)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.3.3...v2.3.4)
|
||||
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.md-sidebar__scrollwrap {
|
||||
max-height: calc(100% - 50px);
|
||||
}
|
||||
|
||||
.md-sidebar--secondary .md-sidebar__scrollwrap {
|
||||
border-radius: 8px;
|
||||
background-color: var(--light-blue) !important;
|
||||
|
||||
@@ -28,7 +28,7 @@ metadata:
|
||||
spec:
|
||||
errors:
|
||||
status:
|
||||
- 500-599
|
||||
- "500-599"
|
||||
query: /{status}.html
|
||||
service:
|
||||
name: whoami
|
||||
|
||||
@@ -385,7 +385,7 @@ To apply a redirection:
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: 80
|
||||
address: ":80"
|
||||
http:
|
||||
redirections:
|
||||
entrypoint:
|
||||
@@ -393,7 +393,7 @@ To apply a redirection:
|
||||
scheme: https
|
||||
|
||||
websecure:
|
||||
address: 443
|
||||
address: ":443"
|
||||
```
|
||||
|
||||
!!! example "HTTP to HTTPS redirection per domain"
|
||||
|
||||
@@ -26,6 +26,20 @@ accessLog: {}
|
||||
By default access logs are written to the standard output.
|
||||
To write the logs into a log file, use the `filePath` option.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
accessLog:
|
||||
filePath: "/path/to/access.log"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--accesslog.filepath=/path/to/access.log
|
||||
```
|
||||
|
||||
### `format`
|
||||
|
||||
By default, logs are written using the Common Log Format (CLF).
|
||||
@@ -60,7 +74,6 @@ accessLog:
|
||||
|
||||
```bash tab="CLI"
|
||||
# Configuring a buffer of 100 lines
|
||||
--accesslog=true
|
||||
--accesslog.filepath=/path/to/access.log
|
||||
--accesslog.bufferingsize=100
|
||||
```
|
||||
@@ -103,7 +116,6 @@ accessLog:
|
||||
|
||||
```bash tab="CLI"
|
||||
# Configuring Multiple Filters
|
||||
--accesslog=true
|
||||
--accesslog.filepath=/path/to/access.log
|
||||
--accesslog.format=json
|
||||
--accesslog.filters.statuscodes=200,300-302
|
||||
@@ -163,7 +175,6 @@ accessLog:
|
||||
|
||||
```bash tab="CLI"
|
||||
# Limiting the Logs to Specific Fields
|
||||
--accesslog=true
|
||||
--accesslog.filepath=/path/to/access.log
|
||||
--accesslog.format=json
|
||||
--accesslog.fields.defaultmode=keep
|
||||
@@ -238,7 +249,6 @@ services:
|
||||
environment:
|
||||
- TZ=US/Alaska
|
||||
command:
|
||||
- --accesslog
|
||||
- --accesslog.fields.names.StartUTC=drop
|
||||
- --providers.docker
|
||||
ports:
|
||||
|
||||
@@ -250,6 +250,34 @@ providers:
|
||||
--providers.kubernetescrd.throttleDuration=10s
|
||||
```
|
||||
|
||||
### `allowCrossNamespace`
|
||||
|
||||
_Optional, Default: true_
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.kubernetesCRD]
|
||||
allowCrossNamespace = false
|
||||
# ...
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
kubernetesCRD:
|
||||
allowCrossNamespace: false
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetescrd.allowCrossNamespace=false
|
||||
```
|
||||
|
||||
If the parameter is set to `false`, an IngressRoute will not be able to reference any resources
|
||||
in another namespace than the IngressRoute namespace.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
Please notice that the default value for this option will be set to `false` in a future version.
|
||||
|
||||
## Further
|
||||
|
||||
Also see the [full example](../user-guides/crd-acme/index.md) with Let's Encrypt.
|
||||
|
||||
@@ -540,6 +540,9 @@ TLS key
|
||||
`--providers.kubernetescrd`:
|
||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||
|
||||
`--providers.kubernetescrd.allowcrossnamespace`:
|
||||
Allow cross namespace resource reference. (Default: ```true```)
|
||||
|
||||
`--providers.kubernetescrd.certauthfilepath`:
|
||||
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||
|
||||
|
||||
@@ -540,6 +540,9 @@ TLS key
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD`:
|
||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD_ALLOWCROSSNAMESPACE`:
|
||||
Allow cross namespace resource reference. (Default: ```true```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD_CERTAUTHFILEPATH`:
|
||||
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
certAuthFilePath = "foobar"
|
||||
disablePassHostHeaders = true
|
||||
namespaces = ["foobar", "foobar"]
|
||||
allowCrossNamespace = true
|
||||
labelSelector = "foobar"
|
||||
ingressClass = "foobar"
|
||||
throttleDuration = 42
|
||||
|
||||
@@ -123,6 +123,7 @@ providers:
|
||||
namespaces:
|
||||
- foobar
|
||||
- foobar
|
||||
allowCrossNamespace: true
|
||||
labelSelector: foobar
|
||||
ingressClass: foobar
|
||||
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`
|
||||
overrides that behavior.
|
||||
|
||||
??? example "Specifying more than one router and service per container"
|
||||
|
||||
Forwarding requests to more than one port on a container requires referencing the service loadbalancer port definition using the service parameter on the router.
|
||||
|
||||
In this example, requests are forwarded for `http://example-a.com` to `http://<private IP of container>:8000` in addition to `http://example-b.com` forwarding to `http://<private IP of container>:9000`:
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
my-container:
|
||||
# ...
|
||||
labels:
|
||||
- traefik.http.routers.www-router.rule=Host(`example-a.com`)
|
||||
- traefik.http.routers.www-router.service=www-service
|
||||
- traefik.http.services.www-service.loadbalancer.server.port=8000
|
||||
- traefik.http.routers.admin-router.rule=Host(`example-b.com`)
|
||||
- traefik.http.routers.admin-router.service=admin-service
|
||||
- traefik.http.services.admin-service.loadbalancer.server.port=9000
|
||||
```
|
||||
|
||||
??? example "Configuring Docker Swarm & Deploying / Exposing Services"
|
||||
|
||||
Enabling the docker provider (Swarm Mode)
|
||||
|
||||
6
go.mod
6
go.mod
@@ -35,7 +35,7 @@ require (
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2
|
||||
github.com/go-acme/lego/v4 v4.1.2
|
||||
github.com/go-acme/lego/v4 v4.1.3
|
||||
github.com/go-check/check v0.0.0-00010101000000-000000000000
|
||||
github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea
|
||||
github.com/golang/protobuf v1.3.4
|
||||
@@ -68,12 +68,12 @@ require (
|
||||
github.com/prometheus/client_golang v1.3.0
|
||||
github.com/prometheus/client_model v0.1.0
|
||||
github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
|
||||
github.com/tinylib/msgp v1.0.2 // indirect
|
||||
github.com/traefik/paerser v0.1.1
|
||||
github.com/traefik/yaegi v0.9.7
|
||||
github.com/traefik/yaegi v0.9.8
|
||||
github.com/uber/jaeger-client-go v2.25.0+incompatible
|
||||
github.com/uber/jaeger-lib v2.2.0+incompatible
|
||||
github.com/unrolled/render v1.0.2
|
||||
|
||||
11
go.sum
11
go.sum
@@ -275,8 +275,8 @@ github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2/go.mod h1:GLy
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-acme/lego/v4 v4.1.2 h1:1zROppXkTbAIh7J7AydGD3dFICLIocucJY1NTH/wB64=
|
||||
github.com/go-acme/lego/v4 v4.1.2/go.mod h1:pIFm5tWkXSgiAEfJ/XQCQIvX1cEvHFwbgLZyx8OVSUE=
|
||||
github.com/go-acme/lego/v4 v4.1.3 h1:D8nnzrijQFUAqdNPwnbvm6tJ3AJAzQAlnROeecUNG/4=
|
||||
github.com/go-acme/lego/v4 v4.1.3/go.mod h1:pIFm5tWkXSgiAEfJ/XQCQIvX1cEvHFwbgLZyx8OVSUE=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
@@ -725,6 +725,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
@@ -766,8 +768,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/traefik/paerser v0.1.1 h1:Suj0iA4hTAV6E4Dh5/++TXAj5u6iTwydBlFssIUz+9w=
|
||||
github.com/traefik/paerser v0.1.1/go.mod h1:yYnAgdEC2wJH5CgG75qGWC8SsFDEapg09o9RrA6FfrE=
|
||||
github.com/traefik/yaegi v0.9.7 h1:CbeKjEhy3DoSC8xC4TQF2Mhmd7u3Cjqluz1//x6Vtcs=
|
||||
github.com/traefik/yaegi v0.9.7/go.mod h1:FAYnRlZyuVlEkvnkHq3bvJ1lW5be6XuwgLdkYgYG6Lk=
|
||||
github.com/traefik/yaegi v0.9.8 h1:sYGKV2d911B9olVsSO/nRmjIh+CQTffAUSzoyv4a4/4=
|
||||
github.com/traefik/yaegi v0.9.8/go.mod h1:FAYnRlZyuVlEkvnkHq3bvJ1lW5be6XuwgLdkYgYG6Lk=
|
||||
github.com/transip/gotransip/v6 v6.2.0 h1:0Z+qVsyeiQdWfcAUeJyF0IEKAPvhJwwpwPi2WGtBIiE=
|
||||
github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
@@ -972,6 +974,7 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
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"
|
||||
|
||||
[providers.kubernetesCRD]
|
||||
allowCrossNamespace = false
|
||||
|
||||
@@ -53,6 +53,7 @@ func (s *K8sSuite) TearDownSuite(c *check.C) {
|
||||
"./fixtures/k8s/coredns.yaml",
|
||||
"./fixtures/k8s/rolebindings.yaml",
|
||||
"./fixtures/k8s/traefik.yaml",
|
||||
"./fixtures/k8s/ccm.yaml",
|
||||
}
|
||||
|
||||
for _, filename := range generatedFiles {
|
||||
|
||||
33
integration/testdata/rawdata-crd.json
vendored
33
integration/testdata/rawdata-crd.json
vendored
@@ -50,6 +50,20 @@
|
||||
"using": [
|
||||
"web"
|
||||
]
|
||||
},
|
||||
"other-ns-test6-route-482e4988e134701d8cc8@kubernetescrd": {
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"service": "other-ns-wrr3",
|
||||
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/c`)",
|
||||
"error": [
|
||||
"the service \"other-ns-wrr3@kubernetescrd\" does not exist"
|
||||
],
|
||||
"status": "disabled",
|
||||
"using": [
|
||||
"web"
|
||||
]
|
||||
}
|
||||
},
|
||||
"middlewares": {
|
||||
@@ -64,6 +78,14 @@
|
||||
"default-test2-route-23c7f4c450289ee29016@kubernetescrd"
|
||||
]
|
||||
},
|
||||
"default-stripprefix2@kubernetescrd": {
|
||||
"stripPrefix": {
|
||||
"prefixes": [
|
||||
"/tobestripped"
|
||||
]
|
||||
},
|
||||
"status": "enabled"
|
||||
},
|
||||
"default-stripprefix@kubernetescrd": {
|
||||
"stripPrefix": {
|
||||
"prefixes": [
|
||||
@@ -172,6 +194,17 @@
|
||||
"default-test3-route-7d0ac22d3d8db4b82618@kubernetescrd"
|
||||
]
|
||||
},
|
||||
"default-wrr2@kubernetescrd": {
|
||||
"weighted": {
|
||||
"services": [
|
||||
{
|
||||
"name": "default-whoami-80",
|
||||
"weight": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": "enabled"
|
||||
},
|
||||
"noop@internal": {
|
||||
"status": "enabled"
|
||||
}
|
||||
|
||||
@@ -119,6 +119,35 @@ subsets:
|
||||
- name: websecure2
|
||||
port: 8443
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoami-ipv6
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 8080
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoami-ipv6
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoami-ipv6
|
||||
namespace: default
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: "2001:db8:85a3:8d3:1319:8a2e:370:7348"
|
||||
ports:
|
||||
- name: web
|
||||
port: 8080
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -157,5 +186,46 @@ spec:
|
||||
protocol: TCP
|
||||
port: 443
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: external-svc-with-ipv6
|
||||
namespace: default
|
||||
spec:
|
||||
externalName: "2001:db8:85a3:8d3:1319:8a2e:370:7347"
|
||||
type: ExternalName
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 8080
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoami-svc
|
||||
namespace: cross-ns
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 80
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoami
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoami-svc
|
||||
namespace: cross-ns
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
- ip: 10.10.0.2
|
||||
ports:
|
||||
- name: web
|
||||
port: 80
|
||||
|
||||
@@ -132,6 +132,36 @@ subsets:
|
||||
- name: myapp4
|
||||
port: 8084
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoamitcp-ipv6
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: myapp-ipv6
|
||||
port: 8080
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoamitcp-ipv6
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoamitcp-ipv6
|
||||
namespace: default
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: "fd00:10:244:0:1::3"
|
||||
- ip: "2001:db8:85a3:8d3:1319:8a2e:370:7348"
|
||||
ports:
|
||||
- name: myapp-ipv6
|
||||
port: 8080
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -167,4 +197,44 @@ spec:
|
||||
type: ExternalName
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
protocol: TCP
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: external.service.with.ipv6
|
||||
namespace: default
|
||||
spec:
|
||||
externalName: "fe80::200:5aee:feaa:20a2"
|
||||
type: ExternalName
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoamitcp-cross-ns
|
||||
namespace: cross-ns
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: myapp
|
||||
port: 8000
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoamitcp
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoamitcp-cross-ns
|
||||
namespace: cross-ns
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
- ip: 10.10.0.2
|
||||
ports:
|
||||
- name: myapp
|
||||
port: 8000
|
||||
|
||||
@@ -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:
|
||||
- name: myapp4
|
||||
port: 8084
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoamiudp-ipv6
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: myapp-ipv6
|
||||
port: 8080
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoamiudp-ipv6
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoamiudp-ipv6
|
||||
namespace: default
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: "fd00:10:244:0:1::3"
|
||||
ports:
|
||||
- name: myapp-ipv6
|
||||
port: 8080
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoamiudp-cross-ns
|
||||
namespace: cross-ns
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: myapp
|
||||
port: 8000
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoamiudp
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoamiudp-cross-ns
|
||||
namespace: cross-ns
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
- ip: 10.10.0.2
|
||||
ports:
|
||||
- name: myapp
|
||||
port: 8000
|
||||
|
||||
@@ -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,6 +43,7 @@ type Provider struct {
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers." json:"disablePassHostHeaders,omitempty" toml:"disablePassHostHeaders,omitempty" yaml:"disablePassHostHeaders,omitempty" export:"true"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
|
||||
AllowCrossNamespace *bool `description:"Allow cross namespace resource reference." json:"allowCrossNamespace,omitempty" toml:"allowCrossNamespace,omitempty" yaml:"allowCrossNamespace,omitempty" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"`
|
||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
||||
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||
@@ -82,6 +83,11 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.AllowCrossNamespace = func(b bool) *bool { return &b }(true)
|
||||
}
|
||||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
return nil
|
||||
@@ -98,6 +104,10 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||
return err
|
||||
}
|
||||
|
||||
if p.AllowCrossNamespace == nil || *p.AllowCrossNamespace {
|
||||
logger.Warn("Cross-namespace reference between IngressRoutes and resources is enabled, please ensure that this is expected (see AllowCrossNamespace option)")
|
||||
}
|
||||
|
||||
pool.GoCtx(func(ctxPool context.Context) {
|
||||
operation := func() error {
|
||||
eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done())
|
||||
@@ -197,7 +207,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
||||
continue
|
||||
}
|
||||
|
||||
errorPage, errorPageService, err := createErrorPageMiddleware(client, middleware.Namespace, middleware.Spec.Errors)
|
||||
errorPage, errorPageService, err := p.createErrorPageMiddleware(client, middleware.Namespace, middleware.Spec.Errors)
|
||||
if err != nil {
|
||||
log.FromContext(ctxMid).Errorf("Error while reading error page middleware: %v", err)
|
||||
continue
|
||||
@@ -236,7 +246,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
||||
}
|
||||
}
|
||||
|
||||
cb := configBuilder{client}
|
||||
cb := configBuilder{client, p.AllowCrossNamespace}
|
||||
for _, service := range client.GetTraefikServices() {
|
||||
err := cb.buildTraefikService(ctx, service, conf.HTTP.Services)
|
||||
if err != nil {
|
||||
@@ -281,7 +291,7 @@ func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error
|
||||
return &corev1.ServicePort{Port: port}, nil
|
||||
}
|
||||
|
||||
func createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) {
|
||||
func (p *Provider) createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) {
|
||||
if errorPage == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
@@ -291,7 +301,7 @@ func createErrorPageMiddleware(client Client, namespace string, errorPage *v1alp
|
||||
Query: errorPage.Query,
|
||||
}
|
||||
|
||||
balancerServerHTTP, err := configBuilder{client}.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
|
||||
balancerServerHTTP, err := configBuilder{client, p.AllowCrossNamespace}.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -749,3 +759,8 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s
|
||||
|
||||
return eventsChanBuffered
|
||||
}
|
||||
|
||||
func isNamespaceAllowed(allowCrossNamespace *bool, parentNamespace, namespace string) bool {
|
||||
// If allowCrossNamespace option is not defined the default behavior is to allow cross namespace references.
|
||||
return allowCrossNamespace == nil || *allowCrossNamespace || parentNamespace == namespace
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
@@ -46,7 +48,8 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
||||
ingressName = ingressRoute.GenerateName
|
||||
}
|
||||
|
||||
cb := configBuilder{client}
|
||||
cb := configBuilder{client, p.AllowCrossNamespace}
|
||||
|
||||
for _, route := range ingressRoute.Spec.Routes {
|
||||
if route.Kind != "Rule" {
|
||||
logger.Errorf("Unsupported match kind: %s. Only \"Rule\" is supported for now.", route.Kind)
|
||||
@@ -64,23 +67,10 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
||||
continue
|
||||
}
|
||||
|
||||
var mds []string
|
||||
for _, mi := range route.Middlewares {
|
||||
if strings.Contains(mi.Name, providerNamespaceSeparator) {
|
||||
if len(mi.Namespace) > 0 {
|
||||
logger.
|
||||
WithField(log.MiddlewareName, mi.Name).
|
||||
Warnf("namespace %q is ignored in cross-provider context", mi.Namespace)
|
||||
}
|
||||
mds = append(mds, mi.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
ns := mi.Namespace
|
||||
if len(ns) == 0 {
|
||||
ns = ingressRoute.Namespace
|
||||
}
|
||||
mds = append(mds, makeID(ns, mi.Name))
|
||||
mds, err := p.makeMiddlewareKeys(ctx, ingressRoute.Namespace, route.Middlewares)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to create middleware keys: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
normalized := provider.Normalize(makeID(ingressRoute.Namespace, serviceKey))
|
||||
@@ -151,8 +141,38 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
||||
return conf
|
||||
}
|
||||
|
||||
func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace string, middlewares []v1alpha1.MiddlewareRef) ([]string, error) {
|
||||
var mds []string
|
||||
|
||||
for _, mi := range middlewares {
|
||||
if strings.Contains(mi.Name, providerNamespaceSeparator) {
|
||||
if len(mi.Namespace) > 0 {
|
||||
log.FromContext(ctx).
|
||||
WithField(log.MiddlewareName, mi.Name).
|
||||
Warnf("namespace %q is ignored in cross-provider context", mi.Namespace)
|
||||
}
|
||||
mds = append(mds, mi.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
ns := ingRouteNamespace
|
||||
if len(mi.Namespace) > 0 {
|
||||
if !isNamespaceAllowed(p.AllowCrossNamespace, ingRouteNamespace, mi.Namespace) {
|
||||
return nil, fmt.Errorf("middleware %s/%s is not in the IngressRoute namespace %s", mi.Namespace, mi.Name, ingRouteNamespace)
|
||||
}
|
||||
|
||||
ns = mi.Namespace
|
||||
}
|
||||
|
||||
mds = append(mds, makeID(ns, mi.Name))
|
||||
}
|
||||
|
||||
return mds, nil
|
||||
}
|
||||
|
||||
type configBuilder struct {
|
||||
client Client
|
||||
client Client
|
||||
allowCrossNamespace *bool
|
||||
}
|
||||
|
||||
// buildTraefikService creates the configuration for the traefik service defined in tService,
|
||||
@@ -268,7 +288,7 @@ func (c configBuilder) buildServersLB(namespace string, svc v1alpha1.LoadBalance
|
||||
return &dynamic.Service{LoadBalancer: lb}, nil
|
||||
}
|
||||
|
||||
func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBalancerSpec) ([]dynamic.Server, error) {
|
||||
func (c configBuilder) loadServers(parentNamespace string, svc v1alpha1.LoadBalancerSpec) ([]dynamic.Server, error) {
|
||||
strategy := svc.Strategy
|
||||
if strategy == "" {
|
||||
strategy = roundRobinStrategy
|
||||
@@ -277,7 +297,11 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
||||
return nil, fmt.Errorf("load balancing strategy %s is not supported", strategy)
|
||||
}
|
||||
|
||||
namespace := namespaceOrFallback(svc, fallbackNamespace)
|
||||
namespace := namespaceOrFallback(svc, parentNamespace)
|
||||
|
||||
if !isNamespaceAllowed(c.allowCrossNamespace, parentNamespace, namespace) {
|
||||
return nil, fmt.Errorf("load balancer service %s/%s is not in the parent resource namespace %s", svc.Namespace, svc.Name, parentNamespace)
|
||||
}
|
||||
|
||||
// If the service uses explicitly the provider suffix
|
||||
sanitizedName := strings.TrimSuffix(svc.Name, providerNamespaceSeparator+providerName)
|
||||
@@ -301,8 +325,10 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostPort := net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port)))
|
||||
|
||||
return append(servers, dynamic.Server{
|
||||
URL: fmt.Sprintf("%s://%s:%d", protocol, service.Spec.ExternalName, svcPort.Port),
|
||||
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
|
||||
}), nil
|
||||
}
|
||||
|
||||
@@ -336,8 +362,10 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
||||
}
|
||||
|
||||
for _, addr := range subset.Addresses {
|
||||
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port)))
|
||||
|
||||
servers = append(servers, dynamic.Server{
|
||||
URL: fmt.Sprintf("%s://%s:%d", protocol, addr.IP, port),
|
||||
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -349,10 +377,14 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
||||
// In addition, if the service is a Kubernetes one,
|
||||
// it generates and returns the configuration part for such a service,
|
||||
// so that the caller can add it to the global config map.
|
||||
func (c configBuilder) nameAndService(ctx context.Context, namespaceService string, service v1alpha1.LoadBalancerSpec) (string, *dynamic.Service, error) {
|
||||
func (c configBuilder) nameAndService(ctx context.Context, parentNamespace string, service v1alpha1.LoadBalancerSpec) (string, *dynamic.Service, error) {
|
||||
svcCtx := log.With(ctx, log.Str(log.ServiceName, service.Name))
|
||||
|
||||
namespace := namespaceOrFallback(service, namespaceService)
|
||||
namespace := namespaceOrFallback(service, parentNamespace)
|
||||
|
||||
if !isNamespaceAllowed(c.allowCrossNamespace, parentNamespace, namespace) {
|
||||
return "", nil, fmt.Errorf("service %s/%s not in the parent resource namespace %s", service.Namespace, service.Name, parentNamespace)
|
||||
}
|
||||
|
||||
switch {
|
||||
case service.Kind == "" || service.Kind == "Service":
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
@@ -53,7 +55,7 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
|
||||
serviceName := makeID(ingressRouteTCP.Namespace, key)
|
||||
|
||||
for _, service := range route.Services {
|
||||
balancerServerTCP, err := createLoadBalancerServerTCP(client, ingressRouteTCP.Namespace, service)
|
||||
balancerServerTCP, err := p.createLoadBalancerServerTCP(client, ingressRouteTCP.Namespace, service)
|
||||
if err != nil {
|
||||
logger.
|
||||
WithField("serviceName", service.Name).
|
||||
@@ -123,9 +125,13 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
|
||||
return conf
|
||||
}
|
||||
|
||||
func createLoadBalancerServerTCP(client Client, namespace string, service v1alpha1.ServiceTCP) (*dynamic.TCPService, error) {
|
||||
ns := namespace
|
||||
func (p *Provider) createLoadBalancerServerTCP(client Client, parentNamespace string, service v1alpha1.ServiceTCP) (*dynamic.TCPService, error) {
|
||||
ns := parentNamespace
|
||||
if len(service.Namespace) > 0 {
|
||||
if !isNamespaceAllowed(p.AllowCrossNamespace, parentNamespace, service.Namespace) {
|
||||
return nil, fmt.Errorf("tcp service %s/%s is not in the parent resource namespace %s", service.Namespace, service.Name, parentNamespace)
|
||||
}
|
||||
|
||||
ns = service.Namespace
|
||||
}
|
||||
|
||||
@@ -165,7 +171,7 @@ func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([
|
||||
var servers []dynamic.TCPServer
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
servers = append(servers, dynamic.TCPServer{
|
||||
Address: fmt.Sprintf("%s:%d", service.Spec.ExternalName, svcPort.Port),
|
||||
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
|
||||
})
|
||||
} else {
|
||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
|
||||
@@ -196,7 +202,7 @@ func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([
|
||||
|
||||
for _, addr := range subset.Addresses {
|
||||
servers = append(servers, dynamic.TCPServer{
|
||||
Address: fmt.Sprintf("%s:%d", addr.IP, port),
|
||||
Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,21 @@ package crd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/provider"
|
||||
crdfake "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/k8s"
|
||||
"github.com/traefik/traefik/v2/pkg/tls"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
@@ -987,6 +995,128 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress Route with IPv6 backends",
|
||||
paths: []string{
|
||||
"services.yml", "with_ipv6.yml",
|
||||
"tcp/services.yml", "tcp/with_ipv6.yml",
|
||||
"udp/services.yml", "udp/with_ipv6.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{
|
||||
"default-test.route-0": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-0",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.UDPService{
|
||||
"default-test.route-0": {
|
||||
LoadBalancer: &dynamic.UDPServersLoadBalancer{
|
||||
Servers: []dynamic.UDPServer{
|
||||
{
|
||||
Address: "[fd00:10:244:0:1::3]:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"default-test.route-673acf455cb2dab0b43a": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-673acf455cb2dab0b43a",
|
||||
Rule: "HostSNI(`*`)",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"default-test.route-673acf455cb2dab0b43a": {
|
||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||
Services: []dynamic.TCPWRRService{
|
||||
{
|
||||
Name: "default-test.route-673acf455cb2dab0b43a-whoamitcp-ipv6-8080",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-test.route-673acf455cb2dab0b43a-external.service.with.ipv6-8080",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test.route-673acf455cb2dab0b43a-whoamitcp-ipv6-8080": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "[fd00:10:244:0:1::3]:8080",
|
||||
},
|
||||
{
|
||||
Address: "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test.route-673acf455cb2dab0b43a-external.service.with.ipv6-8080": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "[fe80::200:5aee:feaa:20a2]:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-route-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-whoami-ipv6-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: func(i bool) *bool { return &i }(true),
|
||||
},
|
||||
},
|
||||
"default-external-svc-with-ipv6-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:db8:85a3:8d3:1319:8a2e:370:7347]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: func(i bool) *bool { return &i }(true),
|
||||
},
|
||||
},
|
||||
"default-test-route-6b204d94623b3df4370c": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "default-whoami-ipv6-8080",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-external-svc-with-ipv6-8080",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
@@ -1000,7 +1130,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
||||
}
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...))
|
||||
p.SetDefaults()
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
@@ -3065,7 +3198,10 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||
}
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...))
|
||||
p.SetDefaults()
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
@@ -3373,7 +3509,10 @@ func TestLoadIngressRouteUDPs(t *testing.T) {
|
||||
}
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...))
|
||||
p.SetDefaults()
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
@@ -3592,3 +3731,563 @@ func TestGetServicePort(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossNamespace(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
allowCrossNamespace bool
|
||||
ingressClass string
|
||||
paths []string
|
||||
expected *dynamic.Configuration
|
||||
}{
|
||||
{
|
||||
desc: "Empty",
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP middleware cross namespace disallowed",
|
||||
paths: []string{"services.yml", "with_middleware_cross_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-9313b71dbe6a649d5049",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bir`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{"default-test-errorpage"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"cross-ns-stripprefix": {
|
||||
StripPrefix: &dynamic.StripPrefix{
|
||||
Prefixes: []string{"/stripit"},
|
||||
ForceSlash: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP middleware cross namespace allowed",
|
||||
paths: []string{"services.yml", "with_middleware_cross_namespace.yml"},
|
||||
allowCrossNamespace: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-crossnamespace-route-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{
|
||||
"cross-ns-stripprefix",
|
||||
},
|
||||
},
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-9313b71dbe6a649d5049",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bir`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{"default-test-errorpage"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"cross-ns-stripprefix": {
|
||||
StripPrefix: &dynamic.StripPrefix{
|
||||
Prefixes: []string{"/stripit"},
|
||||
ForceSlash: false,
|
||||
},
|
||||
},
|
||||
"default-test-errorpage": {
|
||||
Errors: &dynamic.ErrorPage{
|
||||
Status: []string{"500-599"},
|
||||
Service: "default-test-errorpage-errorpage-service",
|
||||
Query: "/{status}.html",
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-crossnamespace-route-6b204d94623b3df4370c": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"default-test-errorpage-errorpage-service": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP cross namespace allowed",
|
||||
paths: []string{"services.yml", "with_cross_namespace.yml"},
|
||||
allowCrossNamespace: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-cross-ns-route-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-cross-ns-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
Priority: 12,
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-cross-ns-route-6b204d94623b3df4370c": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-tr-svc-wrr1",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "cross-ns-tr-svc-wrr2",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-tr-svc-mirror1",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "cross-ns-tr-svc-mirror2",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-whoami-svc-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"default-tr-svc-wrr1": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-tr-svc-wrr2": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-tr-svc-mirror1": {
|
||||
Mirroring: &dynamic.Mirroring{
|
||||
Service: "default-whoami-80",
|
||||
Mirrors: []dynamic.MirrorService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Percent: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-tr-svc-mirror2": {
|
||||
Mirroring: &dynamic.Mirroring{
|
||||
Service: "cross-ns-whoami-svc-80",
|
||||
Mirrors: []dynamic.MirrorService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Percent: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP cross namespace disallowed",
|
||||
paths: []string{"services.yml", "with_cross_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"cross-ns-tr-svc-wrr2": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-whoami-svc-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"cross-ns-tr-svc-mirror2": {
|
||||
Mirroring: &dynamic.Mirroring{
|
||||
Service: "cross-ns-whoami-svc-80",
|
||||
Mirrors: []dynamic.MirrorService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Percent: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TCP cross namespace allowed",
|
||||
paths: []string{"tcp/services.yml", "tcp/with_cross_namespace.yml"},
|
||||
allowCrossNamespace: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-fdd3e9338e47a45efefc",
|
||||
Rule: "HostSNI(`foo.com`)",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "10.10.0.1:8000",
|
||||
Port: "",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.2:8000",
|
||||
Port: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TCP cross namespace disallowed",
|
||||
paths: []string{"tcp/services.yml", "tcp/with_cross_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
// The router that references the invalid service will be discarded.
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-fdd3e9338e47a45efefc",
|
||||
Rule: "HostSNI(`foo.com`)",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "UDP cross namespace allowed",
|
||||
paths: []string{"udp/services.yml", "udp/with_cross_namespace.yml"},
|
||||
allowCrossNamespace: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{
|
||||
"default-test.route-0": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-0",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.UDPService{
|
||||
"default-test.route-0": {
|
||||
LoadBalancer: &dynamic.UDPServersLoadBalancer{
|
||||
Servers: []dynamic.UDPServer{
|
||||
{
|
||||
Address: "10.10.0.1:8000",
|
||||
Port: "",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.2:8000",
|
||||
Port: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "UDP cross namespace disallowed",
|
||||
paths: []string{"udp/services.yml", "udp/with_cross_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
// The router that references the invalid service will be discarded.
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{
|
||||
"default-test.route-0": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-0",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var k8sObjects []runtime.Object
|
||||
var crdObjects []runtime.Object
|
||||
for _, path := range test.paths {
|
||||
yamlContent, err := ioutil.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
objects := k8s.MustParseYaml(yamlContent)
|
||||
for _, obj := range objects {
|
||||
switch o := obj.(type) {
|
||||
case *corev1.Service, *corev1.Endpoints, *corev1.Secret:
|
||||
k8sObjects = append(k8sObjects, o)
|
||||
case *v1alpha1.IngressRoute:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.IngressRouteTCP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.IngressRouteUDP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.Middleware:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.TraefikService:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.TLSOption:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.TLSStore:
|
||||
crdObjects = append(crdObjects, o)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
crdClient := crdfake.NewSimpleClientset(crdObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, crdClient)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
eventCh, err := client.WatchAll([]string{"default", "cross-ns"}, stopCh)
|
||||
require.NoError(t, err)
|
||||
|
||||
if k8sObjects != nil || crdObjects != nil {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
p := Provider{}
|
||||
p.SetDefaults()
|
||||
|
||||
p.AllowCrossNamespace = func(b bool) *bool { return &b }(test.allowCrossNamespace)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
@@ -34,7 +36,7 @@ func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client
|
||||
serviceName := makeID(ingressRouteUDP.Namespace, key)
|
||||
|
||||
for _, service := range route.Services {
|
||||
balancerServerUDP, err := createLoadBalancerServerUDP(client, ingressRouteUDP.Namespace, service)
|
||||
balancerServerUDP, err := p.createLoadBalancerServerUDP(client, ingressRouteUDP.Namespace, service)
|
||||
if err != nil {
|
||||
logger.
|
||||
WithField("serviceName", service.Name).
|
||||
@@ -75,9 +77,13 @@ func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client
|
||||
return conf
|
||||
}
|
||||
|
||||
func createLoadBalancerServerUDP(client Client, namespace string, service v1alpha1.ServiceUDP) (*dynamic.UDPService, error) {
|
||||
ns := namespace
|
||||
func (p *Provider) createLoadBalancerServerUDP(client Client, parentNamespace string, service v1alpha1.ServiceUDP) (*dynamic.UDPService, error) {
|
||||
ns := parentNamespace
|
||||
if len(service.Namespace) > 0 {
|
||||
if !isNamespaceAllowed(p.AllowCrossNamespace, parentNamespace, service.Namespace) {
|
||||
return nil, fmt.Errorf("udp service %s/%s is not in the parent resource namespace %s", service.Namespace, service.Name, ns)
|
||||
}
|
||||
|
||||
ns = service.Namespace
|
||||
}
|
||||
|
||||
@@ -121,7 +127,7 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([
|
||||
var servers []dynamic.UDPServer
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
servers = append(servers, dynamic.UDPServer{
|
||||
Address: fmt.Sprintf("%s:%d", service.Spec.ExternalName, portSpec.Port),
|
||||
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(portSpec.Port))),
|
||||
})
|
||||
} else {
|
||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
|
||||
@@ -152,7 +158,7 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([
|
||||
|
||||
for _, addr := range subset.Addresses {
|
||||
servers = append(servers, dynamic.UDPServer{
|
||||
Address: fmt.Sprintf("%s:%d", addr.IP, port),
|
||||
Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -479,9 +481,10 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr
|
||||
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
protocol := getProtocol(portSpec, portSpec.Name, svcConfig)
|
||||
hostPort := net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(portSpec.Port)))
|
||||
|
||||
svc.LoadBalancer.Servers = []dynamic.Server{
|
||||
{URL: fmt.Sprintf("%s://%s:%d", protocol, service.Spec.ExternalName, portSpec.Port)},
|
||||
{URL: fmt.Sprintf("%s://%s", protocol, hostPort)},
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
@@ -516,8 +519,10 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr
|
||||
protocol := getProtocol(portSpec, portName, svcConfig)
|
||||
|
||||
for _, addr := range subset.Addresses {
|
||||
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port)))
|
||||
|
||||
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.Server{
|
||||
URL: fmt.Sprintf("%s://%s:%d", protocol, addr.IP, port),
|
||||
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,6 +661,47 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress with IPv6 endpoints",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"example-com-testing-bar": {
|
||||
Rule: "PathPrefix(`/bar`)",
|
||||
Service: "testing-service-bar-8080",
|
||||
},
|
||||
"example-com-testing-foo": {
|
||||
Rule: "PathPrefix(`/foo`)",
|
||||
Service: "testing-service-foo-8080",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service-bar-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"testing-service-foo-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:0db8:3c4d:0015:0000:0000:1a2f:2a3b]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TLS support",
|
||||
expected: &dynamic.Configuration{
|
||||
|
||||
@@ -59,7 +59,14 @@ func (r *Router) AddRoute(rule string, priority int, handler http.Handler) error
|
||||
}
|
||||
|
||||
route := r.NewRoute().Handler(handler).Priority(priority)
|
||||
return addRuleOnRoute(route, buildTree())
|
||||
|
||||
err = addRuleOnRoute(route, buildTree())
|
||||
if err != nil {
|
||||
route.BuildOnly()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type tree struct {
|
||||
|
||||
@@ -29,6 +29,16 @@ func Test_addRoute(t *testing.T) {
|
||||
rule: "rulewithnotmatcher",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
desc: "Host empty",
|
||||
rule: "Host(``)",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
desc: "PathPrefix empty",
|
||||
rule: "PathPrefix(``)",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
desc: "PathPrefix",
|
||||
rule: "PathPrefix(`/foo`)",
|
||||
|
||||
@@ -62,6 +62,29 @@ func TestRouterManager_Get(t *testing.T) {
|
||||
entryPoints: []string{"web"},
|
||||
expected: expectedResult{StatusCode: http.StatusOK},
|
||||
},
|
||||
{
|
||||
desc: "empty host",
|
||||
routersConfig: map[string]*dynamic.Router{
|
||||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(``)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: expectedResult{StatusCode: http.StatusNotFound},
|
||||
},
|
||||
{
|
||||
desc: "no load balancer",
|
||||
routersConfig: map[string]*dynamic.Router{
|
||||
|
||||
@@ -109,13 +109,18 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||
tlsOptionsForHostSNI := map[string]map[string]nameAndConfig{}
|
||||
tlsOptionsForHost := map[string]string{}
|
||||
for routerHTTPName, routerHTTPConfig := range configsHTTP {
|
||||
if len(routerHTTPConfig.TLS.Options) == 0 || routerHTTPConfig.TLS.Options == defaultTLSConfigName {
|
||||
if routerHTTPConfig.TLS == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ctxRouter := log.With(provider.AddInContext(ctx, routerHTTPName), log.Str(log.RouterName, routerHTTPName))
|
||||
logger := log.FromContext(ctxRouter)
|
||||
|
||||
tlsOptionsName := defaultTLSConfigName
|
||||
if len(routerHTTPConfig.TLS.Options) > 0 && routerHTTPConfig.TLS.Options != defaultTLSConfigName {
|
||||
tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options)
|
||||
}
|
||||
|
||||
domains, err := rules.ParseDomains(routerHTTPConfig.Rule)
|
||||
if err != nil {
|
||||
routerErr := fmt.Errorf("invalid rule %s, error: %w", routerHTTPConfig.Rule, err)
|
||||
@@ -129,34 +134,27 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||
}
|
||||
|
||||
for _, domain := range domains {
|
||||
if routerHTTPConfig.TLS != nil {
|
||||
tlsOptionsName := routerHTTPConfig.TLS.Options
|
||||
if tlsOptionsName != defaultTLSConfigName {
|
||||
tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options)
|
||||
}
|
||||
tlsConf, err := m.tlsManager.Get(defaultTLSStoreName, tlsOptionsName)
|
||||
if err != nil {
|
||||
routerHTTPConfig.AddError(err, true)
|
||||
logger.Debug(err)
|
||||
continue
|
||||
}
|
||||
|
||||
tlsConf, err := m.tlsManager.Get(defaultTLSStoreName, tlsOptionsName)
|
||||
if err != nil {
|
||||
routerHTTPConfig.AddError(err, true)
|
||||
logger.Debug(err)
|
||||
continue
|
||||
}
|
||||
// domain is already in lower case thanks to the domain parsing
|
||||
if tlsOptionsForHostSNI[domain] == nil {
|
||||
tlsOptionsForHostSNI[domain] = make(map[string]nameAndConfig)
|
||||
}
|
||||
tlsOptionsForHostSNI[domain][tlsOptionsName] = nameAndConfig{
|
||||
routerName: routerHTTPName,
|
||||
TLSConfig: tlsConf,
|
||||
}
|
||||
|
||||
// domain is already in lower case thanks to the domain parsing
|
||||
if tlsOptionsForHostSNI[domain] == nil {
|
||||
tlsOptionsForHostSNI[domain] = make(map[string]nameAndConfig)
|
||||
}
|
||||
tlsOptionsForHostSNI[domain][routerHTTPConfig.TLS.Options] = nameAndConfig{
|
||||
routerName: routerHTTPName,
|
||||
TLSConfig: tlsConf,
|
||||
}
|
||||
|
||||
if _, ok := tlsOptionsForHost[domain]; ok {
|
||||
// Multiple tlsOptions fallback to default
|
||||
tlsOptionsForHost[domain] = "default"
|
||||
} else {
|
||||
tlsOptionsForHost[domain] = routerHTTPConfig.TLS.Options
|
||||
}
|
||||
if name, ok := tlsOptionsForHost[domain]; ok && name != tlsOptionsName {
|
||||
// Different tlsOptions on the same domain fallback to default
|
||||
tlsOptionsForHost[domain] = defaultTLSConfigName
|
||||
} else {
|
||||
tlsOptionsForHost[domain] = tlsOptionsName
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,5 +302,5 @@ func findTLSOptionName(tlsOptionsForHost map[string]string, host string) string
|
||||
return tlsOptions
|
||||
}
|
||||
|
||||
return "default"
|
||||
return defaultTLSConfigName
|
||||
}
|
||||
|
||||
@@ -2,25 +2,31 @@ package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v2/pkg/server/service/tcp"
|
||||
"github.com/traefik/traefik/v2/pkg/tls"
|
||||
traefiktls "github.com/traefik/traefik/v2/pkg/tls"
|
||||
)
|
||||
|
||||
func TestRuntimeConfiguration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
serviceConfig map[string]*runtime.TCPServiceInfo
|
||||
routerConfig map[string]*runtime.TCPRouterInfo
|
||||
expectedError int
|
||||
desc string
|
||||
httpServiceConfig map[string]*runtime.ServiceInfo
|
||||
httpRouterConfig map[string]*runtime.RouterInfo
|
||||
tcpServiceConfig map[string]*runtime.TCPServiceInfo
|
||||
tcpRouterConfig map[string]*runtime.TCPRouterInfo
|
||||
expectedError int
|
||||
}{
|
||||
{
|
||||
desc: "No error",
|
||||
serviceConfig: map[string]*runtime.TCPServiceInfo{
|
||||
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
|
||||
"foo-service": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
@@ -38,7 +44,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
routerConfig: map[string]*runtime.TCPRouterInfo{
|
||||
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
|
||||
"foo": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
@@ -65,9 +71,54 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
},
|
||||
expectedError: 0,
|
||||
},
|
||||
{
|
||||
desc: "HTTP routers with same domain but different TLS options",
|
||||
httpServiceConfig: map[string]*runtime.ServiceInfo{
|
||||
"foo-service": {
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
Port: "8085",
|
||||
URL: "127.0.0.1:8085",
|
||||
},
|
||||
{
|
||||
URL: "127.0.0.1:8086",
|
||||
Port: "8086",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
httpRouterConfig: map[string]*runtime.RouterInfo{
|
||||
"foo": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`bar.foo`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
Router: &dynamic.Router{
|
||||
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`bar.foo`) && PathPrefix(`/path`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: 2,
|
||||
},
|
||||
{
|
||||
desc: "One router with wrong rule",
|
||||
serviceConfig: map[string]*runtime.TCPServiceInfo{
|
||||
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
|
||||
"foo-service": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
@@ -80,7 +131,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
routerConfig: map[string]*runtime.TCPRouterInfo{
|
||||
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
|
||||
"foo": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
@@ -101,7 +152,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "All router with wrong rule",
|
||||
serviceConfig: map[string]*runtime.TCPServiceInfo{
|
||||
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
|
||||
"foo-service": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
@@ -114,7 +165,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
routerConfig: map[string]*runtime.TCPRouterInfo{
|
||||
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
|
||||
"foo": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
@@ -134,7 +185,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "Router with unknown service",
|
||||
serviceConfig: map[string]*runtime.TCPServiceInfo{
|
||||
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
|
||||
"foo-service": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
@@ -147,7 +198,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
routerConfig: map[string]*runtime.TCPRouterInfo{
|
||||
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
|
||||
"foo": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
@@ -168,14 +219,14 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "Router with broken service",
|
||||
serviceConfig: map[string]*runtime.TCPServiceInfo{
|
||||
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
|
||||
"foo-service": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
routerConfig: map[string]*runtime.TCPRouterInfo{
|
||||
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
|
||||
"bar": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
@@ -197,15 +248,17 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
entryPoints := []string{"web"}
|
||||
|
||||
conf := &runtime.Configuration{
|
||||
TCPServices: test.serviceConfig,
|
||||
TCPRouters: test.routerConfig,
|
||||
Services: test.httpServiceConfig,
|
||||
Routers: test.httpRouterConfig,
|
||||
TCPServices: test.tcpServiceConfig,
|
||||
TCPRouters: test.tcpRouterConfig,
|
||||
}
|
||||
serviceManager := tcp.NewManager(conf)
|
||||
tlsManager := tls.NewManager()
|
||||
tlsManager := traefiktls.NewManager()
|
||||
tlsManager.UpdateConfigs(
|
||||
context.Background(),
|
||||
map[string]tls.Store{},
|
||||
map[string]tls.Options{
|
||||
map[string]traefiktls.Store{},
|
||||
map[string]traefiktls.Options{
|
||||
"default": {
|
||||
MinVersion: "VersionTLS10",
|
||||
},
|
||||
@@ -216,7 +269,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
MinVersion: "VersionTLS11",
|
||||
},
|
||||
},
|
||||
[]*tls.CertAndStores{})
|
||||
[]*traefiktls.CertAndStores{})
|
||||
|
||||
routerManager := NewManager(conf, serviceManager,
|
||||
nil, nil, tlsManager)
|
||||
@@ -237,7 +290,235 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
allErrors++
|
||||
}
|
||||
}
|
||||
for _, v := range conf.Services {
|
||||
if v.Err != nil {
|
||||
allErrors++
|
||||
}
|
||||
}
|
||||
for _, v := range conf.Routers {
|
||||
if len(v.Err) > 0 {
|
||||
allErrors++
|
||||
}
|
||||
}
|
||||
assert.Equal(t, test.expectedError, allErrors)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainFronting(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
routers map[string]*runtime.RouterInfo
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
desc: "Request is misdirected when TLS options are different",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-2@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host2.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedStatus: http.StatusMisdirectedRequest,
|
||||
},
|
||||
{
|
||||
desc: "Request is OK when TLS options are the same",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-2@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host2.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "Default TLS options is used when options are ambiguous for the same host",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-2@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host1.local`) && PathPrefix(`/foo`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-3@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host2.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedStatus: http.StatusMisdirectedRequest,
|
||||
},
|
||||
{
|
||||
desc: "Default TLS options should not be used when options are the same for the same host",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-2@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host1.local`) && PathPrefix(`/bar`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-3@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host2.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "Request is misdirected when TLS options have the same name but from different providers",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-2@crd": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host2.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedStatus: http.StatusMisdirectedRequest,
|
||||
},
|
||||
{
|
||||
desc: "Request is OK when TLS options reference from a different provider is the same",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1@crd",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-2@crd": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`host2.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1@crd",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
entryPoints := []string{"web"}
|
||||
tlsOptions := map[string]traefiktls.Options{
|
||||
"default": {
|
||||
MinVersion: "VersionTLS10",
|
||||
},
|
||||
"host1@file": {
|
||||
MinVersion: "VersionTLS12",
|
||||
},
|
||||
"host1@crd": {
|
||||
MinVersion: "VersionTLS12",
|
||||
},
|
||||
}
|
||||
|
||||
conf := &runtime.Configuration{
|
||||
Routers: test.routers,
|
||||
}
|
||||
|
||||
serviceManager := tcp.NewManager(conf)
|
||||
|
||||
tlsManager := traefiktls.NewManager()
|
||||
tlsManager.UpdateConfigs(context.Background(), map[string]traefiktls.Store{}, tlsOptions, []*traefiktls.CertAndStores{})
|
||||
|
||||
httpsHandler := map[string]http.Handler{
|
||||
"web": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}),
|
||||
}
|
||||
|
||||
routerManager := NewManager(conf, serviceManager, nil, httpsHandler, tlsManager)
|
||||
|
||||
routers := routerManager.BuildHandlers(context.Background(), entryPoints)
|
||||
|
||||
router, ok := routers["web"]
|
||||
require.True(t, ok)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "host1.local"
|
||||
req.TLS = &tls.ConnectionState{
|
||||
ServerName: "host2.local",
|
||||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
router.GetHTTPSHandler().ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expectedStatus, rw.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ RepositoryName = "traefik"
|
||||
OutputType = "file"
|
||||
FileName = "traefik_changelog.md"
|
||||
|
||||
# example new bugfix v2.3.4
|
||||
# example new bugfix v2.3.5
|
||||
CurrentRef = "v2.3"
|
||||
PreviousRef = "v2.3.3"
|
||||
PreviousRef = "v2.3.5"
|
||||
BaseBranch = "v2.3"
|
||||
FutureCurrentRefName = "v2.3.4"
|
||||
FutureCurrentRefName = "v2.3.6"
|
||||
|
||||
ThresholdPreviousRef = 10
|
||||
ThresholdCurrentRef = 10
|
||||
|
||||
@@ -231,8 +231,9 @@
|
||||
<div class="text-subtitle2">Service</div>
|
||||
<q-chip
|
||||
dense
|
||||
class="app-chip app-chip-green">
|
||||
class="app-chip app-chip-green app-chip-overflow">
|
||||
{{ exData(middleware).service }}
|
||||
<q-tooltip>{{ exData(middleware).service }}</q-tooltip>
|
||||
</q-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,8 +21,9 @@
|
||||
<div class="col-6">
|
||||
<q-chip
|
||||
dense
|
||||
class="app-chip app-chip-rule">
|
||||
class="app-chip app-chip-rule app-chip-overflow">
|
||||
{{ service.name }}
|
||||
<q-tooltip>{{service.name}}</q-tooltip>
|
||||
</q-chip>
|
||||
</div>
|
||||
<div class="col-3 text-right">
|
||||
|
||||
@@ -66,8 +66,9 @@
|
||||
dense
|
||||
clickable
|
||||
@click.native="$router.push({ path: `/${protocol}/services/${getServiceId()}`})"
|
||||
class="app-chip app-chip-wrap app-chip-service">
|
||||
class="app-chip app-chip-wrap app-chip-service app-chip-overflow">
|
||||
{{ data.service }}
|
||||
<q-tooltip>{{ data.service }}</q-tooltip>
|
||||
</q-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,8 +21,9 @@
|
||||
<div class="col-7">
|
||||
<q-chip
|
||||
dense
|
||||
class="app-chip app-chip-rule">
|
||||
class="app-chip app-chip-rule app-chip-overflow">
|
||||
{{ service.name }}
|
||||
<q-tooltip>{{service.name}}</q-tooltip>
|
||||
</q-chip>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
|
||||
@@ -131,6 +131,16 @@ body {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
&-overflow {
|
||||
max-width: 90%;
|
||||
|
||||
.q-chip__content{
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
&-accent, &-rule {
|
||||
color: $accent;
|
||||
background-color: rgba($accent, 0.1);
|
||||
|
||||
Reference in New Issue
Block a user