From ff7966f9cd3d5ee149bc8c523a850719e9dc9387 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 11 Jan 2024 10:40:06 +0100 Subject: [PATCH] feat: re introduce IpWhitelist middleware as deprecated --- .golangci.yml | 6 + docs/content/middlewares/http/ipwhitelist.md | 201 ++++++++++++++++++ docs/content/middlewares/tcp/ipallowlist.md | 2 +- docs/content/middlewares/tcp/ipwhitelist.md | 64 ++++++ .../dynamic-configuration/docker-labels.yml | 109 +++++----- .../reference/dynamic-configuration/file.toml | 74 ++++--- .../reference/dynamic-configuration/file.yaml | 34 +-- .../kubernetes-crd-definition-v1.yml | 37 ++++ .../reference/dynamic-configuration/kv-ref.md | 121 ++++++----- .../marathon-labels.json | 0 .../traefik.io_middlewares.yaml | 26 +++ .../traefik.io_middlewaretcps.yaml | 11 + integration/fixtures/k8s/01-traefik-crd.yml | 37 ++++ integration/fixtures/simple_whitelist.toml | 18 ++ integration/fixtures/tcp/ip-allowlist.toml | 1 + integration/fixtures/tcp/ip-whitelist.toml | 52 +++++ integration/resources/compose/whitelist.yml | 36 ++++ integration/simple_test.go | 63 ++++++ integration/tcp_test.go | 24 +++ .../testdata/rawdata-crd-label-selector.json | 4 +- integration/testdata/rawdata-crd.json | 22 +- integration/testdata/rawdata-gateway.json | 4 +- .../rawdata-ingress-label-selector.json | 8 +- integration/testdata/rawdata-ingress.json | 16 +- .../testdata/rawdata-ingressclass.json | 4 +- pkg/config/dynamic/middlewares.go | 26 ++- pkg/config/dynamic/tcp_middlewares.go | 13 +- pkg/config/dynamic/zz_generated.deepcopy.go | 57 +++++ pkg/middlewares/ipwhitelist/ip_whitelist.go | 88 ++++++++ .../ipwhitelist/ip_whitelist_test.go | 100 +++++++++ .../tcp/ipwhitelist/ip_whitelist.go | 63 ++++++ .../tcp/ipwhitelist/ip_whitelist_test.go | 139 ++++++++++++ pkg/provider/kubernetes/crd/kubernetes.go | 2 + .../crd/traefikio/v1alpha1/middleware.go | 14 +- .../crd/traefikio/v1alpha1/middlewaretcp.go | 3 + .../v1alpha1/zz_generated.deepcopy.go | 10 + pkg/server/middleware/middlewares.go | 14 ++ pkg/server/middleware/tcp/middlewares.go | 11 + 38 files changed, 1314 insertions(+), 200 deletions(-) create mode 100644 docs/content/middlewares/http/ipwhitelist.md create mode 100644 docs/content/middlewares/tcp/ipwhitelist.md delete mode 100644 docs/content/reference/dynamic-configuration/marathon-labels.json create mode 100644 integration/fixtures/simple_whitelist.toml create mode 100644 integration/fixtures/tcp/ip-whitelist.toml create mode 100644 integration/resources/compose/whitelist.yml create mode 100644 pkg/middlewares/ipwhitelist/ip_whitelist.go create mode 100644 pkg/middlewares/ipwhitelist/ip_whitelist_test.go create mode 100644 pkg/middlewares/tcp/ipwhitelist/ip_whitelist.go create mode 100644 pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go diff --git a/.golangci.yml b/.golangci.yml index 103daa2f5..7128a4177 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -252,6 +252,12 @@ issues: text: 'SA1019: config.ClientCAs.Subjects has been deprecated since Go 1.18' - path: pkg/types/tls_test.go text: 'SA1019: tlsConfig.RootCAs.Subjects has been deprecated since Go 1.18' + - path: pkg/provider/kubernetes/crd/kubernetes.go + text: 'SA1019: middleware.Spec.IPWhiteList is deprecated: please use IPAllowList instead.' + - path: pkg/server/middleware/tcp/middlewares.go + text: 'SA1019: config.IPWhiteList is deprecated: please use IPAllowList instead.' + - path: pkg/server/middleware/middlewares.go + text: 'SA1019: config.IPWhiteList is deprecated: please use IPAllowList instead.' - path: pkg/provider/kubernetes/(crd|gateway)/client.go linters: - interfacebloat diff --git a/docs/content/middlewares/http/ipwhitelist.md b/docs/content/middlewares/http/ipwhitelist.md new file mode 100644 index 000000000..e093ad0a9 --- /dev/null +++ b/docs/content/middlewares/http/ipwhitelist.md @@ -0,0 +1,201 @@ +--- +title: "Traefik HTTP Middlewares IPWhiteList" +description: "Learn how to use IPWhiteList in HTTP middleware for limiting clients to specific IPs in Traefik Proxy. Read the technical documentation." +--- + +# IPWhiteList + +Limiting Clients to Specific IPs +{: .subtitle } + +![IPWhiteList](../../assets/img/middleware/ipwhitelist.png) + +IPWhiteList accepts / refuses requests based on the client IP. + +!!! warning + + This middleware is deprecated, please use the [IPAllowList](./ipallowlist.md) middleware instead. + +## Configuration Examples + +```yaml tab="Docker" +# Accepts request from defined IP +labels: + - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-ipwhitelist +spec: + ipWhiteList: + sourceRange: + - 127.0.0.1/32 + - 192.168.1.7 +``` + +```yaml tab="Consul Catalog" +# Accepts request from defined IP +- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7" +``` + +```yaml tab="File (YAML)" +# Accepts request from defined IP +http: + middlewares: + test-ipwhitelist: + ipWhiteList: + sourceRange: + - "127.0.0.1/32" + - "192.168.1.7" +``` + +```toml tab="File (TOML)" +# Accepts request from defined IP +[http.middlewares] + [http.middlewares.test-ipwhitelist.ipWhiteList] + sourceRange = ["127.0.0.1/32", "192.168.1.7"] +``` + +## Configuration Options + +### `sourceRange` + +The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs by using CIDR notation). + +### `ipStrategy` + +The `ipStrategy` option defines two parameters that set how Traefik determines the client IP: `depth`, and `excludedIPs`. +If no strategy is set, the default behavior is to match `sourceRange` against the Remote address found in the request. + +!!! important "As a middleware, whitelisting happens before the actual proxying to the backend takes place. In addition, the previous network hop only gets appended to `X-Forwarded-For` during the last stages of proxying, i.e. after it has already passed through whitelisting. Therefore, during whitelisting, as the previous network hop is not yet present in `X-Forwarded-For`, it cannot be matched against `sourceRange`." + +#### `ipStrategy.depth` + +The `depth` option tells Traefik to use the `X-Forwarded-For` header and take the IP located at the `depth` position (starting from the right). + +- If `depth` is greater than the total number of IPs in `X-Forwarded-For`, then the client IP will be empty. +- `depth` is ignored if its value is less than or equal to 0. + +!!! example "Examples of Depth & X-Forwarded-For" + + If `depth` is set to 2, and the request `X-Forwarded-For` header is `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP is `"10.0.0.1"` (at depth 4) but the IP used for the whitelisting is `"12.0.0.1"` (`depth=2`). + + | `X-Forwarded-For` | `depth` | clientIP | + |-----------------------------------------|---------|--------------| + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1` | `"13.0.0.1"` | + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` | + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` | + +```yaml tab="Docker" +# Whitelisting Based on `X-Forwarded-For` with `depth=2` +labels: + - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7" + - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.depth=2" +``` + +```yaml tab="Kubernetes" +# Whitelisting Based on `X-Forwarded-For` with `depth=2` +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-ipwhitelist +spec: + ipWhiteList: + sourceRange: + - 127.0.0.1/32 + - 192.168.1.7 + ipStrategy: + depth: 2 +``` + +```yaml tab="Consul Catalog" +# Whitelisting Based on `X-Forwarded-For` with `depth=2` +- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7" +- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.depth=2" +``` + +```yaml tab="File (YAML)" +# Whitelisting Based on `X-Forwarded-For` with `depth=2` +http: + middlewares: + test-ipwhitelist: + ipWhiteList: + sourceRange: + - "127.0.0.1/32" + - "192.168.1.7" + ipStrategy: + depth: 2 +``` + +```toml tab="File (TOML)" +# Whitelisting Based on `X-Forwarded-For` with `depth=2` +[http.middlewares] + [http.middlewares.test-ipwhitelist.ipWhiteList] + sourceRange = ["127.0.0.1/32", "192.168.1.7"] + [http.middlewares.test-ipwhitelist.ipWhiteList.ipStrategy] + depth = 2 +``` + +#### `ipStrategy.excludedIPs` + +`excludedIPs` configures Traefik to scan the `X-Forwarded-For` header and select the first IP not in the list. + +!!! important "If `depth` is specified, `excludedIPs` is ignored." + +!!! example "Example of ExcludedIPs & X-Forwarded-For" + + | `X-Forwarded-For` | `excludedIPs` | clientIP | + |-----------------------------------------|-----------------------|--------------| + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"12.0.0.1,13.0.0.1"` | `"11.0.0.1"` | + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,13.0.0.1"` | `"12.0.0.1"` | + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"10.0.0.1,13.0.0.1"` | `"12.0.0.1"` | + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` | + | `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` | + +```yaml tab="Docker" +# Exclude from `X-Forwarded-For` +labels: + - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" +``` + +```yaml tab="Kubernetes" +# Exclude from `X-Forwarded-For` +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-ipwhitelist +spec: + ipWhiteList: + ipStrategy: + excludedIPs: + - 127.0.0.1/32 + - 192.168.1.7 +``` + +```yaml tab="Consul Catalog" +# Exclude from `X-Forwarded-For` +- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" +``` + +```yaml tab="File (YAML)" +# Exclude from `X-Forwarded-For` +http: + middlewares: + test-ipwhitelist: + ipWhiteList: + ipStrategy: + excludedIPs: + - "127.0.0.1/32" + - "192.168.1.7" +``` + +```toml tab="File (TOML)" +# Exclude from `X-Forwarded-For` +[http.middlewares] + [http.middlewares.test-ipwhitelist.ipWhiteList] + [http.middlewares.test-ipwhitelist.ipWhiteList.ipStrategy] + excludedIPs = ["127.0.0.1/32", "192.168.1.7"] +``` diff --git a/docs/content/middlewares/tcp/ipallowlist.md b/docs/content/middlewares/tcp/ipallowlist.md index 7f1859dd0..e8466b94e 100644 --- a/docs/content/middlewares/tcp/ipallowlist.md +++ b/docs/content/middlewares/tcp/ipallowlist.md @@ -8,7 +8,7 @@ description: "Learn how to use IPAllowList in TCP middleware for limiting client Limiting Clients to Specific IPs {: .subtitle } -IPWhitelist accepts / refuses connections based on the client IP. +IPAllowList accepts / refuses connections based on the client IP. ## Configuration Examples diff --git a/docs/content/middlewares/tcp/ipwhitelist.md b/docs/content/middlewares/tcp/ipwhitelist.md new file mode 100644 index 000000000..c3d1ccb43 --- /dev/null +++ b/docs/content/middlewares/tcp/ipwhitelist.md @@ -0,0 +1,64 @@ +--- +title: "Traefik TCP Middlewares IPWhiteList" +description: "Learn how to use IPWhiteList in TCP middleware for limiting clients to specific IPs in Traefik Proxy. Read the technical documentation." +--- + +# IPWhiteList + +Limiting Clients to Specific IPs +{: .subtitle } + +IPWhiteList accepts / refuses connections based on the client IP. + +!!! warning + + This middleware is deprecated, please use the [IPAllowList](./ipallowlist.md) middleware instead. + +## Configuration Examples + +```yaml tab="Docker" +# Accepts connections from defined IP +labels: + - "traefik.tcp.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: MiddlewareTCP +metadata: + name: test-ipwhitelist +spec: + ipWhiteList: + sourceRange: + - 127.0.0.1/32 + - 192.168.1.7 +``` + +```yaml tab="Consul Catalog" +# Accepts request from defined IP +- "traefik.tcp.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7" +``` + +```toml tab="File (TOML)" +# Accepts request from defined IP +[tcp.middlewares] + [tcp.middlewares.test-ipwhitelist.ipWhiteList] + sourceRange = ["127.0.0.1/32", "192.168.1.7"] +``` + +```yaml tab="File (YAML)" +# Accepts request from defined IP +tcp: + middlewares: + test-ipwhitelist: + ipWhiteList: + sourceRange: + - "127.0.0.1/32" + - "192.168.1.7" +``` + +## Configuration Options + +### `sourceRange` + +The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs by using CIDR notation). diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index 4f7ef62c7..214af5fa1 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -65,57 +65,60 @@ - "traefik.http.middlewares.middleware10.headers.stsincludesubdomains=true" - "traefik.http.middlewares.middleware10.headers.stspreload=true" - "traefik.http.middlewares.middleware10.headers.stsseconds=42" -- "traefik.http.middlewares.middleware11.ipallowlist.ipstrategy.depth=42" -- "traefik.http.middlewares.middleware11.ipallowlist.ipstrategy.excludedips=foobar, foobar" -- "traefik.http.middlewares.middleware11.ipallowlist.sourcerange=foobar, foobar" -- "traefik.http.middlewares.middleware11.ipallowlist.rejectstatuscode=404" -- "traefik.http.middlewares.middleware12.inflightreq.amount=42" -- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.ipstrategy.depth=42" -- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.ipstrategy.excludedips=foobar, foobar" -- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.requestheadername=foobar" -- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.requesthost=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.commonname=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.country=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.domaincomponent=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.locality=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.organization=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.province=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.serialnumber=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.notafter=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.notbefore=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.sans=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.serialnumber=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.commonname=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.country=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.domaincomponent=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.locality=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.organization=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.organizationalunit=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.province=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.serialnumber=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.pem=true" -- "traefik.http.middlewares.middleware14.plugin.foobar.foo=bar" -- "traefik.http.middlewares.middleware15.ratelimit.average=42" -- "traefik.http.middlewares.middleware15.ratelimit.burst=42" -- "traefik.http.middlewares.middleware15.ratelimit.period=42" -- "traefik.http.middlewares.middleware15.ratelimit.sourcecriterion.ipstrategy.depth=42" -- "traefik.http.middlewares.middleware15.ratelimit.sourcecriterion.ipstrategy.excludedips=foobar, foobar" -- "traefik.http.middlewares.middleware15.ratelimit.sourcecriterion.requestheadername=foobar" -- "traefik.http.middlewares.middleware15.ratelimit.sourcecriterion.requesthost=true" -- "traefik.http.middlewares.middleware16.redirectregex.permanent=true" -- "traefik.http.middlewares.middleware16.redirectregex.regex=foobar" -- "traefik.http.middlewares.middleware16.redirectregex.replacement=foobar" -- "traefik.http.middlewares.middleware17.redirectscheme.permanent=true" -- "traefik.http.middlewares.middleware17.redirectscheme.port=foobar" -- "traefik.http.middlewares.middleware17.redirectscheme.scheme=foobar" -- "traefik.http.middlewares.middleware18.replacepath.path=foobar" -- "traefik.http.middlewares.middleware19.replacepathregex.regex=foobar" -- "traefik.http.middlewares.middleware19.replacepathregex.replacement=foobar" -- "traefik.http.middlewares.middleware20.retry.attempts=42" -- "traefik.http.middlewares.middleware20.retry.initialinterval=42" -- "traefik.http.middlewares.middleware21.stripprefix.prefixes=foobar, foobar" -- "traefik.http.middlewares.middleware22.stripprefixregex.regex=foobar, foobar" -- "traefik.http.middlewares.middleware23.grpcweb.alloworigins=foobar, foobar" +- "traefik.http.middlewares.middleware11.ipwhitelist.ipstrategy.depth=42" +- "traefik.http.middlewares.middleware11.ipwhitelist.ipstrategy.excludedips=foobar, foobar" +- "traefik.http.middlewares.middleware12.ipwhitelist.sourcerange=foobar, foobar" +- "traefik.http.middlewares.middleware12.ipallowlist.ipstrategy.depth=42" +- "traefik.http.middlewares.middleware12.ipallowlist.ipstrategy.excludedips=foobar, foobar" +- "traefik.http.middlewares.middleware12.ipallowlist.sourcerange=foobar, foobar" +- "traefik.http.middlewares.middleware12.ipallowlist.rejectstatuscode=404" +- "traefik.http.middlewares.middleware13.inflightreq.amount=42" +- "traefik.http.middlewares.middleware13.inflightreq.sourcecriterion.ipstrategy.depth=42" +- "traefik.http.middlewares.middleware13.inflightreq.sourcecriterion.ipstrategy.excludedips=foobar, foobar" +- "traefik.http.middlewares.middleware13.inflightreq.sourcecriterion.requestheadername=foobar" +- "traefik.http.middlewares.middleware13.inflightreq.sourcecriterion.requesthost=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.issuer.commonname=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.issuer.country=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.issuer.domaincomponent=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.issuer.locality=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.issuer.organization=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.issuer.province=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.issuer.serialnumber=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.notafter=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.notbefore=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.sans=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.serialnumber=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.subject.commonname=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.subject.country=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.subject.domaincomponent=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.subject.locality=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.subject.organization=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.subject.organizationalunit=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.subject.province=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.info.subject.serialnumber=true" +- "traefik.http.middlewares.middleware14.passtlsclientcert.pem=true" +- "traefik.http.middlewares.middleware15.plugin.foobar.foo=bar" +- "traefik.http.middlewares.middleware16.ratelimit.average=42" +- "traefik.http.middlewares.middleware16.ratelimit.burst=42" +- "traefik.http.middlewares.middleware16.ratelimit.period=42" +- "traefik.http.middlewares.middleware16.ratelimit.sourcecriterion.ipstrategy.depth=42" +- "traefik.http.middlewares.middleware16.ratelimit.sourcecriterion.ipstrategy.excludedips=foobar, foobar" +- "traefik.http.middlewares.middleware16.ratelimit.sourcecriterion.requestheadername=foobar" +- "traefik.http.middlewares.middleware16.ratelimit.sourcecriterion.requesthost=true" +- "traefik.http.middlewares.middleware17.redirectregex.permanent=true" +- "traefik.http.middlewares.middleware17.redirectregex.regex=foobar" +- "traefik.http.middlewares.middleware17.redirectregex.replacement=foobar" +- "traefik.http.middlewares.middleware18.redirectscheme.permanent=true" +- "traefik.http.middlewares.middleware18.redirectscheme.port=foobar" +- "traefik.http.middlewares.middleware18.redirectscheme.scheme=foobar" +- "traefik.http.middlewares.middleware19.replacepath.path=foobar" +- "traefik.http.middlewares.middleware20.replacepathregex.regex=foobar" +- "traefik.http.middlewares.middleware20.replacepathregex.replacement=foobar" +- "traefik.http.middlewares.middleware21.retry.attempts=42" +- "traefik.http.middlewares.middleware21.retry.initialinterval=42" +- "traefik.http.middlewares.middleware22.stripprefix.prefixes=foobar, foobar" +- "traefik.http.middlewares.middleware23.stripprefixregex.regex=foobar, foobar" +- "traefik.http.middlewares.middleware24.grpcweb.alloworigins=foobar, foobar" - "traefik.http.routers.router0.entrypoints=foobar, foobar" - "traefik.http.routers.router0.middlewares=foobar, foobar" - "traefik.http.routers.router0.priority=42" @@ -162,8 +165,8 @@ - "traefik.http.services.service01.loadbalancer.sticky.cookie.secure=true" - "traefik.http.services.service01.loadbalancer.server.port=foobar" - "traefik.http.services.service01.loadbalancer.server.scheme=foobar" -- "traefik.tcp.middlewares.tcpmiddleware00.ipallowlist.sourcerange=foobar, foobar" -- "traefik.tcp.middlewares.tcpmiddleware01.inflightconn.amount=42" +- "traefik.tcp.middlewares.tcpmiddleware00.inflightconn.amount=42" +- "traefik.tcp.middlewares.tcpmiddleware01.ipwhitelist.sourcerange=foobar, foobar" - "traefik.tcp.middlewares.tcpmiddleware02.ipallowlist.sourcerange=foobar, foobar" - "traefik.tcp.routers.tcprouter0.entrypoints=foobar, foobar" - "traefik.tcp.routers.tcprouter0.middlewares=foobar, foobar" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 4c1e5c593..49cf27e95 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -197,30 +197,36 @@ name0 = "foobar" name1 = "foobar" [http.middlewares.Middleware11] - [http.middlewares.Middleware11.ipAllowList] + [http.middlewares.Middleware11.ipWhiteList] sourceRange = ["foobar", "foobar"] - rejectStatusCode = 404 - [http.middlewares.Middleware11.ipAllowList.ipStrategy] + [http.middlewares.Middleware11.ipWhiteList.ipStrategy] depth = 42 excludedIPs = ["foobar", "foobar"] [http.middlewares.Middleware12] - [http.middlewares.Middleware12.inFlightReq] + [http.middlewares.Middleware12.ipAllowList] + sourceRange = ["foobar", "foobar"] + rejectStatusCode = 404 + [http.middlewares.Middleware12.ipAllowList.ipStrategy] + depth = 42 + excludedIPs = ["foobar", "foobar"] + [http.middlewares.Middleware13] + [http.middlewares.Middleware13.inFlightReq] amount = 42 - [http.middlewares.Middleware12.inFlightReq.sourceCriterion] + [http.middlewares.Middleware13.inFlightReq.sourceCriterion] requestHeaderName = "foobar" requestHost = true - [http.middlewares.Middleware12.inFlightReq.sourceCriterion.ipStrategy] + [http.middlewares.Middleware13.inFlightReq.sourceCriterion.ipStrategy] depth = 42 excludedIPs = ["foobar", "foobar"] - [http.middlewares.Middleware13] - [http.middlewares.Middleware13.passTLSClientCert] + [http.middlewares.Middleware14] + [http.middlewares.Middleware14.passTLSClientCert] pem = true - [http.middlewares.Middleware13.passTLSClientCert.info] + [http.middlewares.Middleware14.passTLSClientCert.info] notAfter = true notBefore = true sans = true serialNumber = true - [http.middlewares.Middleware13.passTLSClientCert.info.subject] + [http.middlewares.Middleware14.passTLSClientCert.info.subject] country = true province = true locality = true @@ -229,7 +235,7 @@ commonName = true serialNumber = true domainComponent = true - [http.middlewares.Middleware13.passTLSClientCert.info.issuer] + [http.middlewares.Middleware14.passTLSClientCert.info.issuer] country = true province = true locality = true @@ -237,50 +243,50 @@ commonName = true serialNumber = true domainComponent = true - [http.middlewares.Middleware14] - [http.middlewares.Middleware14.plugin] - [http.middlewares.Middleware14.plugin.PluginConf] - foo = "bar" [http.middlewares.Middleware15] - [http.middlewares.Middleware15.rateLimit] + [http.middlewares.Middleware15.plugin] + [http.middlewares.Middleware15.plugin.PluginConf] + foo = "bar" + [http.middlewares.Middleware16] + [http.middlewares.Middleware16.rateLimit] average = 42 period = "42s" burst = 42 - [http.middlewares.Middleware15.rateLimit.sourceCriterion] + [http.middlewares.Middleware16.rateLimit.sourceCriterion] requestHeaderName = "foobar" requestHost = true - [http.middlewares.Middleware15.rateLimit.sourceCriterion.ipStrategy] + [http.middlewares.Middleware16.rateLimit.sourceCriterion.ipStrategy] depth = 42 excludedIPs = ["foobar", "foobar"] - [http.middlewares.Middleware16] - [http.middlewares.Middleware16.redirectRegex] + [http.middlewares.Middleware17] + [http.middlewares.Middleware17.redirectRegex] regex = "foobar" replacement = "foobar" permanent = true - [http.middlewares.Middleware17] - [http.middlewares.Middleware17.redirectScheme] + [http.middlewares.Middleware18] + [http.middlewares.Middleware18.redirectScheme] scheme = "foobar" port = "foobar" permanent = true - [http.middlewares.Middleware18] - [http.middlewares.Middleware18.replacePath] - path = "foobar" [http.middlewares.Middleware19] - [http.middlewares.Middleware19.replacePathRegex] + [http.middlewares.Middleware19.replacePath] + path = "foobar" + [http.middlewares.Middleware20] + [http.middlewares.Middleware20.replacePathRegex] regex = "foobar" replacement = "foobar" - [http.middlewares.Middleware20] - [http.middlewares.Middleware20.retry] + [http.middlewares.Middleware21] + [http.middlewares.Middleware21.retry] attempts = 42 initialInterval = "42s" - [http.middlewares.Middleware21] - [http.middlewares.Middleware21.stripPrefix] - prefixes = ["foobar", "foobar"] [http.middlewares.Middleware22] - [http.middlewares.Middleware22.stripPrefixRegex] - regex = ["foobar", "foobar"] + [http.middlewares.Middleware22.stripPrefix] + prefixes = ["foobar", "foobar"] [http.middlewares.Middleware23] - [http.middlewares.Middleware23.grpcWeb] + [http.middlewares.Middleware23.stripPrefixRegex] + regex = ["foobar", "foobar"] + [http.middlewares.Middleware24] + [http.middlewares.Middleware24.grpcWeb] allowOrigins = ["foobar", "foobar"] [http.serversTransports] [http.serversTransports.ServersTransport0] diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index 368ea3d5b..a1bd21658 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -224,6 +224,16 @@ http: permissionsPolicy: foobar isDevelopment: true Middleware11: + ipWhiteList: + sourceRange: + - foobar + - foobar + ipStrategy: + depth: 42 + excludedIPs: + - foobar + - foobar + Middleware12: ipAllowList: rejectStatusCode: 404 sourceRange: @@ -234,7 +244,7 @@ http: excludedIPs: - foobar - foobar - Middleware12: + Middleware13: inFlightReq: amount: 42 sourceCriterion: @@ -245,7 +255,7 @@ http: - foobar requestHeaderName: foobar requestHost: true - Middleware13: + Middleware14: passTLSClientCert: pem: true info: @@ -270,11 +280,11 @@ http: serialNumber: true domainComponent: true serialNumber: true - Middleware14: + Middleware15: plugin: PluginConf: foo: bar - Middleware15: + Middleware16: rateLimit: average: 42 period: 42s @@ -287,38 +297,38 @@ http: - foobar requestHeaderName: foobar requestHost: true - Middleware16: + Middleware17: redirectRegex: regex: foobar replacement: foobar permanent: true - Middleware17: + Middleware18: redirectScheme: scheme: foobar port: foobar permanent: true - Middleware18: + Middleware19: replacePath: path: foobar - Middleware19: + Middleware20: replacePathRegex: regex: foobar replacement: foobar - Middleware20: + Middleware21: retry: attempts: 42 initialInterval: 42s - Middleware21: + Middleware22: stripPrefix: prefixes: - foobar - foobar - Middleware22: + Middleware23: stripPrefixRegex: regex: - foobar - foobar - Middleware23: + Middleware24: grpcWeb: allowOrigins: - foobar diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index 504c5cd9c..e388639a9 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -1192,6 +1192,32 @@ spec: type: string type: array type: object + ipWhiteList: + description: 'Deprecated: please use IPAllowList instead.' + properties: + ipStrategy: + description: 'IPStrategy holds the IP strategy configuration used + by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position (starting + from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the X-Forwarded-For + header and select the first IP not in the list. + items: + type: string + type: array + type: object + sourceRange: + description: SourceRange defines the set of allowed IPs (or ranges + of allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object passTLSClientCert: description: 'PassTLSClientCert holds the pass TLS client cert middleware configuration. This middleware adds the selected data from the passed @@ -1528,6 +1554,17 @@ spec: type: string type: array type: object + ipWhiteList: + description: 'IPWhiteList defines the IPWhiteList middleware configuration. + Deprecated: please use IPAllowList instead.' + properties: + sourceRange: + description: SourceRange defines the allowed IPs (or ranges of + allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object type: object required: - metadata diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index ef5a7154f..13ed064b2 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -78,64 +78,69 @@ | `traefik/http/middlewares/Middleware10/headers/stsIncludeSubdomains` | `true` | | `traefik/http/middlewares/Middleware10/headers/stsPreload` | `true` | | `traefik/http/middlewares/Middleware10/headers/stsSeconds` | `42` | -| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/depth` | `42` | -| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/excludedIPs/0` | `foobar` | -| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/excludedIPs/1` | `foobar` | -| `traefik/http/middlewares/Middleware11/ipAllowList/rejectStatusCode` | `404` | -| `traefik/http/middlewares/Middleware11/ipAllowList/sourceRange/0` | `foobar` | -| `traefik/http/middlewares/Middleware11/ipAllowList/sourceRange/1` | `foobar` | -| `traefik/http/middlewares/Middleware12/inFlightReq/amount` | `42` | -| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/ipStrategy/depth` | `42` | -| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` | -| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` | -| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/requestHeaderName` | `foobar` | -| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/requestHost` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/commonName` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/country` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/domainComponent` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/locality` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/organization` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/province` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/serialNumber` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/notAfter` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/notBefore` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/sans` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/serialNumber` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/commonName` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/country` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/domainComponent` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/locality` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/organization` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/organizationalUnit` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/province` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/serialNumber` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/pem` | `true` | -| `traefik/http/middlewares/Middleware14/plugin/PluginConf/foo` | `bar` | -| `traefik/http/middlewares/Middleware15/rateLimit/average` | `42` | -| `traefik/http/middlewares/Middleware15/rateLimit/burst` | `42` | -| `traefik/http/middlewares/Middleware15/rateLimit/period` | `42s` | -| `traefik/http/middlewares/Middleware15/rateLimit/sourceCriterion/ipStrategy/depth` | `42` | -| `traefik/http/middlewares/Middleware15/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` | -| `traefik/http/middlewares/Middleware15/rateLimit/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` | -| `traefik/http/middlewares/Middleware15/rateLimit/sourceCriterion/requestHeaderName` | `foobar` | -| `traefik/http/middlewares/Middleware15/rateLimit/sourceCriterion/requestHost` | `true` | -| `traefik/http/middlewares/Middleware16/redirectRegex/permanent` | `true` | -| `traefik/http/middlewares/Middleware16/redirectRegex/regex` | `foobar` | -| `traefik/http/middlewares/Middleware16/redirectRegex/replacement` | `foobar` | -| `traefik/http/middlewares/Middleware17/redirectScheme/permanent` | `true` | -| `traefik/http/middlewares/Middleware17/redirectScheme/port` | `foobar` | -| `traefik/http/middlewares/Middleware17/redirectScheme/scheme` | `foobar` | -| `traefik/http/middlewares/Middleware18/replacePath/path` | `foobar` | -| `traefik/http/middlewares/Middleware19/replacePathRegex/regex` | `foobar` | -| `traefik/http/middlewares/Middleware19/replacePathRegex/replacement` | `foobar` | -| `traefik/http/middlewares/Middleware20/retry/attempts` | `42` | -| `traefik/http/middlewares/Middleware20/retry/initialInterval` | `42s` | -| `traefik/http/middlewares/Middleware21/stripPrefix/prefixes/0` | `foobar` | -| `traefik/http/middlewares/Middleware21/stripPrefix/prefixes/1` | `foobar` | -| `traefik/http/middlewares/Middleware22/stripPrefixRegex/regex/0` | `foobar` | -| `traefik/http/middlewares/Middleware22/stripPrefixRegex/regex/1` | `foobar` | -| `traefik/http/middlewares/Middleware23/grpcWeb/allowOrigins/0` | `foobar` | -| `traefik/http/middlewares/Middleware23/grpcWeb/allowOrigins/1` | `foobar` | +| `traefik/http/middlewares/Middleware11/ipWhiteList/ipStrategy/depth` | `42` | +| `traefik/http/middlewares/Middleware11/ipWhiteList/ipStrategy/excludedIPs/0` | `foobar` | +| `traefik/http/middlewares/Middleware11/ipWhiteList/ipStrategy/excludedIPs/1` | `foobar` | +| `traefik/http/middlewares/Middleware11/ipWhiteList/sourceRange/0` | `foobar` | +| `traefik/http/middlewares/Middleware11/ipWhiteList/sourceRange/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/ipAllowList/ipStrategy/depth` | `42` | +| `traefik/http/middlewares/Middleware12/ipAllowList/ipStrategy/excludedIPs/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/ipAllowList/ipStrategy/excludedIPs/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/ipAllowList/rejectStatusCode` | `404` | +| `traefik/http/middlewares/Middleware12/ipAllowList/sourceRange/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/ipAllowList/sourceRange/1` | `foobar` | +| `traefik/http/middlewares/Middleware13/inFlightReq/amount` | `42` | +| `traefik/http/middlewares/Middleware13/inFlightReq/sourceCriterion/ipStrategy/depth` | `42` | +| `traefik/http/middlewares/Middleware13/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` | +| `traefik/http/middlewares/Middleware13/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` | +| `traefik/http/middlewares/Middleware13/inFlightReq/sourceCriterion/requestHeaderName` | `foobar` | +| `traefik/http/middlewares/Middleware13/inFlightReq/sourceCriterion/requestHost` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/issuer/commonName` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/issuer/country` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/issuer/domainComponent` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/issuer/locality` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/issuer/organization` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/issuer/province` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/issuer/serialNumber` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/notAfter` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/notBefore` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/sans` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/serialNumber` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/subject/commonName` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/subject/country` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/subject/domainComponent` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/subject/locality` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/subject/organization` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/subject/organizationalUnit` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/subject/province` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/info/subject/serialNumber` | `true` | +| `traefik/http/middlewares/Middleware14/passTLSClientCert/pem` | `true` | +| `traefik/http/middlewares/Middleware15/plugin/PluginConf/foo` | `bar` | +| `traefik/http/middlewares/Middleware16/rateLimit/average` | `42` | +| `traefik/http/middlewares/Middleware16/rateLimit/burst` | `42` | +| `traefik/http/middlewares/Middleware16/rateLimit/period` | `42s` | +| `traefik/http/middlewares/Middleware16/rateLimit/sourceCriterion/ipStrategy/depth` | `42` | +| `traefik/http/middlewares/Middleware16/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` | +| `traefik/http/middlewares/Middleware16/rateLimit/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` | +| `traefik/http/middlewares/Middleware16/rateLimit/sourceCriterion/requestHeaderName` | `foobar` | +| `traefik/http/middlewares/Middleware16/rateLimit/sourceCriterion/requestHost` | `true` | +| `traefik/http/middlewares/Middleware17/redirectRegex/permanent` | `true` | +| `traefik/http/middlewares/Middleware17/redirectRegex/regex` | `foobar` | +| `traefik/http/middlewares/Middleware17/redirectRegex/replacement` | `foobar` | +| `traefik/http/middlewares/Middleware18/redirectScheme/permanent` | `true` | +| `traefik/http/middlewares/Middleware18/redirectScheme/port` | `foobar` | +| `traefik/http/middlewares/Middleware18/redirectScheme/scheme` | `foobar` | +| `traefik/http/middlewares/Middleware19/replacePath/path` | `foobar` | +| `traefik/http/middlewares/Middleware20/replacePathRegex/regex` | `foobar` | +| `traefik/http/middlewares/Middleware20/replacePathRegex/replacement` | `foobar` | +| `traefik/http/middlewares/Middleware21/retry/attempts` | `42` | +| `traefik/http/middlewares/Middleware21/retry/initialInterval` | `42s` | +| `traefik/http/middlewares/Middleware22/stripPrefix/prefixes/0` | `foobar` | +| `traefik/http/middlewares/Middleware22/stripPrefix/prefixes/1` | `foobar` | +| `traefik/http/middlewares/Middleware23/stripPrefixRegex/regex/0` | `foobar` | +| `traefik/http/middlewares/Middleware23/stripPrefixRegex/regex/1` | `foobar` | +| `traefik/http/middlewares/Middleware24/grpcWeb/allowOrigins/0` | `foobar` | +| `traefik/http/middlewares/Middleware24/grpcWeb/allowOrigins/1` | `foobar` | | `traefik/http/routers/Router0/entryPoints/0` | `foobar` | | `traefik/http/routers/Router0/entryPoints/1` | `foobar` | | `traefik/http/routers/Router0/middlewares/0` | `foobar` | diff --git a/docs/content/reference/dynamic-configuration/marathon-labels.json b/docs/content/reference/dynamic-configuration/marathon-labels.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index 1290a6163..6eb4089c4 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -617,6 +617,32 @@ spec: type: string type: array type: object + ipWhiteList: + description: 'Deprecated: please use IPAllowList instead.' + properties: + ipStrategy: + description: 'IPStrategy holds the IP strategy configuration used + by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position (starting + from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the X-Forwarded-For + header and select the first IP not in the list. + items: + type: string + type: array + type: object + sourceRange: + description: SourceRange defines the set of allowed IPs (or ranges + of allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object passTLSClientCert: description: 'PassTLSClientCert holds the pass TLS client cert middleware configuration. This middleware adds the selected data from the passed diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml index 462b9ed44..616e48b9c 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml @@ -55,6 +55,17 @@ spec: type: string type: array type: object + ipWhiteList: + description: 'IPWhiteList defines the IPWhiteList middleware configuration. + Deprecated: please use IPAllowList instead.' + properties: + sourceRange: + description: SourceRange defines the allowed IPs (or ranges of + allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object type: object required: - metadata diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 504c5cd9c..e388639a9 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -1192,6 +1192,32 @@ spec: type: string type: array type: object + ipWhiteList: + description: 'Deprecated: please use IPAllowList instead.' + properties: + ipStrategy: + description: 'IPStrategy holds the IP strategy configuration used + by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position (starting + from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the X-Forwarded-For + header and select the first IP not in the list. + items: + type: string + type: array + type: object + sourceRange: + description: SourceRange defines the set of allowed IPs (or ranges + of allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object passTLSClientCert: description: 'PassTLSClientCert holds the pass TLS client cert middleware configuration. This middleware adds the selected data from the passed @@ -1528,6 +1554,17 @@ spec: type: string type: array type: object + ipWhiteList: + description: 'IPWhiteList defines the IPWhiteList middleware configuration. + Deprecated: please use IPAllowList instead.' + properties: + sourceRange: + description: SourceRange defines the allowed IPs (or ranges of + allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object type: object required: - metadata diff --git a/integration/fixtures/simple_whitelist.toml b/integration/fixtures/simple_whitelist.toml new file mode 100644 index 000000000..03fa451e4 --- /dev/null +++ b/integration/fixtures/simple_whitelist.toml @@ -0,0 +1,18 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[entryPoints] + [entryPoints.web] + address = ":8000" + [entryPoints.web.ForwardedHeaders] + insecure = true + +[api] + insecure = true + +[providers] + [providers.docker] diff --git a/integration/fixtures/tcp/ip-allowlist.toml b/integration/fixtures/tcp/ip-allowlist.toml index 3cc13a532..786971f52 100644 --- a/integration/fixtures/tcp/ip-allowlist.toml +++ b/integration/fixtures/tcp/ip-allowlist.toml @@ -4,6 +4,7 @@ [log] level = "DEBUG" + noColor = true [entryPoints] diff --git a/integration/fixtures/tcp/ip-whitelist.toml b/integration/fixtures/tcp/ip-whitelist.toml new file mode 100644 index 000000000..b9c6cc998 --- /dev/null +++ b/integration/fixtures/tcp/ip-whitelist.toml @@ -0,0 +1,52 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + noColor = true + +[entryPoints] + [entryPoints.tcp] + address = ":8093" + +[api] + insecure = true + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[tcp] + [tcp.routers] + [tcp.routers.to-whoami-a] + entryPoints = ["tcp"] + rule = "HostSNI(`whoami-a.test`)" + service = "whoami-a" + middlewares = ["blocking-ipwhitelist"] + [tcp.routers.to-whoami-a.tls] + passthrough = true + + [tcp.routers.to-whoami-b] + entryPoints = ["tcp"] + rule = "HostSNI(`whoami-b.test`)" + service = "whoami-b" + middlewares = ["allowing-ipwhitelist"] + [tcp.routers.to-whoami-b.tls] + passthrough = true + + [tcp.services] + [tcp.services.whoami-a.loadBalancer] + [[tcp.services.whoami-a.loadBalancer.servers]] + address = "{{ .WhoamiA }}" + + [tcp.services.whoami-b.loadBalancer] + [[tcp.services.whoami-b.loadBalancer.servers]] + address = "{{ .WhoamiB }}" + +[tcp.middlewares] + [tcp.middlewares.allowing-ipwhitelist.ipWhiteList] + sourceRange = ["127.0.0.1/32"] + [tcp.middlewares.blocking-ipwhitelist.ipWhiteList] + sourceRange = ["127.127.127.127/32"] diff --git a/integration/resources/compose/whitelist.yml b/integration/resources/compose/whitelist.yml new file mode 100644 index 000000000..790ce52b7 --- /dev/null +++ b/integration/resources/compose/whitelist.yml @@ -0,0 +1,36 @@ +version: "3.8" +services: + noOverrideWhitelist: + image: traefik/whoami + labels: + traefik.enable: true + traefik.http.routers.rt1.rule: Host(`no.override.whitelist.docker.local`) + traefik.http.routers.rt1.middlewares: wl1 + traefik.http.middlewares.wl1.ipwhitelist.sourceRange: 8.8.8.8 + + overrideIPStrategyRemoteAddrWhitelist: + image: traefik/whoami + labels: + traefik.enable: true + traefik.http.routers.rt2.rule: Host(`override.remoteaddr.whitelist.docker.local`) + traefik.http.routers.rt2.middlewares: wl2 + traefik.http.middlewares.wl2.ipwhitelist.sourceRange: 8.8.8.8 + traefik.http.middlewares.wl2.ipwhitelist.ipStrategy: true + + overrideIPStrategyDepthWhitelist: + image: traefik/whoami + labels: + traefik.enable: true + traefik.http.routers.rt3.rule: Host(`override.depth.whitelist.docker.local`) + traefik.http.routers.rt3.middlewares: wl3 + traefik.http.middlewares.wl3.ipwhitelist.sourceRange: 8.8.8.8 + traefik.http.middlewares.wl3.ipwhitelist.ipStrategy.depth: 3 + + overrideIPStrategyExcludedIPsWhitelist: + image: traefik/whoami + labels: + traefik.enable: true + traefik.http.routers.rt4.rule: Host(`override.excludedips.whitelist.docker.local`) + traefik.http.routers.rt4.middlewares: wl4 + traefik.http.middlewares.wl4.ipwhitelist.sourceRange: 8.8.8.8 + traefik.http.middlewares.wl4.ipwhitelist.ipStrategy.excludedIPs: 10.0.0.1,10.0.0.2 diff --git a/integration/simple_test.go b/integration/simple_test.go index f30306619..d8f0f45b7 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -483,6 +483,69 @@ func (s *SimpleSuite) TestIPStrategyAllowlist() { } } +func (s *SimpleSuite) TestIPStrategyWhitelist() { + s.createComposeProject("whitelist") + + s.composeUp() + defer s.composeDown() + + s.traefikCmd(withConfigFile("fixtures/simple_whitelist.toml")) + + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("override")) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("override.remoteaddr.whitelist.docker.local")) + require.NoError(s.T(), err) + + testCases := []struct { + desc string + xForwardedFor string + host string + expectedStatusCode int + }{ + { + desc: "override remote addr reject", + xForwardedFor: "8.8.8.8,8.8.8.8", + host: "override.remoteaddr.whitelist.docker.local", + expectedStatusCode: 403, + }, + { + desc: "override depth accept", + xForwardedFor: "8.8.8.8,10.0.0.1,127.0.0.1", + host: "override.depth.whitelist.docker.local", + expectedStatusCode: 200, + }, + { + desc: "override depth reject", + xForwardedFor: "10.0.0.1,8.8.8.8,127.0.0.1", + host: "override.depth.whitelist.docker.local", + expectedStatusCode: 403, + }, + { + desc: "override excludedIPs reject", + xForwardedFor: "10.0.0.3,10.0.0.1,10.0.0.2", + host: "override.excludedips.whitelist.docker.local", + expectedStatusCode: 403, + }, + { + desc: "override excludedIPs accept", + xForwardedFor: "8.8.8.8,10.0.0.1,10.0.0.2", + host: "override.excludedips.whitelist.docker.local", + expectedStatusCode: 200, + }, + } + + for _, test := range testCases { + req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) + req.Header.Set("X-Forwarded-For", test.xForwardedFor) + req.Host = test.host + req.RequestURI = "" + + err = try.Request(req, 1*time.Second, try.StatusCodeIs(test.expectedStatusCode)) + require.NoErrorf(s.T(), err, "Error during %s: %v", test.desc, err) + } +} + func (s *SimpleSuite) TestXForwardedHeaders() { s.createComposeProject("allowlist") diff --git a/integration/tcp_test.go b/integration/tcp_test.go index 25b6168b9..732bc1f03 100644 --- a/integration/tcp_test.go +++ b/integration/tcp_test.go @@ -248,6 +248,30 @@ func (s *TCPSuite) TestMiddlewareAllowList() { assert.Contains(s.T(), out, "whoami-b") } +func (s *TCPSuite) TestMiddlewareWhiteList() { + file := s.adaptFile("fixtures/tcp/ip-whitelist.toml", struct { + WhoamiA string + WhoamiB string + }{ + WhoamiA: s.getComposeServiceIP("whoami-a") + ":8080", + WhoamiB: s.getComposeServiceIP("whoami-b") + ":8080", + }) + + s.traefikCmd(withConfigFile(file)) + + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-a.test`)")) + require.NoError(s.T(), err) + + // Traefik not passes through, ipWhiteList closes connection + _, err = guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-a.test") + assert.ErrorIs(s.T(), err, io.EOF) + + // Traefik passes through, termination handled by whoami-b + out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-b") +} + func (s *TCPSuite) TestWRR() { file := s.adaptFile("fixtures/tcp/wrr.toml", struct { WhoamiB string diff --git a/integration/testdata/rawdata-crd-label-selector.json b/integration/testdata/rawdata-crd-label-selector.json index b5c9f1669..445a7daf3 100644 --- a/integration/testdata/rawdata-crd-label-selector.json +++ b/integration/testdata/rawdata-crd-label-selector.json @@ -45,7 +45,7 @@ "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.6:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -59,7 +59,7 @@ ], "serverStatus": { "http://10.42.0.3:80": "UP", - "http://10.42.0.6:80": "UP" + "http://10.42.0.5:80": "UP" } }, "noop@internal": { diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index 81f397196..5724a7126 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -139,7 +139,7 @@ "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.7:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -153,7 +153,7 @@ ], "serverStatus": { "http://10.42.0.3:80": "UP", - "http://10.42.0.7:80": "UP" + "http://10.42.0.5:80": "UP" } }, "default-test2-route-23c7f4c450289ee29016@kubernetescrd": { @@ -163,7 +163,7 @@ "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.7:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -177,7 +177,7 @@ ], "serverStatus": { "http://10.42.0.3:80": "UP", - "http://10.42.0.7:80": "UP" + "http://10.42.0.5:80": "UP" } }, "default-testst-route-60ad45fcb5fc1f5f3629@kubernetescrd": { @@ -187,7 +187,7 @@ "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.7:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -202,7 +202,7 @@ ], "serverStatus": { "http://10.42.0.3:80": "UP", - "http://10.42.0.7:80": "UP" + "http://10.42.0.5:80": "UP" } }, "default-whoami-80@kubernetescrd": { @@ -212,7 +212,7 @@ "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.7:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -223,7 +223,7 @@ "status": "enabled", "serverStatus": { "http://10.42.0.3:80": "UP", - "http://10.42.0.7:80": "UP" + "http://10.42.0.5:80": "UP" } }, "default-wrr1@kubernetescrd": { @@ -295,7 +295,7 @@ "address": "10.42.0.2:8080" }, { - "address": "10.42.0.4:8080" + "address": "10.42.0.6:8080" } ] }, @@ -347,10 +347,10 @@ "loadBalancer": { "servers": [ { - "address": "10.42.0.5:8090" + "address": "10.42.0.4:8090" }, { - "address": "10.42.0.6:8090" + "address": "10.42.0.7:8090" } ] }, diff --git a/integration/testdata/rawdata-gateway.json b/integration/testdata/rawdata-gateway.json index d629cfc41..f8e364e66 100644 --- a/integration/testdata/rawdata-gateway.json +++ b/integration/testdata/rawdata-gateway.json @@ -127,7 +127,7 @@ "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.4:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -138,7 +138,7 @@ "status": "enabled", "serverStatus": { "http://10.42.0.3:80": "UP", - "http://10.42.0.4:80": "UP" + "http://10.42.0.5:80": "UP" } }, "noop@internal": { diff --git a/integration/testdata/rawdata-ingress-label-selector.json b/integration/testdata/rawdata-ingress-label-selector.json index d72826ae7..f8e4c88a3 100644 --- a/integration/testdata/rawdata-ingress-label-selector.json +++ b/integration/testdata/rawdata-ingress-label-selector.json @@ -83,10 +83,10 @@ "loadBalancer": { "servers": [ { - "url": "http://10.42.0.2:80" + "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.7:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -99,8 +99,8 @@ "default-test-ingress-whoami-test-whoami@kubernetes" ], "serverStatus": { - "http://10.42.0.2:80": "UP", - "http://10.42.0.7:80": "UP" + "http://10.42.0.3:80": "UP", + "http://10.42.0.5:80": "UP" } }, "noop@internal": { diff --git a/integration/testdata/rawdata-ingress.json b/integration/testdata/rawdata-ingress.json index ecfa38ee6..305cb3015 100644 --- a/integration/testdata/rawdata-ingress.json +++ b/integration/testdata/rawdata-ingress.json @@ -119,10 +119,10 @@ "loadBalancer": { "servers": [ { - "url": "XXXX" + "url": "http://10.42.0.3:80" }, { - "url": "XXXX" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -136,18 +136,18 @@ "default-whoami-keep-route-whoami-test-keep-keep@kubernetes" ], "serverStatus": { - "http://XXXX": "UP", - "http://XXXX": "UP" + "http://10.42.0.3:80": "UP", + "http://10.42.0.5:80": "UP" } }, "default-whoami-http@kubernetes": { "loadBalancer": { "servers": [ { - "url": "http://10.42.0.10:80" + "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.8:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -161,8 +161,8 @@ "default-test-ingress-whoami-test-whoami@kubernetes" ], "serverStatus": { - "http://10.42.0.10:80": "UP", - "http://10.42.0.8:80": "UP" + "http://10.42.0.3:80": "UP", + "http://10.42.0.5:80": "UP" } }, "noop@internal": { diff --git a/integration/testdata/rawdata-ingressclass.json b/integration/testdata/rawdata-ingressclass.json index 4215504c0..a020096cc 100644 --- a/integration/testdata/rawdata-ingressclass.json +++ b/integration/testdata/rawdata-ingressclass.json @@ -83,7 +83,7 @@ "loadBalancer": { "servers": [ { - "url": "http://10.42.0.4:80" + "url": "http://10.42.0.3:80" }, { "url": "http://10.42.0.5:80" @@ -99,7 +99,7 @@ "default-whoami-keep-route-whoami-test-keep-keep@kubernetes" ], "serverStatus": { - "http://10.42.0.4:80": "UP", + "http://10.42.0.3:80": "UP", "http://10.42.0.5:80": "UP" } }, diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index a73881a2a..dcb884337 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -12,12 +12,14 @@ import ( // Middleware holds the Middleware configuration. type Middleware struct { - AddPrefix *AddPrefix `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty" export:"true"` - StripPrefix *StripPrefix `json:"stripPrefix,omitempty" toml:"stripPrefix,omitempty" yaml:"stripPrefix,omitempty" export:"true"` - StripPrefixRegex *StripPrefixRegex `json:"stripPrefixRegex,omitempty" toml:"stripPrefixRegex,omitempty" yaml:"stripPrefixRegex,omitempty" export:"true"` - ReplacePath *ReplacePath `json:"replacePath,omitempty" toml:"replacePath,omitempty" yaml:"replacePath,omitempty" export:"true"` - ReplacePathRegex *ReplacePathRegex `json:"replacePathRegex,omitempty" toml:"replacePathRegex,omitempty" yaml:"replacePathRegex,omitempty" export:"true"` - Chain *Chain `json:"chain,omitempty" toml:"chain,omitempty" yaml:"chain,omitempty" export:"true"` + AddPrefix *AddPrefix `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty" export:"true"` + StripPrefix *StripPrefix `json:"stripPrefix,omitempty" toml:"stripPrefix,omitempty" yaml:"stripPrefix,omitempty" export:"true"` + StripPrefixRegex *StripPrefixRegex `json:"stripPrefixRegex,omitempty" toml:"stripPrefixRegex,omitempty" yaml:"stripPrefixRegex,omitempty" export:"true"` + ReplacePath *ReplacePath `json:"replacePath,omitempty" toml:"replacePath,omitempty" yaml:"replacePath,omitempty" export:"true"` + ReplacePathRegex *ReplacePathRegex `json:"replacePathRegex,omitempty" toml:"replacePathRegex,omitempty" yaml:"replacePathRegex,omitempty" export:"true"` + Chain *Chain `json:"chain,omitempty" toml:"chain,omitempty" yaml:"chain,omitempty" export:"true"` + // Deprecated: please use IPAllowList instead. + IPWhiteList *IPWhiteList `json:"ipWhiteList,omitempty" toml:"ipWhiteList,omitempty" yaml:"ipWhiteList,omitempty" export:"true"` IPAllowList *IPAllowList `json:"ipAllowList,omitempty" toml:"ipAllowList,omitempty" yaml:"ipAllowList,omitempty" export:"true"` Headers *Headers `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"` Errors *ErrorPage `json:"errors,omitempty" toml:"errors,omitempty" yaml:"errors,omitempty" export:"true"` @@ -376,6 +378,18 @@ func (s *IPStrategy) Get() (ip.Strategy, error) { // +k8s:deepcopy-gen=true +// IPWhiteList holds the IP whitelist middleware configuration. +// This middleware accepts / refuses requests based on the client IP. +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipwhitelist/ +// Deprecated: please use IPAllowList instead. +type IPWhiteList struct { + // SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation). + SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"` + IPStrategy *IPStrategy `json:"ipStrategy,omitempty" toml:"ipStrategy,omitempty" yaml:"ipStrategy,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` +} + +// +k8s:deepcopy-gen=true + // IPAllowList holds the IP allowlist middleware configuration. // This middleware accepts / refuses requests based on the client IP. // More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/ diff --git a/pkg/config/dynamic/tcp_middlewares.go b/pkg/config/dynamic/tcp_middlewares.go index 018ae65f7..4368dc334 100644 --- a/pkg/config/dynamic/tcp_middlewares.go +++ b/pkg/config/dynamic/tcp_middlewares.go @@ -5,7 +5,9 @@ package dynamic // TCPMiddleware holds the TCPMiddleware configuration. type TCPMiddleware struct { InFlightConn *TCPInFlightConn `json:"inFlightConn,omitempty" toml:"inFlightConn,omitempty" yaml:"inFlightConn,omitempty" export:"true"` - IPAllowList *TCPIPAllowList `json:"ipAllowList,omitempty" toml:"ipAllowList,omitempty" yaml:"ipAllowList,omitempty" export:"true"` + // Deprecated: please use IPAllowList instead. + IPWhiteList *TCPIPWhiteList `json:"ipWhiteList,omitempty" toml:"ipWhiteList,omitempty" yaml:"ipWhiteList,omitempty" export:"true"` + IPAllowList *TCPIPAllowList `json:"ipAllowList,omitempty" toml:"ipAllowList,omitempty" yaml:"ipAllowList,omitempty" export:"true"` } // +k8s:deepcopy-gen=true @@ -22,6 +24,15 @@ type TCPInFlightConn struct { // +k8s:deepcopy-gen=true +// TCPIPWhiteList holds the TCP IPWhiteList middleware configuration. +// Deprecated: please use IPAllowList instead. +type TCPIPWhiteList struct { + // SourceRange defines the allowed IPs (or ranges of allowed IPs by using CIDR notation). + SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"` +} + +// +k8s:deepcopy-gen=true + // TCPIPAllowList holds the TCP IPAllowList middleware configuration. type TCPIPAllowList struct { // SourceRange defines the allowed IPs (or ranges of allowed IPs by using CIDR notation). diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index ae5442a2e..2f2a11e2e 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -600,6 +600,32 @@ func (in *IPStrategy) DeepCopy() *IPStrategy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPWhiteList) DeepCopyInto(out *IPWhiteList) { + *out = *in + if in.SourceRange != nil { + in, out := &in.SourceRange, &out.SourceRange + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.IPStrategy != nil { + in, out := &in.IPStrategy, &out.IPStrategy + *out = new(IPStrategy) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPWhiteList. +func (in *IPWhiteList) DeepCopy() *IPWhiteList { + if in == nil { + return nil + } + out := new(IPWhiteList) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InFlightReq) DeepCopyInto(out *InFlightReq) { *out = *in @@ -675,6 +701,11 @@ func (in *Middleware) DeepCopyInto(out *Middleware) { *out = new(Chain) (*in).DeepCopyInto(*out) } + if in.IPWhiteList != nil { + in, out := &in.IPWhiteList, &out.IPWhiteList + *out = new(IPWhiteList) + (*in).DeepCopyInto(*out) + } if in.IPAllowList != nil { in, out := &in.IPAllowList, &out.IPAllowList *out = new(IPAllowList) @@ -1443,6 +1474,27 @@ func (in *TCPIPAllowList) DeepCopy() *TCPIPAllowList { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TCPIPWhiteList) DeepCopyInto(out *TCPIPWhiteList) { + *out = *in + if in.SourceRange != nil { + in, out := &in.SourceRange, &out.SourceRange + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPIPWhiteList. +func (in *TCPIPWhiteList) DeepCopy() *TCPIPWhiteList { + if in == nil { + return nil + } + out := new(TCPIPWhiteList) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TCPInFlightConn) DeepCopyInto(out *TCPInFlightConn) { *out = *in @@ -1467,6 +1519,11 @@ func (in *TCPMiddleware) DeepCopyInto(out *TCPMiddleware) { *out = new(TCPInFlightConn) **out = **in } + if in.IPWhiteList != nil { + in, out := &in.IPWhiteList, &out.IPWhiteList + *out = new(TCPIPWhiteList) + (*in).DeepCopyInto(*out) + } if in.IPAllowList != nil { in, out := &in.IPAllowList, &out.IPAllowList *out = new(TCPIPAllowList) diff --git a/pkg/middlewares/ipwhitelist/ip_whitelist.go b/pkg/middlewares/ipwhitelist/ip_whitelist.go new file mode 100644 index 000000000..937bde420 --- /dev/null +++ b/pkg/middlewares/ipwhitelist/ip_whitelist.go @@ -0,0 +1,88 @@ +package ipwhitelist + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/ip" + "github.com/traefik/traefik/v3/pkg/middlewares" + "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +const ( + typeName = "IPWhiteLister" +) + +// ipWhiteLister is a middleware that provides Checks of the Requesting IP against a set of Whitelists. +type ipWhiteLister struct { + next http.Handler + whiteLister *ip.Checker + strategy ip.Strategy + name string +} + +// New builds a new IPWhiteLister given a list of CIDR-Strings to whitelist. +func New(ctx context.Context, next http.Handler, config dynamic.IPWhiteList, name string) (http.Handler, error) { + logger := middlewares.GetLogger(ctx, name, typeName) + logger.Debug().Msg("Creating middleware") + + if len(config.SourceRange) == 0 { + return nil, errors.New("sourceRange is empty, IPWhiteLister not created") + } + + checker, err := ip.NewChecker(config.SourceRange) + if err != nil { + return nil, fmt.Errorf("cannot parse CIDR whitelist %s: %w", config.SourceRange, err) + } + + strategy, err := config.IPStrategy.Get() + if err != nil { + return nil, err + } + + logger.Debug().Msgf("Setting up IPWhiteLister with sourceRange: %s", config.SourceRange) + + return &ipWhiteLister{ + strategy: strategy, + whiteLister: checker, + next: next, + name: name, + }, nil +} + +func (wl *ipWhiteLister) GetTracingInformation() (string, string, trace.SpanKind) { + return wl.name, typeName, trace.SpanKindInternal +} + +func (wl *ipWhiteLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + logger := middlewares.GetLogger(req.Context(), wl.name, typeName) + ctx := logger.WithContext(req.Context()) + + clientIP := wl.strategy.GetIP(req) + err := wl.whiteLister.IsAuthorized(clientIP) + if err != nil { + msg := fmt.Sprintf("Rejecting IP %s: %v", clientIP, err) + logger.Debug().Msg(msg) + tracing.SetStatusErrorf(req.Context(), msg) + reject(ctx, rw) + return + } + logger.Debug().Msgf("Accepting IP %s", clientIP) + + wl.next.ServeHTTP(rw, req) +} + +func reject(ctx context.Context, rw http.ResponseWriter) { + statusCode := http.StatusForbidden + + rw.WriteHeader(statusCode) + _, err := rw.Write([]byte(http.StatusText(statusCode))) + if err != nil { + log.Ctx(ctx).Error().Err(err).Send() + } +} diff --git a/pkg/middlewares/ipwhitelist/ip_whitelist_test.go b/pkg/middlewares/ipwhitelist/ip_whitelist_test.go new file mode 100644 index 000000000..5e040016a --- /dev/null +++ b/pkg/middlewares/ipwhitelist/ip_whitelist_test.go @@ -0,0 +1,100 @@ +package ipwhitelist + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/config/dynamic" +) + +func TestNewIPWhiteLister(t *testing.T) { + testCases := []struct { + desc string + whiteList dynamic.IPWhiteList + expectedError bool + }{ + { + desc: "invalid IP", + whiteList: dynamic.IPWhiteList{ + SourceRange: []string{"foo"}, + }, + expectedError: true, + }, + { + desc: "valid IP", + whiteList: dynamic.IPWhiteList{ + SourceRange: []string{"10.10.10.10"}, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + + if test.expectedError { + assert.Error(t, err) + } else { + require.NoError(t, err) + assert.NotNil(t, whiteLister) + } + }) + } +} + +func TestIPWhiteLister_ServeHTTP(t *testing.T) { + testCases := []struct { + desc string + whiteList dynamic.IPWhiteList + remoteAddr string + expected int + }{ + { + desc: "authorized with remote address", + whiteList: dynamic.IPWhiteList{ + SourceRange: []string{"20.20.20.20"}, + }, + remoteAddr: "20.20.20.20:1234", + expected: 200, + }, + { + desc: "non authorized with remote address", + whiteList: dynamic.IPWhiteList{ + SourceRange: []string{"20.20.20.20"}, + }, + remoteAddr: "20.20.20.21:1234", + expected: 403, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + require.NoError(t, err) + + recorder := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, "http://10.10.10.10", nil) + + if len(test.remoteAddr) > 0 { + req.RemoteAddr = test.remoteAddr + } + + whiteLister.ServeHTTP(recorder, req) + + assert.Equal(t, test.expected, recorder.Code) + }) + } +} diff --git a/pkg/middlewares/tcp/ipwhitelist/ip_whitelist.go b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist.go new file mode 100644 index 000000000..86f3b6ac0 --- /dev/null +++ b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist.go @@ -0,0 +1,63 @@ +package ipwhitelist + +import ( + "context" + "errors" + "fmt" + + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/ip" + "github.com/traefik/traefik/v3/pkg/middlewares" + "github.com/traefik/traefik/v3/pkg/tcp" +) + +const ( + typeName = "IPWhiteListerTCP" +) + +// ipWhiteLister is a middleware that provides Checks of the Requesting IP against a set of Whitelists. +type ipWhiteLister struct { + next tcp.Handler + whiteLister *ip.Checker + name string +} + +// New builds a new TCP IPWhiteLister given a list of CIDR-Strings to whitelist. +func New(ctx context.Context, next tcp.Handler, config dynamic.TCPIPWhiteList, name string) (tcp.Handler, error) { + logger := middlewares.GetLogger(ctx, name, typeName) + logger.Debug().Msg("Creating middleware") + + if len(config.SourceRange) == 0 { + return nil, errors.New("sourceRange is empty, IPWhiteLister not created") + } + + checker, err := ip.NewChecker(config.SourceRange) + if err != nil { + return nil, fmt.Errorf("cannot parse CIDR whitelist %s: %w", config.SourceRange, err) + } + + logger.Debug().Msgf("Setting up IPWhiteLister with sourceRange: %s", config.SourceRange) + + return &ipWhiteLister{ + whiteLister: checker, + next: next, + name: name, + }, nil +} + +func (wl *ipWhiteLister) ServeTCP(conn tcp.WriteCloser) { + logger := middlewares.GetLogger(context.Background(), wl.name, typeName) + + addr := conn.RemoteAddr().String() + + err := wl.whiteLister.IsAuthorized(addr) + if err != nil { + logger.Error().Err(err).Msgf("Connection from %s rejected", addr) + conn.Close() + return + } + + logger.Debug().Msgf("Connection from %s accepted", addr) + + wl.next.ServeTCP(conn) +} diff --git a/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go new file mode 100644 index 000000000..72fa33d0c --- /dev/null +++ b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go @@ -0,0 +1,139 @@ +package ipwhitelist + +import ( + "context" + "io" + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/tcp" +) + +func TestNewIPWhiteLister(t *testing.T) { + testCases := []struct { + desc string + whiteList dynamic.TCPIPWhiteList + expectedError bool + }{ + { + desc: "Empty config", + whiteList: dynamic.TCPIPWhiteList{}, + expectedError: true, + }, + { + desc: "invalid IP", + whiteList: dynamic.TCPIPWhiteList{ + SourceRange: []string{"foo"}, + }, + expectedError: true, + }, + { + desc: "valid IP", + whiteList: dynamic.TCPIPWhiteList{ + SourceRange: []string{"10.10.10.10"}, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + next := tcp.HandlerFunc(func(conn tcp.WriteCloser) {}) + whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + + if test.expectedError { + assert.Error(t, err) + } else { + require.NoError(t, err) + assert.NotNil(t, whiteLister) + } + }) + } +} + +func TestIPWhiteLister_ServeHTTP(t *testing.T) { + testCases := []struct { + desc string + whiteList dynamic.TCPIPWhiteList + remoteAddr string + expected string + }{ + { + desc: "authorized with remote address", + whiteList: dynamic.TCPIPWhiteList{ + SourceRange: []string{"20.20.20.20"}, + }, + remoteAddr: "20.20.20.20:1234", + expected: "OK", + }, + { + desc: "non authorized with remote address", + whiteList: dynamic.TCPIPWhiteList{ + SourceRange: []string{"20.20.20.20"}, + }, + remoteAddr: "20.20.20.21:1234", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + next := tcp.HandlerFunc(func(conn tcp.WriteCloser) { + write, err := conn.Write([]byte("OK")) + require.NoError(t, err) + assert.Equal(t, 2, write) + + err = conn.Close() + require.NoError(t, err) + }) + + whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + require.NoError(t, err) + + server, client := net.Pipe() + + go func() { + whiteLister.ServeTCP(&contextWriteCloser{client, addr{test.remoteAddr}}) + }() + + read, err := io.ReadAll(server) + require.NoError(t, err) + + assert.Equal(t, test.expected, string(read)) + }) + } +} + +type contextWriteCloser struct { + net.Conn + addr +} + +type addr struct { + remoteAddr string +} + +func (a addr) Network() string { + panic("implement me") +} + +func (a addr) String() string { + return a.remoteAddr +} + +func (c contextWriteCloser) CloseWrite() error { + panic("implement me") +} + +func (c contextWriteCloser) RemoteAddr() net.Addr { return c.addr } + +func (c contextWriteCloser) Context() context.Context { + return context.Background() +} diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 0e22a9390..9922ed989 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -288,6 +288,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) ReplacePath: middleware.Spec.ReplacePath, ReplacePathRegex: middleware.Spec.ReplacePathRegex, Chain: createChainMiddleware(ctxMid, middleware.Namespace, middleware.Spec.Chain), + IPWhiteList: middleware.Spec.IPWhiteList, IPAllowList: middleware.Spec.IPAllowList, Headers: middleware.Spec.Headers, Errors: errorPage, @@ -314,6 +315,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) conf.TCP.Middlewares[id] = &dynamic.TCPMiddleware{ InFlightConn: middlewareTCP.Spec.InFlightConn, + IPWhiteList: middlewareTCP.Spec.IPWhiteList, IPAllowList: middlewareTCP.Spec.IPAllowList, } } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go index 51078f856..c86d51d3a 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go @@ -26,12 +26,14 @@ type Middleware struct { // MiddlewareSpec defines the desired state of a Middleware. type MiddlewareSpec struct { - AddPrefix *dynamic.AddPrefix `json:"addPrefix,omitempty"` - StripPrefix *dynamic.StripPrefix `json:"stripPrefix,omitempty"` - StripPrefixRegex *dynamic.StripPrefixRegex `json:"stripPrefixRegex,omitempty"` - ReplacePath *dynamic.ReplacePath `json:"replacePath,omitempty"` - ReplacePathRegex *dynamic.ReplacePathRegex `json:"replacePathRegex,omitempty"` - Chain *Chain `json:"chain,omitempty"` + AddPrefix *dynamic.AddPrefix `json:"addPrefix,omitempty"` + StripPrefix *dynamic.StripPrefix `json:"stripPrefix,omitempty"` + StripPrefixRegex *dynamic.StripPrefixRegex `json:"stripPrefixRegex,omitempty"` + ReplacePath *dynamic.ReplacePath `json:"replacePath,omitempty"` + ReplacePathRegex *dynamic.ReplacePathRegex `json:"replacePathRegex,omitempty"` + Chain *Chain `json:"chain,omitempty"` + // Deprecated: please use IPAllowList instead. + IPWhiteList *dynamic.IPWhiteList `json:"ipWhiteList,omitempty"` IPAllowList *dynamic.IPAllowList `json:"ipAllowList,omitempty"` Headers *dynamic.Headers `json:"headers,omitempty"` Errors *ErrorPage `json:"errors,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go index 10799f295..d15402a26 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go @@ -25,6 +25,9 @@ type MiddlewareTCP struct { type MiddlewareTCPSpec struct { // InFlightConn defines the InFlightConn middleware configuration. InFlightConn *dynamic.TCPInFlightConn `json:"inFlightConn,omitempty"` + // IPWhiteList defines the IPWhiteList middleware configuration. + // Deprecated: please use IPAllowList instead. + IPWhiteList *dynamic.TCPIPWhiteList `json:"ipWhiteList,omitempty"` // IPAllowList defines the IPAllowList middleware configuration. IPAllowList *dynamic.TCPIPAllowList `json:"ipAllowList,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go index 200969b3d..b08d2f7c4 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go @@ -689,6 +689,11 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) { *out = new(Chain) (*in).DeepCopyInto(*out) } + if in.IPWhiteList != nil { + in, out := &in.IPWhiteList, &out.IPWhiteList + *out = new(dynamic.IPWhiteList) + (*in).DeepCopyInto(*out) + } if in.IPAllowList != nil { in, out := &in.IPAllowList, &out.IPAllowList *out = new(dynamic.IPAllowList) @@ -862,6 +867,11 @@ func (in *MiddlewareTCPSpec) DeepCopyInto(out *MiddlewareTCPSpec) { *out = new(dynamic.TCPInFlightConn) **out = **in } + if in.IPWhiteList != nil { + in, out := &in.IPWhiteList, &out.IPWhiteList + *out = new(dynamic.TCPIPWhiteList) + (*in).DeepCopyInto(*out) + } if in.IPAllowList != nil { in, out := &in.IPAllowList, &out.IPAllowList *out = new(dynamic.TCPIPAllowList) diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go index fe2fd23a9..72da39663 100644 --- a/pkg/server/middleware/middlewares.go +++ b/pkg/server/middleware/middlewares.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/containous/alice" + "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/middlewares/addprefix" "github.com/traefik/traefik/v3/pkg/middlewares/auth" @@ -22,6 +23,7 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares/headers" "github.com/traefik/traefik/v3/pkg/middlewares/inflightreq" "github.com/traefik/traefik/v3/pkg/middlewares/ipallowlist" + "github.com/traefik/traefik/v3/pkg/middlewares/ipwhitelist" "github.com/traefik/traefik/v3/pkg/middlewares/passtlsclientcert" "github.com/traefik/traefik/v3/pkg/middlewares/ratelimiter" "github.com/traefik/traefik/v3/pkg/middlewares/redirect" @@ -236,6 +238,18 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( } } + // IPWhiteList + if config.IPWhiteList != nil { + log.Warn().Msg("IPWhiteList is deprecated, please use IPAllowList instead.") + + if middleware != nil { + return nil, badConf + } + middleware = func(next http.Handler) (http.Handler, error) { + return ipwhitelist.New(ctx, next, *config.IPWhiteList, middlewareName) + } + } + // IPAllowList if config.IPAllowList != nil { if middleware != nil { diff --git a/pkg/server/middleware/tcp/middlewares.go b/pkg/server/middleware/tcp/middlewares.go index e03a4fb84..0a5d30bc8 100644 --- a/pkg/server/middleware/tcp/middlewares.go +++ b/pkg/server/middleware/tcp/middlewares.go @@ -5,9 +5,11 @@ import ( "fmt" "strings" + "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/middlewares/tcp/inflightconn" "github.com/traefik/traefik/v3/pkg/middlewares/tcp/ipallowlist" + "github.com/traefik/traefik/v3/pkg/middlewares/tcp/ipwhitelist" "github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/tcp" ) @@ -94,6 +96,15 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( } } + // IPWhiteList + if config.IPWhiteList != nil { + log.Warn().Msg("IPWhiteList is deprecated, please use IPAllowList instead.") + + middleware = func(next tcp.Handler) (tcp.Handler, error) { + return ipwhitelist.New(ctx, next, *config.IPWhiteList, middlewareName) + } + } + // IPAllowList if config.IPAllowList != nil { middleware = func(next tcp.Handler) (tcp.Handler, error) {