Merge branch v2.3 into master
This commit is contained in:
commit
eebbe64b36
46 changed files with 1949 additions and 148 deletions
27
CHANGELOG.md
27
CHANGELOG.md
|
@ -1,3 +1,30 @@
|
|||
## [v2.3.5](https://github.com/traefik/traefik/tree/v2.3.5) (2020-12-10)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.3.4...v2.3.5)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Update go-acme/lego to v4.1.3 ([#7625](https://github.com/traefik/traefik/pull/7625) by [ldez](https://github.com/ldez))
|
||||
- **[k8s,k8s/crd]** IngressRoute: add an option to disable cross-namespace routing ([#7595](https://github.com/traefik/traefik/pull/7595) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[k8s/crd,k8s/ingress]** Fix concatenation of IPv6 addresses and ports ([#7620](https://github.com/traefik/traefik/pull/7620) by [jspdown](https://github.com/jspdown))
|
||||
- **[tcp,tls]** Fix TLS options fallback when domain and options are the same ([#7609](https://github.com/traefik/traefik/pull/7609) by [jspdown](https://github.com/jspdown))
|
||||
- **[webui]** Fix UI bug on long service name ([#7535](https://github.com/traefik/traefik/pull/7535) by [ipinak](https://github.com/ipinak))
|
||||
|
||||
**Documentation:**
|
||||
- **[docker]** Add example for multiple service per container ([#7610](https://github.com/traefik/traefik/pull/7610) by [notsureifkevin](https://github.com/notsureifkevin))
|
||||
- Documentation: Add spacing to sidebars so the last item is always visible ([#7616](https://github.com/traefik/traefik/pull/7616) by [paulocfjunior](https://github.com/paulocfjunior))
|
||||
- Fix typos in migration guide ([#7596](https://github.com/traefik/traefik/pull/7596) by [marsavela](https://github.com/marsavela))
|
||||
|
||||
## [v2.3.4](https://github.com/traefik/traefik/tree/v2.3.4) (2020-11-24)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.3.3...v2.3.4)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Update go-acme/lego to v4.1.2 ([#7577](https://github.com/traefik/traefik/pull/7577) by [ldez](https://github.com/ldez))
|
||||
- **[k8s,k8s/crd,k8s/ingress]** Apply labelSelector as a TweakListOptions for Kubernetes informers ([#7521](https://github.com/traefik/traefik/pull/7521) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[middleware]** Do not evaluate templated URL in redirectRegex middleware ([#7573](https://github.com/traefik/traefik/pull/7573) by [jspdown](https://github.com/jspdown))
|
||||
- **[provider]** fix: invalid slice parsing. ([#7583](https://github.com/traefik/traefik/pull/7583) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[ecs]** Fix clusters option in ECS provider documentation ([#7586](https://github.com/traefik/traefik/pull/7586) by [skapin](https://github.com/skapin))
|
||||
|
||||
## [v2.3.3](https://github.com/traefik/traefik/tree/v2.3.3) (2020-11-19)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.3.2...v2.3.3)
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -87,7 +87,7 @@ _Optional, Default=["default"]_
|
|||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.ecs]
|
||||
cluster = ["default"]
|
||||
clusters = ["default"]
|
||||
# ...
|
||||
```
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -549,6 +549,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).
|
||||
|
||||
|
|
|
@ -549,6 +549,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).
|
||||
|
||||
|
|
|
@ -114,6 +114,7 @@
|
|||
certAuthFilePath = "foobar"
|
||||
disablePassHostHeaders = true
|
||||
namespaces = ["foobar", "foobar"]
|
||||
allowCrossNamespace = true
|
||||
labelSelector = "foobar"
|
||||
ingressClass = "foobar"
|
||||
throttleDuration = 42
|
||||
|
|
|
@ -124,6 +124,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)
|
||||
|
|
4
go.mod
4
go.mod
|
@ -33,7 +33,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.0
|
||||
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.4.2
|
||||
|
@ -71,7 +71,7 @@ require (
|
|||
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.0
|
||||
github.com/traefik/paerser v0.1.1
|
||||
github.com/traefik/yaegi v0.9.7
|
||||
github.com/uber/jaeger-client-go v2.25.0+incompatible
|
||||
github.com/uber/jaeger-lib v2.2.0+incompatible
|
||||
|
|
8
go.sum
8
go.sum
|
@ -284,8 +284,8 @@ github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2/go.mod h1:GLy
|
|||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml 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.0 h1:/9pMjaeaLq6m0n+io+kv2ySs2ZfrmH6eazuMoN18GHo=
|
||||
github.com/go-acme/lego/v4 v4.1.0/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=
|
||||
|
@ -786,8 +786,8 @@ github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDW
|
|||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/traefik/paerser v0.1.0 h1:B4v1tbvd8YnHsA7spwHKEWJoGrRP+2jYpIozsCMHhl0=
|
||||
github.com/traefik/paerser v0.1.0/go.mod h1:yYnAgdEC2wJH5CgG75qGWC8SsFDEapg09o9RrA6FfrE=
|
||||
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/transip/gotransip/v6 v6.2.0 h1:0Z+qVsyeiQdWfcAUeJyF0IEKAPvhJwwpwPi2WGtBIiE=
|
||||
|
|
120
integration/fixtures/k8s/07-ingressroute-cross-namespace.yml
Normal file
120
integration/fixtures/k8s/07-ingressroute-cross-namespace.yml
Normal file
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: other-ns
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoami
|
||||
namespace: other-ns
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoami
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test6.route
|
||||
namespace: other-ns
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`foo.com`) && PathPrefix(`/a`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: whoami
|
||||
namespace: default
|
||||
port: 80
|
||||
- match: Host(`foo.com`) && PathPrefix(`/b`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: wrr2
|
||||
namespace: default
|
||||
kind: TraefikService
|
||||
- match: Host(`foo.com`) && PathPrefix(`/c`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: wrr3
|
||||
kind: TraefikService
|
||||
- match: Host(`foo.com`) && PathPrefix(`/d`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: whoami
|
||||
namespace: other-ns
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: stripprefix2
|
||||
namespace: default
|
||||
- match: Host(`foo.com`) && PathPrefix(`/e`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: whoami
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: test-errorpage
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: wrr2
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
weighted:
|
||||
services:
|
||||
- name: whoami
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: wrr3
|
||||
namespace: other-ns
|
||||
|
||||
spec:
|
||||
weighted:
|
||||
services:
|
||||
- name: whoami
|
||||
namespace: default
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: stripprefix2
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
stripPrefix:
|
||||
prefixes:
|
||||
- /tobestripped
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-errorpage
|
||||
|
||||
spec:
|
||||
errors:
|
||||
status:
|
||||
- 500-599
|
||||
query: /{status}.html
|
||||
service:
|
||||
name: whoami
|
||||
namespace: other-ns
|
||||
port: 80
|
|
@ -16,3 +16,4 @@
|
|||
address = ":8000"
|
||||
|
||||
[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
|
@ -61,6 +61,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": {
|
||||
|
@ -75,6 +89,14 @@
|
|||
"default-test2-route-23c7f4c450289ee29016@kubernetescrd"
|
||||
]
|
||||
},
|
||||
"default-stripprefix2@kubernetescrd": {
|
||||
"stripPrefix": {
|
||||
"prefixes": [
|
||||
"/tobestripped"
|
||||
]
|
||||
},
|
||||
"status": "enabled"
|
||||
},
|
||||
"default-stripprefix@kubernetescrd": {
|
||||
"stripPrefix": {
|
||||
"prefixes": [
|
||||
|
@ -205,6 +227,17 @@
|
|||
"default-test3-route-7d0ac22d3d8db4b82618@kubernetescrd"
|
||||
]
|
||||
},
|
||||
"default-wrr2@kubernetescrd": {
|
||||
"weighted": {
|
||||
"services": [
|
||||
{
|
||||
"name": "default-whoami-80",
|
||||
"weight": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": "enabled"
|
||||
},
|
||||
"noop@internal": {
|
||||
"status": "enabled"
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package redirect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
@ -47,24 +44,17 @@ func (r *redirect) GetTracingInformation() (string, ext.SpanKindEnum) {
|
|||
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
oldURL := rawURL(req)
|
||||
|
||||
// If the Regexp doesn't match, skip to the next handler
|
||||
// If the Regexp doesn't match, skip to the next handler.
|
||||
if !r.regex.MatchString(oldURL) {
|
||||
r.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
// apply a rewrite regexp to the URL
|
||||
// Apply a rewrite regexp to the URL.
|
||||
newURL := r.regex.ReplaceAllString(oldURL, r.replacement)
|
||||
|
||||
// replace any variables that may be in there
|
||||
rewrittenURL := &bytes.Buffer{}
|
||||
if err := applyString(newURL, rewrittenURL, req); err != nil {
|
||||
r.errHandler.ServeHTTP(rw, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
// parse the rewritten URL and replace request URL with it
|
||||
parsedURL, err := url.Parse(rewrittenURL.String())
|
||||
// Parse the rewritten URL and replace request URL with it.
|
||||
parsedURL, err := url.Parse(newURL)
|
||||
if err != nil {
|
||||
r.errHandler.ServeHTTP(rw, req, err)
|
||||
return
|
||||
|
@ -78,7 +68,7 @@ func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
|
||||
req.URL = parsedURL
|
||||
|
||||
// make sure the request URI corresponds the rewritten URL
|
||||
// Make sure the request URI corresponds the rewritten URL.
|
||||
req.RequestURI = req.URL.RequestURI()
|
||||
r.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
@ -138,14 +128,3 @@ func rawURL(req *http.Request) string {
|
|||
|
||||
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
||||
}
|
||||
|
||||
func applyString(in string, out io.Writer, req *http.Request) error {
|
||||
t, err := template.New("t").Parse(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := struct{ Request *http.Request }{Request: req}
|
||||
|
||||
return t.Execute(out, data)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||
)
|
||||
|
||||
func TestRedirectRegexHandler(t *testing.T) {
|
||||
|
@ -35,16 +34,6 @@ func TestRedirectRegexHandler(t *testing.T) {
|
|||
expectedURL: "https://foobar.com:443",
|
||||
expectedStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "use request header",
|
||||
config: dynamic.RedirectRegex{
|
||||
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
||||
Replacement: `https://${1}{{ .Request.Header.Get "X-Foo" }}$2:443$4`,
|
||||
},
|
||||
url: "http://foo.com:80",
|
||||
expectedURL: "https://foobar.com:443",
|
||||
expectedStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "URL doesn't match regex",
|
||||
config: dynamic.RedirectRegex{
|
||||
|
@ -186,7 +175,7 @@ func TestRedirectRegexHandler(t *testing.T) {
|
|||
method = test.method
|
||||
}
|
||||
|
||||
req := testhelpers.MustNewRequest(method, test.url, nil)
|
||||
req := httptest.NewRequest(method, test.url, nil)
|
||||
if test.secured {
|
||||
req.TLS = &tls.ConnectionState{}
|
||||
}
|
||||
|
|
|
@ -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,12 +43,18 @@ type Provider struct {
|
|||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
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"`
|
||||
lastConfiguration safe.Safe
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.AllowCrossNamespace = func(b bool) *bool { return &b }(true)
|
||||
}
|
||||
|
||||
func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
|
||||
_, err := labels.Parse(p.LabelSelector)
|
||||
if err != 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)
|
||||
|
@ -346,7 +356,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
|
||||
}
|
||||
|
@ -356,7 +366,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
|
||||
}
|
||||
|
@ -816,3 +826,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"
|
||||
|
@ -47,7 +49,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)
|
||||
|
@ -65,23 +68,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))
|
||||
|
@ -152,8 +142,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,
|
||||
|
@ -270,7 +290,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
|
||||
|
@ -279,7 +299,11 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
|||
return nil, fmt.Errorf("load balancing strategy %s is not supported", strategy)
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -303,8 +327,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
|
||||
}
|
||||
|
||||
|
@ -338,8 +364,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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -351,10 +379,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
|
||||
}
|
||||
|
||||
|
@ -174,7 +180,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)
|
||||
|
@ -205,7 +211,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,15 +2,23 @@ package crd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/paerser/types"
|
||||
"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)
|
||||
|
@ -1010,6 +1018,129 @@ 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{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-route-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-whoami-ipv6-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: func(i bool) *bool { return &i }(true),
|
||||
},
|
||||
},
|
||||
"default-external-svc-with-ipv6-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:db8:85a3:8d3:1319:8a2e:370:7347]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: func(i bool) *bool { return &i }(true),
|
||||
},
|
||||
},
|
||||
"default-test-route-6b204d94623b3df4370c": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "default-whoami-ipv6-8080",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-external-svc-with-ipv6-8080",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
@ -1023,7 +1154,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)
|
||||
})
|
||||
}
|
||||
|
@ -3205,7 +3339,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)
|
||||
})
|
||||
}
|
||||
|
@ -3519,7 +3656,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)
|
||||
})
|
||||
}
|
||||
|
@ -3738,3 +3878,572 @@ func TestGetServicePort(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossNamespace(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
allowCrossNamespace bool
|
||||
ingressClass string
|
||||
paths []string
|
||||
expected *dynamic.Configuration
|
||||
}{
|
||||
{
|
||||
desc: "Empty",
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP middleware cross namespace disallowed",
|
||||
paths: []string{"services.yml", "with_middleware_cross_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-9313b71dbe6a649d5049",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bir`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{"default-test-errorpage"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"cross-ns-stripprefix": {
|
||||
StripPrefix: &dynamic.StripPrefix{
|
||||
Prefixes: []string{"/stripit"},
|
||||
ForceSlash: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP middleware cross namespace allowed",
|
||||
paths: []string{"services.yml", "with_middleware_cross_namespace.yml"},
|
||||
allowCrossNamespace: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-crossnamespace-route-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{
|
||||
"cross-ns-stripprefix",
|
||||
},
|
||||
},
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-9313b71dbe6a649d5049",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bir`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{"default-test-errorpage"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"cross-ns-stripprefix": {
|
||||
StripPrefix: &dynamic.StripPrefix{
|
||||
Prefixes: []string{"/stripit"},
|
||||
ForceSlash: false,
|
||||
},
|
||||
},
|
||||
"default-test-errorpage": {
|
||||
Errors: &dynamic.ErrorPage{
|
||||
Status: []string{"500-599"},
|
||||
Service: "default-test-errorpage-errorpage-service",
|
||||
Query: "/{status}.html",
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-crossnamespace-route-6b204d94623b3df4370c": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"default-test-errorpage-errorpage-service": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP cross namespace allowed",
|
||||
paths: []string{"services.yml", "with_cross_namespace.yml"},
|
||||
allowCrossNamespace: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-cross-ns-route-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-cross-ns-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
Priority: 12,
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-cross-ns-route-6b204d94623b3df4370c": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-tr-svc-wrr1",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "cross-ns-tr-svc-wrr2",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-tr-svc-mirror1",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "cross-ns-tr-svc-mirror2",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-whoami-svc-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"default-tr-svc-wrr1": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-tr-svc-wrr2": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-tr-svc-mirror1": {
|
||||
Mirroring: &dynamic.Mirroring{
|
||||
Service: "default-whoami-80",
|
||||
Mirrors: []dynamic.MirrorService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Percent: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-tr-svc-mirror2": {
|
||||
Mirroring: &dynamic.Mirroring{
|
||||
Service: "cross-ns-whoami-svc-80",
|
||||
Mirrors: []dynamic.MirrorService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Percent: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP cross namespace disallowed",
|
||||
paths: []string{"services.yml", "with_cross_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"cross-ns-tr-svc-wrr2": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-whoami-svc-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"cross-ns-tr-svc-mirror2": {
|
||||
Mirroring: &dynamic.Mirroring{
|
||||
Service: "cross-ns-whoami-svc-80",
|
||||
Mirrors: []dynamic.MirrorService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Percent: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TCP cross namespace allowed",
|
||||
paths: []string{"tcp/services.yml", "tcp/with_cross_namespace.yml"},
|
||||
allowCrossNamespace: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-fdd3e9338e47a45efefc",
|
||||
Rule: "HostSNI(`foo.com`)",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "10.10.0.1:8000",
|
||||
Port: "",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.2:8000",
|
||||
Port: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TCP cross namespace disallowed",
|
||||
paths: []string{"tcp/services.yml", "tcp/with_cross_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
// The router that references the invalid service will be discarded.
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-fdd3e9338e47a45efefc",
|
||||
Rule: "HostSNI(`foo.com`)",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "UDP cross namespace allowed",
|
||||
paths: []string{"udp/services.yml", "udp/with_cross_namespace.yml"},
|
||||
allowCrossNamespace: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{
|
||||
"default-test.route-0": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-0",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.UDPService{
|
||||
"default-test.route-0": {
|
||||
LoadBalancer: &dynamic.UDPServersLoadBalancer{
|
||||
Servers: []dynamic.UDPServer{
|
||||
{
|
||||
Address: "10.10.0.1:8000",
|
||||
Port: "",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.2:8000",
|
||||
Port: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "UDP cross namespace disallowed",
|
||||
paths: []string{"udp/services.yml", "udp/with_cross_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
// The router that references the invalid service will be discarded.
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{
|
||||
"default-test.route-0": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-0",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var k8sObjects []runtime.Object
|
||||
var crdObjects []runtime.Object
|
||||
for _, path := range test.paths {
|
||||
yamlContent, err := ioutil.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
objects := k8s.MustParseYaml(yamlContent)
|
||||
for _, obj := range objects {
|
||||
switch o := obj.(type) {
|
||||
case *corev1.Service, *corev1.Endpoints, *corev1.Secret:
|
||||
k8sObjects = append(k8sObjects, o)
|
||||
case *v1alpha1.IngressRoute:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.IngressRouteTCP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.IngressRouteUDP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.Middleware:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.TraefikService:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.TLSOption:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.TLSStore:
|
||||
crdObjects = append(crdObjects, o)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
crdClient := crdfake.NewSimpleClientset(crdObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, crdClient)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
eventCh, err := client.WatchAll([]string{"default", "cross-ns"}, stopCh)
|
||||
require.NoError(t, err)
|
||||
|
||||
if k8sObjects != nil || crdObjects != nil {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
p := Provider{}
|
||||
p.SetDefaults()
|
||||
|
||||
p.AllowCrossNamespace = func(b bool) *bool { return &b }(test.allowCrossNamespace)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"context"
|
||||
"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{
|
||||
|
|
|
@ -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.3
|
||||
# example new bugfix v2.3.5
|
||||
CurrentRef = "v2.3"
|
||||
PreviousRef = "v2.3.2"
|
||||
PreviousRef = "v2.3.4"
|
||||
BaseBranch = "v2.3"
|
||||
FutureCurrentRefName = "v2.3.3"
|
||||
FutureCurrentRefName = "v2.3.5"
|
||||
|
||||
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);
|
||||
|
|
Loading…
Reference in a new issue