From 53ed8e04ae41aabd67f8e0283930746ce2afbc87 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 23 Nov 2020 12:00:03 +0100 Subject: [PATCH 01/14] Update go-acme/lego to v4.1.2 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 04adf5f48..6010bad44 100644 --- a/go.mod +++ b/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.0 + github.com/go-acme/lego/v4 v4.1.2 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 diff --git a/go.sum b/go.sum index d4e35bcf7..b0ff75b08 100644 --- a/go.sum +++ b/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.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.2 h1:1zROppXkTbAIh7J7AydGD3dFICLIocucJY1NTH/wB64= +github.com/go-acme/lego/v4 v4.1.2/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= From 1c505903ff8ae20821b84dabcf08c104b8001e18 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 24 Nov 2020 09:40:03 +0100 Subject: [PATCH 02/14] fix: invalid slice parsing. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6010bad44..76298aa9f 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,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 diff --git a/go.sum b/go.sum index b0ff75b08..eec9c67e5 100644 --- a/go.sum +++ b/go.sum @@ -764,8 +764,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= From e9cccf6504764aab9a37e77a764da8f1a2159a42 Mon Sep 17 00:00:00 2001 From: Harold Ozouf Date: Tue, 24 Nov 2020 14:16:03 +0100 Subject: [PATCH 03/14] Do not evaluate templated URL in redirectRegex middleware --- pkg/middlewares/redirect/redirect.go | 31 +++---------------- .../redirect/redirect_regex_test.go | 13 +------- 2 files changed, 6 insertions(+), 38 deletions(-) diff --git a/pkg/middlewares/redirect/redirect.go b/pkg/middlewares/redirect/redirect.go index 9f6939474..e58422315 100644 --- a/pkg/middlewares/redirect/redirect.go +++ b/pkg/middlewares/redirect/redirect.go @@ -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) -} diff --git a/pkg/middlewares/redirect/redirect_regex_test.go b/pkg/middlewares/redirect/redirect_regex_test.go index bc9f08242..d6346cf75 100644 --- a/pkg/middlewares/redirect/redirect_regex_test.go +++ b/pkg/middlewares/redirect/redirect_regex_test.go @@ -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{} } From d2c1d39d423674e3d7b98a3322fe120b7714ceb7 Mon Sep 17 00:00:00 2001 From: SkapiN Date: Tue, 24 Nov 2020 14:50:03 +0100 Subject: [PATCH 04/14] Fix clusters option in ECS provider documentation --- docs/content/providers/ecs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/providers/ecs.md b/docs/content/providers/ecs.md index ab860c524..7a4afa92c 100644 --- a/docs/content/providers/ecs.md +++ b/docs/content/providers/ecs.md @@ -87,7 +87,7 @@ _Optional, Default=["default"]_ ```toml tab="File (TOML)" [providers.ecs] - cluster = ["default"] + clusters = ["default"] # ... ``` From 76e35a09b76be4dc4833d2d867d3ea8169c8ea75 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Tue, 24 Nov 2020 17:06:04 +0100 Subject: [PATCH 05/14] Prepare release v2.3.4 --- CHANGELOG.md | 12 ++++++++++++ script/gcg/traefik-bugfix.toml | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35864f514..0f718aead 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [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) diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index 0a4e2f244..3b6392736 100644 --- a/script/gcg/traefik-bugfix.toml +++ b/script/gcg/traefik-bugfix.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example new bugfix v2.3.3 +# example new bugfix v2.3.4 CurrentRef = "v2.3" -PreviousRef = "v2.3.2" +PreviousRef = "v2.3.3" BaseBranch = "v2.3" -FutureCurrentRefName = "v2.3.3" +FutureCurrentRefName = "v2.3.4" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 From 512ed086bd237dbde14294eea75ba1910ecae54e Mon Sep 17 00:00:00 2001 From: Sergiu Marsavela Date: Fri, 27 Nov 2020 11:18:04 +0100 Subject: [PATCH 06/14] Fix typos in migration guide --- docs/content/migration/v1-to-v2.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/migration/v1-to-v2.md b/docs/content/migration/v1-to-v2.md index 7840726ed..3c0c58080 100644 --- a/docs/content/migration/v1-to-v2.md +++ b/docs/content/migration/v1-to-v2.md @@ -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" From 121eaced490f5cef4ab1ffba0d00a9cb0ef50d3c Mon Sep 17 00:00:00 2001 From: Kevin Crawley <3759919+notsureifkevin@users.noreply.github.com> Date: Thu, 3 Dec 2020 02:36:03 -0600 Subject: [PATCH 07/14] Add example for multiple service per container --- docs/content/routing/providers/docker.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md index 6cc0cf2a8..d3af40057 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -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://:8000` in addition to `http://example-b.com` forwarding to `http://: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) From 7403b6fb82582e96cdd0a2e57451bfcd10eabb8c Mon Sep 17 00:00:00 2001 From: Harold Ozouf Date: Fri, 4 Dec 2020 20:56:04 +0100 Subject: [PATCH 08/14] Fix concatenation of IPv6 addresses and ports --- .../kubernetes/crd/fixtures/services.yml | 44 ++++++- .../kubernetes/crd/fixtures/tcp/services.yml | 42 +++++- .../kubernetes/crd/fixtures/tcp/with_ipv6.yml | 17 +++ .../kubernetes/crd/fixtures/udp/services.yml | 29 +++++ .../kubernetes/crd/fixtures/udp/with_ipv6.yml | 14 ++ .../kubernetes/crd/fixtures/with_ipv6.yml | 18 +++ .../kubernetes/crd/kubernetes_http.go | 10 +- pkg/provider/kubernetes/crd/kubernetes_tcp.go | 6 +- .../kubernetes/crd/kubernetes_test.go | 122 ++++++++++++++++++ pkg/provider/kubernetes/crd/kubernetes_udp.go | 6 +- .../Ingress-with-IPv6-endpoints_endpoint.yml | 12 ++ .../Ingress-with-IPv6-endpoints_ingress.yml | 18 +++ .../Ingress-with-IPv6-endpoints_service.yml | 26 ++++ pkg/provider/kubernetes/ingress/kubernetes.go | 9 +- .../kubernetes/ingress/kubernetes_test.go | 41 ++++++ 15 files changed, 403 insertions(+), 11 deletions(-) create mode 100644 pkg/provider/kubernetes/crd/fixtures/tcp/with_ipv6.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/udp/with_ipv6.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/with_ipv6.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints_endpoint.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints_ingress.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints_service.yml diff --git a/pkg/provider/kubernetes/crd/fixtures/services.yml b/pkg/provider/kubernetes/crd/fixtures/services.yml index 72b2e1b16..50d6d65c6 100644 --- a/pkg/provider/kubernetes/crd/fixtures/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/services.yml @@ -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,16 @@ 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 diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml index 7aabcb9be..32a854c8a 100644 --- a/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml @@ -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,14 @@ spec: type: ExternalName ports: - name: http - protocol: TCP \ No newline at end of file + protocol: TCP + +--- +apiVersion: v1 +kind: Service +metadata: + name: external.service.with.ipv6 + namespace: default +spec: + externalName: "fe80::200:5aee:feaa:20a2" + type: ExternalName diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_ipv6.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_ipv6.yml new file mode 100644 index 000000000..777915c18 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_ipv6.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml index c1a589cb6..f1c0abe18 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml @@ -101,3 +101,32 @@ 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 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_ipv6.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_ipv6.yml new file mode 100644 index 000000000..2acc8bdc7 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_ipv6.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_ipv6.yml b/pkg/provider/kubernetes/crd/fixtures/with_ipv6.yml new file mode 100644 index 000000000..23af879ac --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_ipv6.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 9a28bbf43..1a40c561c 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "net" + "strconv" "strings" "github.com/traefik/traefik/v2/pkg/config/dynamic" @@ -301,8 +303,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 +340,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), }) } } diff --git a/pkg/provider/kubernetes/crd/kubernetes_tcp.go b/pkg/provider/kubernetes/crd/kubernetes_tcp.go index 49cff4e60..abf29bb8e 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_tcp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_tcp.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "net" + "strconv" "strings" "github.com/traefik/traefik/v2/pkg/config/dynamic" @@ -165,7 +167,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 +198,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))), }) } } diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 73ed3588f..b866f20cb 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -987,6 +987,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 { diff --git a/pkg/provider/kubernetes/crd/kubernetes_udp.go b/pkg/provider/kubernetes/crd/kubernetes_udp.go index 18d4f4b65..2d02c8b5f 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_udp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_udp.go @@ -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" @@ -121,7 +123,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 +154,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))), }) } } diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints_endpoint.yml new file mode 100644 index 000000000..e70dd1a13 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints_endpoint.yml @@ -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 diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints_ingress.yml new file mode 100644 index 000000000..a12baef74 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints_ingress.yml @@ -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 diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints_service.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints_service.yml new file mode 100644 index 000000000..9b8bfdb8b --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints_service.yml @@ -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" diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index f5944edb1..8f06b214b 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -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), }) } } diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index 253b62f47..ee9c98347 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -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{ From 134a767a7f22fc3835e69751b9a17e3b09c73264 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 4 Dec 2020 23:40:03 +0100 Subject: [PATCH 09/14] Update go-acme/lego to v4.1.3 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 76298aa9f..ccf24f391 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index eec9c67e5..c6b5724d1 100644 --- a/go.sum +++ b/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= From 0d15ac8861e81059a5da651d95ef5a433593ad2f Mon Sep 17 00:00:00 2001 From: Ioannis Pinakoulakis Date: Mon, 7 Dec 2020 15:14:03 +0200 Subject: [PATCH 10/14] Fix UI bug on long service name --- webui/src/components/_commons/PanelMiddlewares.vue | 3 ++- .../src/components/_commons/PanelMirroringServices.vue | 3 ++- webui/src/components/_commons/PanelRouterDetails.vue | 3 ++- .../src/components/_commons/PanelWeightedServices.vue | 3 ++- webui/src/css/sass/app.scss | 10 ++++++++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/webui/src/components/_commons/PanelMiddlewares.vue b/webui/src/components/_commons/PanelMiddlewares.vue index daab41a76..266a12f16 100644 --- a/webui/src/components/_commons/PanelMiddlewares.vue +++ b/webui/src/components/_commons/PanelMiddlewares.vue @@ -231,8 +231,9 @@
Service
+ class="app-chip app-chip-green app-chip-overflow"> {{ exData(middleware).service }} + {{ exData(middleware).service }} diff --git a/webui/src/components/_commons/PanelMirroringServices.vue b/webui/src/components/_commons/PanelMirroringServices.vue index f9b505408..09a613894 100644 --- a/webui/src/components/_commons/PanelMirroringServices.vue +++ b/webui/src/components/_commons/PanelMirroringServices.vue @@ -21,8 +21,9 @@
+ class="app-chip app-chip-rule app-chip-overflow"> {{ service.name }} + {{service.name}}
diff --git a/webui/src/components/_commons/PanelRouterDetails.vue b/webui/src/components/_commons/PanelRouterDetails.vue index cb53987ce..26ddfc6f3 100644 --- a/webui/src/components/_commons/PanelRouterDetails.vue +++ b/webui/src/components/_commons/PanelRouterDetails.vue @@ -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 }} + {{ data.service }}
diff --git a/webui/src/components/_commons/PanelWeightedServices.vue b/webui/src/components/_commons/PanelWeightedServices.vue index 6cf5784c0..fb0ec582c 100644 --- a/webui/src/components/_commons/PanelWeightedServices.vue +++ b/webui/src/components/_commons/PanelWeightedServices.vue @@ -21,8 +21,9 @@
+ class="app-chip app-chip-rule app-chip-overflow"> {{ service.name }} + {{service.name}}
diff --git a/webui/src/css/sass/app.scss b/webui/src/css/sass/app.scss index f9ab3e5a9..0bdc9ba3b 100644 --- a/webui/src/css/sass/app.scss +++ b/webui/src/css/sass/app.scss @@ -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); From 02d856b8a5da96bd145cb043161e07d8aa5475db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20J=C3=BAnior?= Date: Mon, 7 Dec 2020 14:24:04 -0300 Subject: [PATCH 11/14] Documentation: Add spacing to sidebars so the last item is always visible --- docs/content/assets/styles/menu.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/content/assets/styles/menu.css b/docs/content/assets/styles/menu.css index da46bae00..6cdcfa870 100644 --- a/docs/content/assets/styles/menu.css +++ b/docs/content/assets/styles/menu.css @@ -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; From c72769e2ea077332f982c4b1ef38c6b111e9998b Mon Sep 17 00:00:00 2001 From: Harold Ozouf Date: Wed, 9 Dec 2020 14:16:03 +0100 Subject: [PATCH 12/14] Fix TLS options fallback when domain and options are the same Co-authored-by: Kevin Pollet --- pkg/server/router/tcp/router.go | 54 +++-- pkg/server/router/tcp/router_test.go | 323 +++++++++++++++++++++++++-- 2 files changed, 328 insertions(+), 49 deletions(-) diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index ed8bc8ff9..69a04fc4f 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -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 } diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go index 6a966b79d..a00465a86 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -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) + }) + } +} From 7ba907f261b4cedfc82d06c4a3a5a37ce819e889 Mon Sep 17 00:00:00 2001 From: Romain Date: Thu, 10 Dec 2020 14:58:04 +0100 Subject: [PATCH 13/14] IngressRoute: add an option to disable cross-namespace routing Co-authored-by: Jean-Baptiste Doumenjou <925513+jbdoumenjou@users.noreply.github.com> --- docs/content/providers/kubernetes-crd.md | 28 + .../reference/static-configuration/cli-ref.md | 3 + .../reference/static-configuration/env-ref.md | 3 + .../reference/static-configuration/file.toml | 1 + .../reference/static-configuration/file.yaml | 1 + .../k8s/07-ingressroute-cross-namespace.yml | 120 ++++ integration/fixtures/k8s_crd.toml | 1 + integration/k8s_test.go | 1 + integration/testdata/rawdata-crd.json | 33 + .../kubernetes/crd/fixtures/services.yml | 30 + .../kubernetes/crd/fixtures/tcp/services.yml | 30 + .../crd/fixtures/tcp/with_cross_namespace.yml | 16 + .../kubernetes/crd/fixtures/udp/services.yml | 30 + .../crd/fixtures/udp/with_cross_namespace.yml | 15 + .../crd/fixtures/with_cross_namespace.yml | 91 +++ .../with_middleware_cross_namespace.yml | 58 ++ pkg/provider/kubernetes/crd/kubernetes.go | 23 +- .../kubernetes/crd/kubernetes_http.go | 72 ++- pkg/provider/kubernetes/crd/kubernetes_tcp.go | 10 +- .../kubernetes/crd/kubernetes_test.go | 583 +++++++++++++++++- pkg/provider/kubernetes/crd/kubernetes_udp.go | 10 +- 21 files changed, 1123 insertions(+), 36 deletions(-) create mode 100644 integration/fixtures/k8s/07-ingressroute-cross-namespace.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/tcp/with_cross_namespace.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/udp/with_cross_namespace.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/with_cross_namespace.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/with_middleware_cross_namespace.yml diff --git a/docs/content/providers/kubernetes-crd.md b/docs/content/providers/kubernetes-crd.md index 41a6c5b89..8f2e6cfe5 100644 --- a/docs/content/providers/kubernetes-crd.md +++ b/docs/content/providers/kubernetes-crd.md @@ -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. diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 8ecd20175..f5ad7077b 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -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). diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 83c5e8480..9abff68d3 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -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). diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 8bef7d6ac..1d5985d19 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -113,6 +113,7 @@ certAuthFilePath = "foobar" disablePassHostHeaders = true namespaces = ["foobar", "foobar"] + allowCrossNamespace = true labelSelector = "foobar" ingressClass = "foobar" throttleDuration = 42 diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 9da38a70a..26332ef18 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -123,6 +123,7 @@ providers: namespaces: - foobar - foobar + allowCrossNamespace: true labelSelector: foobar ingressClass: foobar throttleDuration: 42s diff --git a/integration/fixtures/k8s/07-ingressroute-cross-namespace.yml b/integration/fixtures/k8s/07-ingressroute-cross-namespace.yml new file mode 100644 index 000000000..fbdeb88a4 --- /dev/null +++ b/integration/fixtures/k8s/07-ingressroute-cross-namespace.yml @@ -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 diff --git a/integration/fixtures/k8s_crd.toml b/integration/fixtures/k8s_crd.toml index 1f85c7822..3015b6093 100644 --- a/integration/fixtures/k8s_crd.toml +++ b/integration/fixtures/k8s_crd.toml @@ -16,3 +16,4 @@ address = ":8000" [providers.kubernetesCRD] + allowCrossNamespace = false diff --git a/integration/k8s_test.go b/integration/k8s_test.go index 60bd25b36..728e326e0 100644 --- a/integration/k8s_test.go +++ b/integration/k8s_test.go @@ -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 { diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index 570bee4d4..193f2ed13 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -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" } diff --git a/pkg/provider/kubernetes/crd/fixtures/services.yml b/pkg/provider/kubernetes/crd/fixtures/services.yml index 50d6d65c6..7d39877c4 100644 --- a/pkg/provider/kubernetes/crd/fixtures/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/services.yml @@ -199,3 +199,33 @@ spec: - 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 diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml index 32a854c8a..1f1377343 100644 --- a/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml @@ -208,3 +208,33 @@ metadata: 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 diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_cross_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_cross_namespace.yml new file mode 100644 index 000000000..c7ba22f19 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_cross_namespace.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml index f1c0abe18..887ddc385 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml @@ -130,3 +130,33 @@ subsets: 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 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_cross_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_cross_namespace.yml new file mode 100644 index 000000000..8df091d70 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_cross_namespace.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_cross_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/with_cross_namespace.yml new file mode 100644 index 000000000..bdc98ca1e --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_cross_namespace.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_middleware_cross_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/with_middleware_cross_namespace.yml new file mode 100644 index 000000000..aaed4a1d8 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_middleware_cross_namespace.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 3d3678a83..2a440cfc1 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -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 +} diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 1a40c561c..b66b65b61 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -48,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) @@ -66,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)) @@ -153,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, @@ -270,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 @@ -279,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) @@ -355,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": diff --git a/pkg/provider/kubernetes/crd/kubernetes_tcp.go b/pkg/provider/kubernetes/crd/kubernetes_tcp.go index abf29bb8e..915ed4cd6 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_tcp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_tcp.go @@ -55,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). @@ -125,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 } diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index b866f20cb..aff6eb964 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -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) @@ -1122,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) }) } @@ -3187,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) }) } @@ -3495,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) }) } @@ -3714,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) + }) + } +} diff --git a/pkg/provider/kubernetes/crd/kubernetes_udp.go b/pkg/provider/kubernetes/crd/kubernetes_udp.go index 2d02c8b5f..0346084a2 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_udp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_udp.go @@ -36,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). @@ -77,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 } From 42d8e6d60def0b49e2de4720b3ad16dc5901f3c3 Mon Sep 17 00:00:00 2001 From: Romain Date: Thu, 10 Dec 2020 16:48:04 +0100 Subject: [PATCH 14/14] Prepare release v2.3.5 --- CHANGELOG.md | 15 +++++++++++++++ script/gcg/traefik-bugfix.toml | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f718aead..e7b266e52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## [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) diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index 3b6392736..408b454d1 100644 --- a/script/gcg/traefik-bugfix.toml +++ b/script/gcg/traefik-bugfix.toml @@ -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.4" BaseBranch = "v2.3" -FutureCurrentRefName = "v2.3.4" +FutureCurrentRefName = "v2.3.5" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10