From d3516aec313c35faeb89a2f35f3b3532d19c7ccb Mon Sep 17 00:00:00 2001 From: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> Date: Thu, 4 Apr 2024 11:32:05 +0200 Subject: [PATCH 01/18] docs: excludedIPs with IPWhiteList and IPAllowList middleware --- docs/content/middlewares/http/ipallowlist.md | 63 +++++++++++++++++-- docs/content/middlewares/http/ipwhitelist.md | 19 +++++- docs/content/middlewares/tcp/ipallowlist.md | 2 +- .../kubernetes-crd-definition-v1.yml | 12 ++-- .../traefik.containo.us_middlewares.yaml | 6 +- .../traefik.io_middlewares.yaml | 6 +- integration/fixtures/k8s/01-traefik-crd.yml | 12 ++-- pkg/config/dynamic/middlewares.go | 6 +- pkg/config/dynamic/tcp_middlewares.go | 5 ++ 9 files changed, 100 insertions(+), 31 deletions(-) diff --git a/docs/content/middlewares/http/ipallowlist.md b/docs/content/middlewares/http/ipallowlist.md index d62e253bb..ec7d70e03 100644 --- a/docs/content/middlewares/http/ipallowlist.md +++ b/docs/content/middlewares/http/ipallowlist.md @@ -8,11 +8,11 @@ description: "Learn how to use IPAllowList in HTTP middleware for limiting clien Limiting Clients to Specific IPs {: .subtitle } -IPAllowList accepts / refuses requests based on the client IP. +IPAllowList limits allowed requests based on the client IP. ## Configuration Examples -```yaml tab="Docker & Swarm" +```yaml tab="Docker" # Accepts request from defined IP labels: - "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7" @@ -35,6 +35,18 @@ spec: - "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7" ``` +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange": "127.0.0.1/32,192.168.1.7" +} +``` + +```yaml tab="Rancher" +# Accepts request from defined IP +labels: + - "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7" +``` + ```yaml tab="File (YAML)" # Accepts request from defined IP http: @@ -57,6 +69,8 @@ http: ### `sourceRange` +_Required_ + The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs by using CIDR notation). ### `ipStrategy` @@ -83,7 +97,7 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and take th | `"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 & Swarm" +```yaml tab="Docker" # Allowlisting Based on `X-Forwarded-For` with `depth=2` labels: - "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7" @@ -111,6 +125,20 @@ spec: - "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.depth=2" ``` +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange": "127.0.0.1/32, 192.168.1.7", + "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.depth": "2" +} +``` + +```yaml tab="Rancher" +# Whitelisting Based on `X-Forwarded-For` with `depth=2` +labels: + - "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7" + - "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.depth=2" +``` + ```yaml tab="File (YAML)" # Allowlisting Based on `X-Forwarded-For` with `depth=2` http: @@ -149,9 +177,10 @@ http: | `"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 & Swarm" +```yaml tab="Docker" # Exclude from `X-Forwarded-For` labels: + - "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourceRange=127.0.0.1/32, 192.168.1.0/24" - "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" ``` @@ -163,6 +192,9 @@ metadata: name: test-ipallowlist spec: ipAllowList: + sourceRange: + - 127.0.0.1/32 + - 192.168.1.0/24 ipStrategy: excludedIPs: - 127.0.0.1/32 @@ -171,25 +203,44 @@ spec: ```yaml tab="Consul Catalog" # Exclude from `X-Forwarded-For` +- "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourceRange=127.0.0.1/32, 192.168.1.0/24" - "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" ``` +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourceRange=127.0.0.1/32, 192.168.1.0/24" + "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.excludedips": "127.0.0.1/32, 192.168.1.7" +} +``` + +```yaml tab="Rancher" +# Exclude from `X-Forwarded-For` +labels: + - "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourceRange=127.0.0.1/32, 192.168.1.0/24" + - "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" +``` + ```yaml tab="File (YAML)" # Exclude from `X-Forwarded-For` http: middlewares: test-ipallowlist: ipAllowList: + sourceRange: + - 127.0.0.1/32 + - 192.168.1.0/24 ipStrategy: excludedIPs: - - "127.0.0.1/32" - - "192.168.1.7" + - 127.0.0.1/32 + - 192.168.1.7 ``` ```toml tab="File (TOML)" # Exclude from `X-Forwarded-For` [http.middlewares] [http.middlewares.test-ipallowlist.ipAllowList] + sourceRange = ["127.0.0.1/32", "192.168.1.0/24"] [http.middlewares.test-ipallowlist.ipAllowList.ipStrategy] excludedIPs = ["127.0.0.1/32", "192.168.1.7"] ``` diff --git a/docs/content/middlewares/http/ipwhitelist.md b/docs/content/middlewares/http/ipwhitelist.md index eaf761541..13ca1c0f0 100644 --- a/docs/content/middlewares/http/ipwhitelist.md +++ b/docs/content/middlewares/http/ipwhitelist.md @@ -10,7 +10,7 @@ Limiting Clients to Specific IPs ![IPWhiteList](../../assets/img/middleware/ipwhitelist.png) -IPWhiteList accepts / refuses requests based on the client IP. +IPWhiteList limits allowed requests based on the client IP. !!! warning @@ -75,6 +75,8 @@ http: ### `sourceRange` +_Required_ + The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs by using CIDR notation). ### `ipStrategy` @@ -184,6 +186,7 @@ http: ```yaml tab="Docker" # Exclude from `X-Forwarded-For` labels: + - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourceRange=127.0.0.1/32, 192.168.1.0/24" - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" ``` @@ -196,6 +199,9 @@ metadata: spec: ipWhiteList: ipStrategy: + sourceRange: + - 127.0.0.1/32 + - 192.168.1.0/24 excludedIPs: - 127.0.0.1/32 - 192.168.1.7 @@ -203,11 +209,13 @@ spec: ```yaml tab="Consul Catalog" # Exclude from `X-Forwarded-For` +- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourceRange=127.0.0.1/32, 192.168.1.0/24" - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" ``` ```json tab="Marathon" "labels": { + "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourceRange=127.0.0.1/32, 192.168.1.0/24" "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.excludedips": "127.0.0.1/32, 192.168.1.7" } ``` @@ -215,6 +223,7 @@ spec: ```yaml tab="Rancher" # Exclude from `X-Forwarded-For` labels: + - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourceRange=127.0.0.1/32, 192.168.1.0/24" - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" ``` @@ -224,16 +233,20 @@ http: middlewares: test-ipwhitelist: ipWhiteList: + sourceRange: + - 127.0.0.1/32 + - 192.168.1.0/24 ipStrategy: excludedIPs: - - "127.0.0.1/32" - - "192.168.1.7" + - 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] + sourceRange = ["127.0.0.1/32", "192.168.1.0/24"] [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 e8466b94e..39e014ef7 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 } -IPAllowList accepts / refuses connections based on the client IP. +IPAllowList limits allowed requests based on the client IP. ## Configuration Examples 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 85ff27f7e..2b4b4aad6 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -1241,7 +1241,7 @@ spec: ipAllowList: description: |- IPAllowList holds the IP allowlist middleware configuration. - This middleware accepts / refuses requests based on the client IP. + This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipallowlist/ properties: ipStrategy: @@ -1271,7 +1271,7 @@ spec: ipWhiteList: description: |- IPWhiteList holds the IP whitelist middleware configuration. - This middleware accepts / refuses requests based on the client IP. + This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ Deprecated: please use IPAllowList instead. properties: @@ -1294,7 +1294,7 @@ spec: type: object sourceRange: description: SourceRange defines the set of allowed IPs (or ranges - of allowed IPs by using CIDR notation). + of allowed IPs by using CIDR notation). Required. items: type: string type: array @@ -3671,7 +3671,7 @@ spec: ipAllowList: description: |- IPAllowList holds the IP allowlist middleware configuration. - This middleware accepts / refuses requests based on the client IP. + This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipallowlist/ properties: ipStrategy: @@ -3701,7 +3701,7 @@ spec: ipWhiteList: description: |- IPWhiteList holds the IP whitelist middleware configuration. - This middleware accepts / refuses requests based on the client IP. + This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ Deprecated: please use IPAllowList instead. properties: @@ -3724,7 +3724,7 @@ spec: type: object sourceRange: description: SourceRange defines the set of allowed IPs (or ranges - of allowed IPs by using CIDR notation). + of allowed IPs by using CIDR notation). Required. items: type: string type: array diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml index be0af55c5..605b8af5f 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml @@ -626,7 +626,7 @@ spec: ipAllowList: description: |- IPAllowList holds the IP allowlist middleware configuration. - This middleware accepts / refuses requests based on the client IP. + This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipallowlist/ properties: ipStrategy: @@ -656,7 +656,7 @@ spec: ipWhiteList: description: |- IPWhiteList holds the IP whitelist middleware configuration. - This middleware accepts / refuses requests based on the client IP. + This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ Deprecated: please use IPAllowList instead. properties: @@ -679,7 +679,7 @@ spec: type: object sourceRange: description: SourceRange defines the set of allowed IPs (or ranges - of allowed IPs by using CIDR notation). + of allowed IPs by using CIDR notation). Required. items: type: string type: array diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index 66913e653..0068a365f 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -626,7 +626,7 @@ spec: ipAllowList: description: |- IPAllowList holds the IP allowlist middleware configuration. - This middleware accepts / refuses requests based on the client IP. + This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipallowlist/ properties: ipStrategy: @@ -656,7 +656,7 @@ spec: ipWhiteList: description: |- IPWhiteList holds the IP whitelist middleware configuration. - This middleware accepts / refuses requests based on the client IP. + This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ Deprecated: please use IPAllowList instead. properties: @@ -679,7 +679,7 @@ spec: type: object sourceRange: description: SourceRange defines the set of allowed IPs (or ranges - of allowed IPs by using CIDR notation). + of allowed IPs by using CIDR notation). Required. items: type: string type: array diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 85ff27f7e..2b4b4aad6 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -1241,7 +1241,7 @@ spec: ipAllowList: description: |- IPAllowList holds the IP allowlist middleware configuration. - This middleware accepts / refuses requests based on the client IP. + This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipallowlist/ properties: ipStrategy: @@ -1271,7 +1271,7 @@ spec: ipWhiteList: description: |- IPWhiteList holds the IP whitelist middleware configuration. - This middleware accepts / refuses requests based on the client IP. + This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ Deprecated: please use IPAllowList instead. properties: @@ -1294,7 +1294,7 @@ spec: type: object sourceRange: description: SourceRange defines the set of allowed IPs (or ranges - of allowed IPs by using CIDR notation). + of allowed IPs by using CIDR notation). Required. items: type: string type: array @@ -3671,7 +3671,7 @@ spec: ipAllowList: description: |- IPAllowList holds the IP allowlist middleware configuration. - This middleware accepts / refuses requests based on the client IP. + This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipallowlist/ properties: ipStrategy: @@ -3701,7 +3701,7 @@ spec: ipWhiteList: description: |- IPWhiteList holds the IP whitelist middleware configuration. - This middleware accepts / refuses requests based on the client IP. + This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ Deprecated: please use IPAllowList instead. properties: @@ -3724,7 +3724,7 @@ spec: type: object sourceRange: description: SourceRange defines the set of allowed IPs (or ranges - of allowed IPs by using CIDR notation). + of allowed IPs by using CIDR notation). Required. items: type: string type: array diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index 391f22de4..c820d9fcf 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -387,11 +387,11 @@ 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. +// This middleware limits allowed requests based on the client IP. // More info: https://doc.traefik.io/traefik/v2.11/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 defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation). Required. 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"` } @@ -399,7 +399,7 @@ type IPWhiteList struct { // +k8s:deepcopy-gen=true // IPAllowList holds the IP allowlist middleware configuration. -// This middleware accepts / refuses requests based on the client IP. +// This middleware limits allowed requests based on the client IP. // More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipallowlist/ type IPAllowList struct { // SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation). diff --git a/pkg/config/dynamic/tcp_middlewares.go b/pkg/config/dynamic/tcp_middlewares.go index bf6dbdb25..e98390c64 100644 --- a/pkg/config/dynamic/tcp_middlewares.go +++ b/pkg/config/dynamic/tcp_middlewares.go @@ -24,6 +24,9 @@ type TCPInFlightConn struct { // +k8s:deepcopy-gen=true // TCPIPWhiteList holds the TCP IPWhiteList middleware configuration. +// This middleware limits allowed requests based on the client IP. +// More info: https://doc.traefik.io/traefik/v2.11/middlewares/tcp/ipwhitelist/ +// 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"` @@ -32,6 +35,8 @@ type TCPIPWhiteList struct { // +k8s:deepcopy-gen=true // TCPIPAllowList holds the TCP IPAllowList middleware configuration. +// This middleware limits allowed requests based on the client IP. +// More info: https://doc.traefik.io/traefik/v2.11/middlewares/tcp/ipallowlist/ type TCPIPAllowList 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"` From 998c6174cd4172306816ef4c743d2daa51a33de0 Mon Sep 17 00:00:00 2001 From: Martijn Cremer Date: Fri, 5 Apr 2024 10:14:03 +0200 Subject: [PATCH 02/18] Improved documentation about Nomad ACL minimum rights --- docs/content/providers/nomad.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/content/providers/nomad.md b/docs/content/providers/nomad.md index 8ff435b2a..5c565b055 100644 --- a/docs/content/providers/nomad.md +++ b/docs/content/providers/nomad.md @@ -163,6 +163,7 @@ providers: _Optional, Default=""_ Token is used to provide a per-request ACL token, if Nomad ACLs are enabled. +The appropriate ACL privilege for this token is 'read-job', as outlined in the [Nomad documentation on ACL](https://developer.hashicorp.com/nomad/tutorials/access-control/access-control-policies). ```yaml tab="File (YAML)" providers: From e5062cef420e6c8ced63687a8a02962ec1577224 Mon Sep 17 00:00:00 2001 From: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> Date: Fri, 5 Apr 2024 15:14:04 +0200 Subject: [PATCH 03/18] chore: update dependencies --- go.mod | 32 +++++++++++++-------------- go.sum | 70 ++++++++++++++++++++++++++++------------------------------ 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/go.mod b/go.mod index c9cd7a9ab..4f8f1c373 100644 --- a/go.mod +++ b/go.mod @@ -12,8 +12,8 @@ require ( github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc - github.com/docker/cli v24.0.7+incompatible - github.com/docker/docker v24.0.7+incompatible + github.com/docker/cli v24.0.9+incompatible + github.com/docker/docker v24.0.9+incompatible github.com/docker/go-connections v0.4.0 github.com/fatih/structs v1.1.0 github.com/fsnotify/fsnotify v1.7.0 @@ -51,7 +51,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_model v0.3.0 - github.com/quic-go/quic-go v0.40.1 + github.com/quic-go/quic-go v0.42.0 github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 @@ -67,11 +67,11 @@ require ( github.com/vulcand/predicate v1.2.0 go.elastic.co/apm/module/apmot/v2 v2.4.8 go.elastic.co/apm/v2 v2.4.8 - golang.org/x/mod v0.14.0 - golang.org/x/net v0.20.0 + golang.org/x/mod v0.17.0 + golang.org/x/net v0.24.0 golang.org/x/text v0.14.0 golang.org/x/time v0.5.0 - golang.org/x/tools v0.17.0 + golang.org/x/tools v0.20.0 google.golang.org/grpc v1.59.0 gopkg.in/DataDog/dd-trace-go.v1 v1.56.1 gopkg.in/yaml.v3 v3.0.1 @@ -170,7 +170,7 @@ require ( github.com/go-errors/errors v1.0.1 // indirect github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect @@ -188,7 +188,7 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect + github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f // indirect github.com/google/s2a-go v0.1.5 // indirect github.com/google/uuid v1.4.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect @@ -256,7 +256,7 @@ require ( github.com/nrdcg/porkbun v0.3.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/onsi/ginkgo v1.16.5 // indirect - github.com/onsi/ginkgo/v2 v2.9.5 // indirect + github.com/onsi/ginkgo/v2 v2.17.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/opencontainers/runc v1.1.7 // indirect @@ -272,7 +272,6 @@ require ( github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/redis/go-redis/v9 v9.2.1 // indirect github.com/sacloud/api-client-go v0.2.8 // indirect github.com/sacloud/go-http v0.1.6 // indirect @@ -310,24 +309,25 @@ require ( go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.3.0 // indirect - go.uber.org/mock v0.3.0 // indirect + go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect go.uber.org/zap v1.21.0 // indirect go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect - golang.org/x/crypto v0.19.0 // indirect - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/term v0.17.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/term v0.19.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.128.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect diff --git a/go.sum b/go.sum index cb7a4cfd6..d2a55c692 100644 --- a/go.sum +++ b/go.sum @@ -318,12 +318,12 @@ github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dnsimple/dnsimple-go v1.2.0 h1:ddTGyLVKly5HKb5L65AkLqFqwZlWo3WnR0BlFZlIddM= github.com/dnsimple/dnsimple-go v1.2.0/go.mod h1:z/cs26v/eiRvUyXsHQBLd8lWF8+cD6GbmkPH84plM4U= -github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= -github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v24.0.9+incompatible h1:OxbimnP/z+qVjDLpq9wbeFU3Nc30XhSe+LkwYQisD50= +github.com/docker/cli v24.0.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -430,8 +430,8 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -579,8 +579,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= -github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f h1:f00RU+zOX+B3rLAmMMkzHUF2h1z4DeYR9tTCvEq2REY= +github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.5 h1:8IYp3w9nysqv3JH+NJgXJzGbDHzLOTj43BmSkp+O7qg= github.com/google/s2a-go v0.1.5/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= @@ -980,8 +980,8 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= +github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -991,8 +991,8 @@ github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+t github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -1106,10 +1106,8 @@ github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= -github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= -github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q= -github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c= +github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM= +github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac h1:wBGhHdXKICZmvAPWS8gQoMyOWDH7QAi9bU4Z1nDWnFU= github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac/go.mod h1:67sLWL17mVlO1HFROaTBmU71NB4R8UNCesFHhg0f6LQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -1339,8 +1337,8 @@ go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= -go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -1389,8 +1387,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1404,8 +1402,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8= +golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1436,8 +1434,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1498,8 +1496,8 @@ golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1527,8 +1525,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1632,8 +1630,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1644,8 +1642,8 @@ golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1746,8 +1744,8 @@ golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyj golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1889,8 +1887,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/DataDog/dd-trace-go.v1 v1.56.1 h1:AUe/ZF7xm6vYnigPe+TY54DmfWYJxhMRaw/TfvrbzvE= gopkg.in/DataDog/dd-trace-go.v1 v1.56.1/go.mod h1:KDLJ3CWVOSuVVwu+0ZR5KZo2rP6c7YyBV3v387dIpUU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From cef842245c032c063a463f2baaf7c92ecc5a8070 Mon Sep 17 00:00:00 2001 From: Romain Date: Mon, 8 Apr 2024 17:16:04 +0200 Subject: [PATCH 04/18] Introduce Lingering Timeout Co-authored-by: Baptiste Mayelle Co-authored-by: Kevin Pollet --- docs/content/migration/v2.md | 32 ++++++ .../reference/static-configuration/cli-ref.md | 18 +++- .../reference/static-configuration/env-ref.md | 18 +++- .../reference/static-configuration/file.toml | 6 ++ .../reference/static-configuration/file.yaml | 6 ++ docs/content/routing/entrypoints.md | 81 +++++++++++---- pkg/api/handler_entrypoint_test.go | 22 +++-- pkg/api/testdata/entrypoints.json | 16 +-- pkg/config/static/static_config.go | 99 ++++++++++++++++++- pkg/redactor/redactor_config_test.go | 13 ++- .../testdata/anonymized-static-config.json | 11 ++- pkg/server/router/tcp/router.go | 12 --- pkg/server/server_entrypoint_tcp.go | 82 +++++++++++---- pkg/server/server_entrypoint_tcp_test.go | 88 ++++++++++++++++- 14 files changed, 418 insertions(+), 86 deletions(-) diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 0f3114fc3..0a80cf43d 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -577,3 +577,35 @@ the maximum user-defined router priority value is: - `(MaxInt32 - 1000)` for 32-bit platforms, - `(MaxInt64 - 1000)` for 64-bit platforms. + +### .Transport.RespondingTimeouts. + +Starting with `v2.11.1` the following timeout options are deprecated: + +- `.transport.respondingTimeouts.readTimeout` +- `.transport.respondingTimeouts.writeTimeout` +- `.transport.respondingTimeouts.idleTimeout` + +They have been replaced by: + +- `.transport.respondingTimeouts.http.readTimeout` +- `.transport.respondingTimeouts.http.writeTimeout` +- `.transport.respondingTimeouts.http.idleTimeout` + +### .Transport.RespondingTimeouts.TCP.LingeringTimeout + +Starting with `v2.11.1` a new `lingeringTimeout` entryPoints option has been introduced, with a default value of 2s. + +The lingering timeout defines the maximum duration between each TCP read operation on the connection. +As a layer 4 timeout, it applies during HTTP handling but respects the configured HTTP server `readTimeout`. + +This change avoids Traefik instances with the default configuration hanging while waiting for bytes to be read on the connection. + +We suggest to adapt this value accordingly to your situation. +The new default value is purposely narrowed and can close the connection too early. + +Increasing the `lingeringTimeout` value could be the solution notably if you are dealing with the following errors: + +- TCP: `Error while handling TCP connection: readfrom tcp X.X.X.X:X->X.X.X.X:X: read tcp X.X.X.X:X->X.X.X.X:X: i/o timeout` +- HTTP: `'499 Client Closed Request' caused by: context canceled` +- HTTP: `ReverseProxy read error during body copy: read tcp X.X.X.X:X->X.X.X.X:X: use of closed network connection` diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 60a8c6513..e3ae10e2a 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -183,15 +183,27 @@ Duration to give active requests a chance to finish before Traefik stops. (Defau `--entrypoints..transport.lifecycle.requestacceptgracetimeout`: Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure. (Default: ```0```) -`--entrypoints..transport.respondingtimeouts.idletimeout`: +`--entrypoints..transport.respondingtimeouts.http.idletimeout`: IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```180```) -`--entrypoints..transport.respondingtimeouts.readtimeout`: +`--entrypoints..transport.respondingtimeouts.http.readtimeout`: ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```0```) -`--entrypoints..transport.respondingtimeouts.writetimeout`: +`--entrypoints..transport.respondingtimeouts.http.writetimeout`: WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```) +`--entrypoints..transport.respondingtimeouts.idletimeout`: +(Deprecated) IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```0```) + +`--entrypoints..transport.respondingtimeouts.readtimeout`: +(Deprecated) ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```0```) + +`--entrypoints..transport.respondingtimeouts.tcp.lingeringtimeout`: +LingeringTimeout is the maximum duration between each TCP read operation on the connection. If zero, no timeout is set. (Default: ```2```) + +`--entrypoints..transport.respondingtimeouts.writetimeout`: +(Deprecated) WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```) + `--entrypoints..udp.timeout`: Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 849601f63..9ae961694 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -183,15 +183,27 @@ Duration to give active requests a chance to finish before Traefik stops. (Defau `TRAEFIK_ENTRYPOINTS__TRANSPORT_LIFECYCLE_REQUESTACCEPTGRACETIMEOUT`: Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure. (Default: ```0```) -`TRAEFIK_ENTRYPOINTS__TRANSPORT_RESPONDINGTIMEOUTS_IDLETIMEOUT`: +`TRAEFIK_ENTRYPOINTS__TRANSPORT_RESPONDINGTIMEOUTS_HTTP_IDLETIMEOUT`: IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```180```) -`TRAEFIK_ENTRYPOINTS__TRANSPORT_RESPONDINGTIMEOUTS_READTIMEOUT`: +`TRAEFIK_ENTRYPOINTS__TRANSPORT_RESPONDINGTIMEOUTS_HTTP_READTIMEOUT`: ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```0```) -`TRAEFIK_ENTRYPOINTS__TRANSPORT_RESPONDINGTIMEOUTS_WRITETIMEOUT`: +`TRAEFIK_ENTRYPOINTS__TRANSPORT_RESPONDINGTIMEOUTS_HTTP_WRITETIMEOUT`: WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```) +`TRAEFIK_ENTRYPOINTS__TRANSPORT_RESPONDINGTIMEOUTS_IDLETIMEOUT`: +(Deprecated) IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```0```) + +`TRAEFIK_ENTRYPOINTS__TRANSPORT_RESPONDINGTIMEOUTS_READTIMEOUT`: +(Deprecated) ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```0```) + +`TRAEFIK_ENTRYPOINTS__TRANSPORT_RESPONDINGTIMEOUTS_TCP_LINGERINGTIMEOUT`: +LingeringTimeout is the maximum duration between each TCP read operation on the connection. If zero, no timeout is set. (Default: ```2```) + +`TRAEFIK_ENTRYPOINTS__TRANSPORT_RESPONDINGTIMEOUTS_WRITETIMEOUT`: +(Deprecated) WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```) + `TRAEFIK_ENTRYPOINTS__UDP_TIMEOUT`: Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 42c4e6fdf..bb402467c 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -26,6 +26,12 @@ readTimeout = "42s" writeTimeout = "42s" idleTimeout = "42s" + [entryPoints.EntryPoint0.transport.respondingTimeouts.http] + readTimeout = "42s" + writeTimeout = "42s" + idleTimeout = "42s" + [entryPoints.EntryPoint0.transport.respondingTimeouts.tcp] + lingeringTimeout = "42s" [entryPoints.EntryPoint0.proxyProtocol] insecure = true trustedIPs = ["foobar", "foobar"] diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index abb8e05d8..d5265aa1d 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -24,6 +24,12 @@ entryPoints: readTimeout: 42s writeTimeout: 42s idleTimeout: 42s + http: + readTimeout: 42s + writeTimeout: 42s + idleTimeout: 42s + tcp: + lingeringTimeout: 42s keepAliveMaxTime: 42s keepAliveMaxRequests: 42 proxyProtocol: diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index 70ce11cf5..c6a79ba8e 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -397,10 +397,11 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward #### `respondingTimeouts` -`respondingTimeouts` are timeouts for incoming requests to the Traefik instance. -Setting them has no effect for UDP entryPoints. +##### `http` -??? info "`transport.respondingTimeouts.readTimeout`" +`respondingTimeouts.http` are timeouts for incoming requests to the Traefik instance. + +??? info "`transport.respondingTimeouts.http.readTimeout`" _Optional, Default=0s_ @@ -417,7 +418,8 @@ Setting them has no effect for UDP entryPoints. address: ":8888" transport: respondingTimeouts: - readTimeout: 42 + http: + readTimeout: 42 ``` ```toml tab="File (TOML)" @@ -425,18 +427,17 @@ Setting them has no effect for UDP entryPoints. [entryPoints] [entryPoints.name] address = ":8888" - [entryPoints.name.transport] - [entryPoints.name.transport.respondingTimeouts] - readTimeout = 42 + [entryPoints.name.transport.respondingTimeouts.http] + readTimeout = 42 ``` ```bash tab="CLI" ## Static configuration --entryPoints.name.address=:8888 - --entryPoints.name.transport.respondingTimeouts.readTimeout=42 + --entryPoints.name.transport.respondingTimeouts.http.readTimeout=42 ``` -??? info "`transport.respondingTimeouts.writeTimeout`" +??? info "`transport.respondingTimeouts.http.writeTimeout`" _Optional, Default=0s_ @@ -454,7 +455,8 @@ Setting them has no effect for UDP entryPoints. address: ":8888" transport: respondingTimeouts: - writeTimeout: 42 + http: + writeTimeout: 42 ``` ```toml tab="File (TOML)" @@ -462,18 +464,17 @@ Setting them has no effect for UDP entryPoints. [entryPoints] [entryPoints.name] address = ":8888" - [entryPoints.name.transport] - [entryPoints.name.transport.respondingTimeouts] - writeTimeout = 42 + [entryPoints.name.transport.respondingTimeouts.http] + writeTimeout = 42 ``` ```bash tab="CLI" ## Static configuration --entryPoints.name.address=:8888 - --entryPoints.name.transport.respondingTimeouts.writeTimeout=42 + --entryPoints.name.transport.respondingTimeouts.http.writeTimeout=42 ``` -??? info "`transport.respondingTimeouts.idleTimeout`" +??? info "`transport.respondingTimeouts.http.idleTimeout`" _Optional, Default=180s_ @@ -490,7 +491,8 @@ Setting them has no effect for UDP entryPoints. address: ":8888" transport: respondingTimeouts: - idleTimeout: 42 + http: + idleTimeout: 42 ``` ```toml tab="File (TOML)" @@ -498,15 +500,54 @@ Setting them has no effect for UDP entryPoints. [entryPoints] [entryPoints.name] address = ":8888" - [entryPoints.name.transport] - [entryPoints.name.transport.respondingTimeouts] - idleTimeout = 42 + [entryPoints.name.transport.respondingTimeouts.http] + idleTimeout = 42 ``` ```bash tab="CLI" ## Static configuration --entryPoints.name.address=:8888 - --entryPoints.name.transport.respondingTimeouts.idleTimeout=42 + --entryPoints.name.transport.respondingTimeouts.http.idleTimeout=42 + +##### `tcp` + +`respondingTimeouts.tcp` are timeouts for client connections to the Traefik instance. + +??? info "`transport.respondingTimeouts.tcp.lingeringTimeout`" + + _Optional, Default=2s_ + + `lingeringTimeout` is the maximum duration between each TCP read operation on the connection. + As a layer 4 timeout, it also applies during HTTP handling, but respect the configured HTTP server `readTimeout`. + + If zero, the lingering is disabled. + Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits). + If no units are provided, the value is parsed assuming seconds. + + ```yaml tab="File (YAML)" + ## Static configuration + entryPoints: + name: + address: ":8888" + transport: + respondingTimeouts: + tcp: + lingeringTimeout: 42 + ``` + + ```toml tab="File (TOML)" + ## Static configuration + [entryPoints] + [entryPoints.name] + address = ":8888" + [entryPoints.name.transport.respondingTimeouts.tcp] + lingeringTimeout = 42 + ``` + + ```bash tab="CLI" + ## Static configuration + --entryPoints.name.address=:8888 + --entryPoints.name.transport.respondingTimeouts.tcp.lingeringTimeout=42 ``` #### `lifeCycle` diff --git a/pkg/api/handler_entrypoint_test.go b/pkg/api/handler_entrypoint_test.go index d5d6a5d1f..79b92a65f 100644 --- a/pkg/api/handler_entrypoint_test.go +++ b/pkg/api/handler_entrypoint_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v2/pkg/config/runtime" "github.com/traefik/traefik/v2/pkg/config/static" ) @@ -55,9 +56,11 @@ func TestHandler_EntryPoints(t *testing.T) { GraceTimeOut: 2, }, RespondingTimeouts: &static.RespondingTimeouts{ - ReadTimeout: 3, - WriteTimeout: 4, - IdleTimeout: 5, + HTTP: &static.HTTPRespondingTimeouts{ + ReadTimeout: paerserDurationPtr(3), + WriteTimeout: paerserDurationPtr(4), + IdleTimeout: paerserDurationPtr(5), + }, }, }, ProxyProtocol: &static.ProxyProtocol{ @@ -77,9 +80,11 @@ func TestHandler_EntryPoints(t *testing.T) { GraceTimeOut: 20, }, RespondingTimeouts: &static.RespondingTimeouts{ - ReadTimeout: 30, - WriteTimeout: 40, - IdleTimeout: 50, + HTTP: &static.HTTPRespondingTimeouts{ + ReadTimeout: paerserDurationPtr(3), + WriteTimeout: paerserDurationPtr(4), + IdleTimeout: paerserDurationPtr(5), + }, }, }, ProxyProtocol: &static.ProxyProtocol{ @@ -263,3 +268,8 @@ func generateEntryPoints(nb int) map[string]*static.EntryPoint { return eps } + +func paerserDurationPtr(duration int) *ptypes.Duration { + d := ptypes.Duration(duration) + return &d +} diff --git a/pkg/api/testdata/entrypoints.json b/pkg/api/testdata/entrypoints.json index d93d07bfc..93f56a401 100644 --- a/pkg/api/testdata/entrypoints.json +++ b/pkg/api/testdata/entrypoints.json @@ -23,9 +23,11 @@ "requestAcceptGraceTimeout": "1ns" }, "respondingTimeouts": { - "idleTimeout": "5ns", - "readTimeout": "3ns", - "writeTimeout": "4ns" + "http": { + "idleTimeout": "5ns", + "readTimeout": "3ns", + "writeTimeout": "4ns" + } } } }, @@ -53,9 +55,11 @@ "requestAcceptGraceTimeout": "10ns" }, "respondingTimeouts": { - "idleTimeout": "50ns", - "readTimeout": "30ns", - "writeTimeout": "40ns" + "http": { + "idleTimeout": "5ns", + "readTimeout": "3ns", + "writeTimeout": "4ns" + } } } } diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index dcf9b3dba..2a47763a0 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -56,6 +56,9 @@ const ( // DefaultUDPTimeout defines how long to wait by default on an idle session, // before releasing all resources related to that session. DefaultUDPTimeout = 3 * time.Second + + // defaultLingeringTimeout defines the default maximum duration between each read operation on the connection. + defaultLingeringTimeout = 2 * time.Second ) // Configuration is the static configuration. @@ -118,16 +121,44 @@ func (a *API) SetDefaults() { a.Dashboard = true } -// RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance. +// RespondingTimeouts contains timeout configurations. type RespondingTimeouts struct { - ReadTimeout ptypes.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"` - WriteTimeout ptypes.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"` - IdleTimeout ptypes.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"` + // Deprecated: please use `respondingTimeouts.http.readTimeout` instead. + ReadTimeout *ptypes.Duration `description:"(Deprecated) ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"` + // Deprecated: please use `respondingTimeouts.http.writeTimeout` instead. + WriteTimeout *ptypes.Duration `description:"(Deprecated) WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"` + // Deprecated: please use `respondingTimeouts.http.idleTimeout` instead. + IdleTimeout *ptypes.Duration `description:"(Deprecated) IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"` + + HTTP *HTTPRespondingTimeouts `description:"Defines the HTTP responding timeouts." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"` + TCP *TCPRespondingTimeouts `description:"Defines the TCP responding timeouts." json:"tcp,omitempty" toml:"tcp,omitempty" yaml:"tcp,omitempty" export:"true"` +} + +// HTTPRespondingTimeouts contains HTTP timeout configurations for incoming requests to the Traefik instance. +type HTTPRespondingTimeouts struct { + ReadTimeout *ptypes.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"` + WriteTimeout *ptypes.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"` + IdleTimeout *ptypes.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"` +} + +// TCPRespondingTimeouts contains TCP timeout configurations for client connections to the Traefik instance. +type TCPRespondingTimeouts struct { + LingeringTimeout ptypes.Duration `description:"LingeringTimeout is the maximum duration between each TCP read operation on the connection. If zero, no timeout is set." json:"lingeringTimeout,omitempty" toml:"lingeringTimeout,omitempty" yaml:"lingeringTimeout,omitempty" export:"true"` } // SetDefaults sets the default values. func (a *RespondingTimeouts) SetDefaults() { - a.IdleTimeout = ptypes.Duration(DefaultIdleTimeout) + noTimeout := ptypes.Duration(0) + defaultIdleTimeout := ptypes.Duration(DefaultIdleTimeout) + a.HTTP = &HTTPRespondingTimeouts{ + ReadTimeout: &noTimeout, + WriteTimeout: &noTimeout, + IdleTimeout: &defaultIdleTimeout, + } + + a.TCP = &TCPRespondingTimeouts{ + LingeringTimeout: ptypes.Duration(defaultLingeringTimeout), + } } // ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers. @@ -211,6 +242,39 @@ func (c *Configuration) SetEffectiveConfiguration() { c.EntryPoints["http"] = ep } + for _, entrypoint := range c.EntryPoints { + if entrypoint.Transport == nil || + entrypoint.Transport.RespondingTimeouts == nil { + continue + } + + respondingTimeouts := entrypoint.Transport.RespondingTimeouts + + if respondingTimeouts.ReadTimeout != nil && + respondingTimeouts.HTTP != nil && + respondingTimeouts.HTTP.ReadTimeout == nil { + log.WithoutContext().Warnf("Option `respondingTimeouts.readTimeout` is deprecated, please use `respondingTimeouts.http.readTimeout` instead.") + respondingTimeouts.HTTP.ReadTimeout = respondingTimeouts.ReadTimeout + respondingTimeouts.ReadTimeout = nil + } + + if respondingTimeouts.WriteTimeout != nil && + respondingTimeouts.HTTP != nil && + respondingTimeouts.HTTP.WriteTimeout == nil { + log.WithoutContext().Warnf("Option `respondingTimeouts.writeTimeout` is deprecated, please use `respondingTimeouts.http.writeTimeout` instead.") + respondingTimeouts.HTTP.WriteTimeout = respondingTimeouts.WriteTimeout + respondingTimeouts.WriteTimeout = nil + } + + if respondingTimeouts.IdleTimeout != nil && + respondingTimeouts.HTTP != nil && + respondingTimeouts.HTTP.IdleTimeout == nil { + log.WithoutContext().Warnf("Option `respondingTimeouts.idleTimeout` is deprecated, please use `respondingTimeouts.http.idleTimeout` instead.") + respondingTimeouts.HTTP.IdleTimeout = respondingTimeouts.IdleTimeout + respondingTimeouts.IdleTimeout = nil + } + } + // Creates the internal traefik entry point if needed if (c.API != nil && c.API.Insecure) || (c.Ping != nil && !c.Ping.ManualRouting && c.Ping.EntryPoint == DefaultInternalEntryPointName) || @@ -316,6 +380,31 @@ func (c *Configuration) ValidateConfiguration() error { return errors.New("Nomad provider cannot have both namespace and namespaces options configured") } + for epName, entrypoint := range c.EntryPoints { + if entrypoint.Transport == nil || + entrypoint.Transport.RespondingTimeouts == nil || + entrypoint.Transport.RespondingTimeouts.HTTP == nil { + continue + } + + respondingTimeouts := entrypoint.Transport.RespondingTimeouts + + if respondingTimeouts.ReadTimeout != nil && + respondingTimeouts.HTTP.ReadTimeout != nil { + return fmt.Errorf("entrypoint %q has `readTimeout` option is defined multiple times (`respondingTimeouts.readTimeout` is deprecated)", epName) + } + + if respondingTimeouts.WriteTimeout != nil && + respondingTimeouts.HTTP.WriteTimeout != nil { + return fmt.Errorf("entrypoint %q has `writeTimeout` option is defined multiple times (`respondingTimeouts.writeTimeout` is deprecated)", epName) + } + + if respondingTimeouts.IdleTimeout != nil && + respondingTimeouts.HTTP.IdleTimeout != nil { + return fmt.Errorf("entrypoint %q has `idleTimeout` option is defined multiple times (`respondingTimeouts.idleTimeout` is deprecated)", epName) + } + } + return nil } diff --git a/pkg/redactor/redactor_config_test.go b/pkg/redactor/redactor_config_test.go index 368f2d956..4cc080683 100644 --- a/pkg/redactor/redactor_config_test.go +++ b/pkg/redactor/redactor_config_test.go @@ -520,6 +520,8 @@ func TestDo_staticConfiguration(t *testing.T) { }, } + paerserDuration := ptypes.Duration(111 * time.Second) + config.EntryPoints = static.EntryPoints{ "foobar": { Address: "foo Address", @@ -529,9 +531,14 @@ func TestDo_staticConfiguration(t *testing.T) { GraceTimeOut: ptypes.Duration(111 * time.Second), }, RespondingTimeouts: &static.RespondingTimeouts{ - ReadTimeout: ptypes.Duration(111 * time.Second), - WriteTimeout: ptypes.Duration(111 * time.Second), - IdleTimeout: ptypes.Duration(111 * time.Second), + HTTP: &static.HTTPRespondingTimeouts{ + ReadTimeout: &paerserDuration, + WriteTimeout: &paerserDuration, + IdleTimeout: &paerserDuration, + }, + TCP: &static.TCPRespondingTimeouts{ + LingeringTimeout: ptypes.Duration(111 * time.Second), + }, }, }, ProxyProtocol: &static.ProxyProtocol{ diff --git a/pkg/redactor/testdata/anonymized-static-config.json b/pkg/redactor/testdata/anonymized-static-config.json index 09e0d796d..dd25c1fa3 100644 --- a/pkg/redactor/testdata/anonymized-static-config.json +++ b/pkg/redactor/testdata/anonymized-static-config.json @@ -26,9 +26,14 @@ "graceTimeOut": "1m51s" }, "respondingTimeouts": { - "readTimeout": "1m51s", - "writeTimeout": "1m51s", - "idleTimeout": "1m51s" + "http": { + "readTimeout": "1m51s", + "writeTimeout": "1m51s", + "idleTimeout": "1m51s" + }, + "tcp": { + "lingeringTimeout": "1m51s" + } } }, "proxyProtocol": { diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index 0d8524398..551bb43e7 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -9,7 +9,6 @@ import ( "net" "net/http" "slices" - "time" "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/traefik/traefik/v2/pkg/log" @@ -117,17 +116,6 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) { return } - // Remove read/write deadline and delegate this to underlying tcp server (for now only handled by HTTP Server) - err = conn.SetReadDeadline(time.Time{}) - if err != nil { - log.WithoutContext().Errorf("Error while setting read deadline: %v", err) - } - - err = conn.SetWriteDeadline(time.Time{}) - if err != nil { - log.WithoutContext().Errorf("Error while setting write deadline: %v", err) - } - connData, err := tcpmuxer.NewConnData(hello.serverName, conn, hello.protos) if err != nil { log.WithoutContext().Errorf("Error while reading TCP connection data: %v", err) diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 1fb371d08..b5aaa1ac0 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -244,24 +244,15 @@ func (e *TCPEntryPoint) Start(ctx context.Context) { panic(err) } + if e.transportConfiguration != nil && + e.transportConfiguration.RespondingTimeouts != nil && + e.transportConfiguration.RespondingTimeouts.TCP != nil && + e.transportConfiguration.RespondingTimeouts.TCP.LingeringTimeout > 0 { + lingeringTimeout := time.Duration(e.transportConfiguration.RespondingTimeouts.TCP.LingeringTimeout) + writeCloser = newLingeringConnection(writeCloser, lingeringTimeout) + } + safe.Go(func() { - // Enforce read/write deadlines at the connection level, - // because when we're peeking the first byte to determine whether we are doing TLS, - // the deadlines at the server level are not taken into account. - if e.transportConfiguration.RespondingTimeouts.ReadTimeout > 0 { - err := writeCloser.SetReadDeadline(time.Now().Add(time.Duration(e.transportConfiguration.RespondingTimeouts.ReadTimeout))) - if err != nil { - logger.Errorf("Error while setting read deadline: %v", err) - } - } - - if e.transportConfiguration.RespondingTimeouts.WriteTimeout > 0 { - err = writeCloser.SetWriteDeadline(time.Now().Add(time.Duration(e.transportConfiguration.RespondingTimeouts.WriteTimeout))) - if err != nil { - logger.Errorf("Error while setting write deadline: %v", err) - } - } - e.switcher.ServeTCP(newTrackedConnection(writeCloser, e.tracker)) }) } @@ -391,6 +382,55 @@ func writeCloser(conn net.Conn) (tcp.WriteCloser, error) { } } +// lingeringConn represents a writeCloser with lingeringTimeout handling. +type lingeringConn struct { + tcp.WriteCloser + + lingeringTimeout time.Duration + + rdlMu sync.RWMutex + // readDeadline is the current readDeadline set by an upper caller. + // In case of HTTP, the HTTP go server manipulates deadlines on the connection. + readDeadline time.Time +} + +// newLingeringConnection returns the given writeCloser augmented with lingeringTimeout handling. +func newLingeringConnection(conn tcp.WriteCloser, timeout time.Duration) tcp.WriteCloser { + return &lingeringConn{ + WriteCloser: conn, + lingeringTimeout: timeout, + } +} + +// Read reads data from the connection and postpones the connection readDeadline according to the lingeringTimeout config. +// It also ensures that the upper level set readDeadline is enforced. +func (l *lingeringConn) Read(b []byte) (int, error) { + if l.lingeringTimeout > 0 { + deadline := time.Now().Add(l.lingeringTimeout) + + l.rdlMu.RLock() + if !l.readDeadline.IsZero() && deadline.After(l.readDeadline) { + deadline = l.readDeadline + } + l.rdlMu.RUnlock() + + if err := l.WriteCloser.SetReadDeadline(deadline); err != nil { + return 0, err + } + } + + return l.WriteCloser.Read(b) +} + +// SetReadDeadline sets and save the read deadline. +func (l *lingeringConn) SetReadDeadline(t time.Time) error { + l.rdlMu.Lock() + l.readDeadline = t + l.rdlMu.Unlock() + + return l.WriteCloser.SetReadDeadline(t) +} + // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted // connections. type tcpKeepAliveListener struct { @@ -419,7 +459,7 @@ func (ln tcpKeepAliveListener) Accept() (net.Conn, error) { } func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoint, listener net.Listener) (net.Listener, error) { - timeout := entryPoint.Transport.RespondingTimeouts.ReadTimeout + timeout := *entryPoint.Transport.RespondingTimeouts.HTTP.ReadTimeout // proxyproto use 200ms if ReadHeaderTimeout is set to 0 and not no timeout if timeout == 0 { timeout = -1 @@ -588,9 +628,9 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati serverHTTP := &http.Server{ Handler: handler, ErrorLog: httpServerLogger, - ReadTimeout: time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout), - WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout), - IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), + ReadTimeout: time.Duration(*configuration.Transport.RespondingTimeouts.HTTP.ReadTimeout), + WriteTimeout: time.Duration(*configuration.Transport.RespondingTimeouts.HTTP.WriteTimeout), + IdleTimeout: time.Duration(*configuration.Transport.RespondingTimeouts.HTTP.IdleTimeout), } if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) { serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context { diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index f0b12c8dd..6b177099f 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -69,8 +69,10 @@ func testShutdown(t *testing.T, router *tcprouter.Router) { epConfig.LifeCycle.RequestAcceptGraceTimeout = 0 epConfig.LifeCycle.GraceTimeOut = ptypes.Duration(5 * time.Second) - epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(5 * time.Second) - epConfig.RespondingTimeouts.WriteTimeout = ptypes.Duration(5 * time.Second) + readTimeout := ptypes.Duration(5 * time.Second) + epConfig.RespondingTimeouts.HTTP.ReadTimeout = &readTimeout + writeTimeout := ptypes.Duration(5 * time.Second) + epConfig.RespondingTimeouts.HTTP.WriteTimeout = &writeTimeout entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ // We explicitly use an IPV4 address because on Alpine, with an IPV6 address @@ -157,7 +159,8 @@ func startEntrypoint(entryPoint *TCPEntryPoint, router *tcprouter.Router) (net.C func TestReadTimeoutWithoutFirstByte(t *testing.T) { epConfig := &static.EntryPointsTransport{} epConfig.SetDefaults() - epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second) + readTimeout := ptypes.Duration(2 * time.Second) + epConfig.RespondingTimeouts.HTTP.ReadTimeout = &readTimeout entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ Address: ":0", @@ -194,7 +197,84 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) { func TestReadTimeoutWithFirstByte(t *testing.T) { epConfig := &static.EntryPointsTransport{} epConfig.SetDefaults() - epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second) + readTimeout := ptypes.Duration(2 * time.Second) + epConfig.RespondingTimeouts.HTTP.ReadTimeout = &readTimeout + + entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + Address: ":0", + Transport: epConfig, + ForwardedHeaders: &static.ForwardedHeaders{}, + HTTP2: &static.HTTP2Config{}, + }, nil) + require.NoError(t, err) + + router := &tcprouter.Router{} + router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + + conn, err := startEntrypoint(entryPoint, router) + require.NoError(t, err) + + _, err = conn.Write([]byte("GET /some HTTP/1.1\r\n")) + require.NoError(t, err) + + errChan := make(chan error) + + go func() { + b := make([]byte, 2048) + _, err := conn.Read(b) + errChan <- err + }() + + select { + case err := <-errChan: + require.Equal(t, io.EOF, err) + case <-time.Tick(5 * time.Second): + t.Error("Timeout while read") + } +} + +func TestLingeringTimeoutWithoutFirstByte(t *testing.T) { + epConfig := &static.EntryPointsTransport{} + epConfig.SetDefaults() + + entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + Address: ":0", + Transport: epConfig, + ForwardedHeaders: &static.ForwardedHeaders{}, + HTTP2: &static.HTTP2Config{}, + }, nil) + require.NoError(t, err) + + router := &tcprouter.Router{} + router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + + conn, err := startEntrypoint(entryPoint, router) + require.NoError(t, err) + + errChan := make(chan error) + + go func() { + b := make([]byte, 2048) + _, err := conn.Read(b) + errChan <- err + }() + + select { + case err := <-errChan: + require.Equal(t, io.EOF, err) + case <-time.Tick(5 * time.Second): + t.Error("Timeout while read") + } +} + +func TestLingeringTimeoutWithFirstByte(t *testing.T) { + epConfig := &static.EntryPointsTransport{} + epConfig.SetDefaults() + epConfig.RespondingTimeouts.TCP.LingeringTimeout = ptypes.Duration(time.Second) entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ Address: ":0", From 76723b12883097568ff6a1e34a857195999023fc Mon Sep 17 00:00:00 2001 From: guangwu Date: Tue, 9 Apr 2024 19:12:04 +0800 Subject: [PATCH 05/18] Close created file in ACME local store CheckFile func --- pkg/provider/acme/local_store_unix.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/provider/acme/local_store_unix.go b/pkg/provider/acme/local_store_unix.go index f6d590536..163ecd7ae 100644 --- a/pkg/provider/acme/local_store_unix.go +++ b/pkg/provider/acme/local_store_unix.go @@ -11,14 +11,15 @@ import ( // CheckFile checks file permissions and content size. func CheckFile(name string) (bool, error) { f, err := os.Open(name) - if err != nil { - if os.IsNotExist(err) { - f, err = os.Create(name) - if err != nil { - return false, err - } - return false, f.Chmod(0o600) + if err != nil && os.IsNotExist(err) { + nf, err := os.Create(name) + if err != nil { + return false, err } + defer nf.Close() + return false, nf.Chmod(0o600) + } + if err != nil { return false, err } defer f.Close() From 0017471f0d643ecc53768839c24658183fa07e54 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Wed, 10 Apr 2024 09:34:07 +0200 Subject: [PATCH 06/18] Add option to set Gateway status address Co-authored-by: Romain --- docs/content/providers/kubernetes-gateway.md | 79 ++++++++++ .../reference/static-configuration/cli-ref.md | 15 ++ .../reference/static-configuration/env-ref.md | 15 ++ .../reference/static-configuration/file.toml | 6 + .../reference/static-configuration/file.yaml | 6 + .../kubernetes/gateway/fixtures/services.yml | 13 ++ pkg/provider/kubernetes/gateway/kubernetes.go | 81 +++++++++- .../kubernetes/gateway/kubernetes_test.go | 149 +++++++++++++++--- 8 files changed, 333 insertions(+), 31 deletions(-) diff --git a/docs/content/providers/kubernetes-gateway.md b/docs/content/providers/kubernetes-gateway.md index fe49dbe75..c01159904 100644 --- a/docs/content/providers/kubernetes-gateway.md +++ b/docs/content/providers/kubernetes-gateway.md @@ -212,6 +212,85 @@ providers: --providers.kubernetesgateway.namespaces=default,production ``` +### `statusAddress` + +#### `ip` + +_Optional, Default: ""_ + +This IP will get copied to the Gateway `status.addresses`, and currently only supports one IP value (IPv4 or IPv6). + +```yaml tab="File (YAML)" +providers: + kubernetesGateway: + statusAddress: + ip: "1.2.3.4" + # ... +``` + +```toml tab="File (TOML)" +[providers.kubernetesGateway.statusAddress] + ip = "1.2.3.4" + # ... +``` + +```bash tab="CLI" +--providers.kubernetesgateway.statusaddress.ip=1.2.3.4 +``` + +#### `hostname` + +_Optional, Default: ""_ + +This Hostname will get copied to the Gateway `status.addresses`. + +```yaml tab="File (YAML)" +providers: + kubernetesGateway: + statusAddress: + hostname: "example.net" + # ... +``` + +```toml tab="File (TOML)" +[providers.kubernetesGateway.statusAddress] + hostname = "example.net" + # ... +``` + +```bash tab="CLI" +--providers.kubernetesgateway.statusaddress.hostname=example.net +``` + +#### `service` + +_Optional_ + +The Kubernetes service to copy status addresses from. +When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the gateways. + +```yaml tab="File (YAML)" +providers: + kubernetesGateway: + statusAddress: + service: + namespace: default + name: foo + # ... +``` + +```toml tab="File (TOML)" +[providers.kubernetesGateway.statusAddress.service] + namespace = "default" + name = "foo" + # ... +``` + +```bash tab="CLI" +--providers.kubernetesgateway.statusaddress.service.namespace=default +--providers.kubernetesgateway.statusaddress.service.name=foo +``` + ### `experimentalChannel` _Optional, Default: false_ diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 6194dfbfe..6d49edb59 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -738,6 +738,21 @@ Kubernetes label selector to select specific GatewayClasses. `--providers.kubernetesgateway.namespaces`: Kubernetes namespaces. +`--providers.kubernetesgateway.statusaddress.hostname`: +Hostname used for Kubernetes Gateway status address. + +`--providers.kubernetesgateway.statusaddress.ip`: +IP used to set Kubernetes Gateway status address. + +`--providers.kubernetesgateway.statusaddress.service`: +Published Kubernetes Service to copy status addresses from. + +`--providers.kubernetesgateway.statusaddress.service.name`: +Name of the Kubernetes service. + +`--providers.kubernetesgateway.statusaddress.service.namespace`: +Namespace of the Kubernetes service. + `--providers.kubernetesgateway.throttleduration`: Kubernetes refresh throttle duration (Default: ```0```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 7fcde07fc..cac42b39b 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -738,6 +738,21 @@ Kubernetes label selector to select specific GatewayClasses. `TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_NAMESPACES`: Kubernetes namespaces. +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_STATUSADDRESS_HOSTNAME`: +Hostname used for Kubernetes Gateway status address. + +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_STATUSADDRESS_IP`: +IP used to set Kubernetes Gateway status address. + +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_STATUSADDRESS_SERVICE`: +Published Kubernetes Service to copy status addresses from. + +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_STATUSADDRESS_SERVICE_NAME`: +Name of the Kubernetes service. + +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_STATUSADDRESS_SERVICE_NAMESPACE`: +Namespace of the Kubernetes service. + `TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_THROTTLEDURATION`: Kubernetes refresh throttle duration (Default: ```0```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index e5a6e379e..9d796c4a6 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -147,6 +147,12 @@ labelSelector = "foobar" throttleDuration = "42s" experimentalChannel = true + [providers.kubernetesGateway.statusAddress] + ip = "foobar" + hostname = "foobar" + [providers.kubernetesGateway.statusAddress.service] + name = "foobar" + namespace = "foobar" [providers.rest] insecure = true [providers.consulCatalog] diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 362418fbc..477fa6b0e 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -164,6 +164,12 @@ providers: labelSelector: foobar throttleDuration: 42s experimentalChannel: true + statusAddress: + ip: foobar + hostname: foobar + service: + name: foobar + namespace: foobar rest: insecure: true consulCatalog: diff --git a/pkg/provider/kubernetes/gateway/fixtures/services.yml b/pkg/provider/kubernetes/gateway/fixtures/services.yml index 2dee27a3e..d9ebf1e6b 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/services.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/services.yml @@ -269,3 +269,16 @@ spec: - protocol: TCP port: 10000 name: tcp-2 + +--- +apiVersion: v1 +kind: Service +metadata: + name: status-address + namespace: default + +status: + loadBalancer: + ingress: + - hostname: foo.bar + - ip: 1.2.3.4 diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index 4c355c3a5..590f840b4 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -58,6 +58,7 @@ type Provider struct { LabelSelector string `description:"Kubernetes label selector to select specific GatewayClasses." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` ThrottleDuration ptypes.Duration `description:"Kubernetes refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` ExperimentalChannel bool `description:"Toggles Experimental Channel resources support (TCPRoute, TLSRoute...)." json:"experimentalChannel,omitempty" toml:"experimentalChannel,omitempty" yaml:"experimentalChannel,omitempty" export:"true"` + StatusAddress *StatusAddress `description:"Defines the Kubernetes Gateway status address." json:"statusAddress,omitempty" toml:"statusAddress,omitempty" yaml:"statusAddress,omitempty" export:"true"` EntryPoints map[string]Entrypoint `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` @@ -71,6 +72,19 @@ type Provider struct { routerTransform k8s.RouterTransform } +// StatusAddress holds the Gateway Status address configuration. +type StatusAddress struct { + IP string `description:"IP used to set Kubernetes Gateway status address." json:"ip,omitempty" toml:"ip,omitempty" yaml:"ip,omitempty"` + Hostname string `description:"Hostname used for Kubernetes Gateway status address." json:"hostname,omitempty" toml:"hostname,omitempty" yaml:"hostname,omitempty"` + Service ServiceRef `description:"Published Kubernetes Service to copy status addresses from." json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty"` +} + +// ServiceRef holds a Kubernetes service reference. +type ServiceRef struct { + Name string `description:"Name of the Kubernetes service." json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty"` + Namespace string `description:"Namespace of the Kubernetes service." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"` +} + // BuildFilterFunc returns the name of the filter and the related dynamic.Middleware if needed. type BuildFilterFunc func(name, namespace string) (string, *dynamic.Middleware, error) @@ -368,9 +382,14 @@ func (p *Provider) createGatewayConf(ctx context.Context, client Client, gateway // and cannot be configured on the Gateway. listenerStatuses := p.fillGatewayConf(ctx, client, gateway, conf, tlsConfigs) - gatewayStatus, errG := p.makeGatewayStatus(gateway, listenerStatuses) + addresses, err := p.gatewayAddresses(client) + if err != nil { + return nil, fmt.Errorf("get Gateway status addresses: %w", err) + } - err := client.UpdateGatewayStatus(gateway, gatewayStatus) + gatewayStatus, errG := p.makeGatewayStatus(gateway, listenerStatuses, addresses) + + err = client.UpdateGatewayStatus(gateway, gatewayStatus) if err != nil { return nil, fmt.Errorf("an error occurred while updating gateway status: %w", err) } @@ -618,11 +637,8 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * return listenerStatuses } -func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses []gatev1.ListenerStatus) (gatev1.GatewayStatus, error) { - // As Status.Addresses are not implemented yet, we initialize an empty array to follow the API expectations. - gatewayStatus := gatev1.GatewayStatus{ - Addresses: []gatev1.GatewayStatusAddress{}, - } +func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses []gatev1.ListenerStatus, addresses []gatev1.GatewayStatusAddress) (gatev1.GatewayStatus, error) { + gatewayStatus := gatev1.GatewayStatus{Addresses: addresses} var result error for i, listener := range listenerStatuses { @@ -701,6 +717,57 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses [ return gatewayStatus, nil } +func (p *Provider) gatewayAddresses(client Client) ([]gatev1.GatewayStatusAddress, error) { + if p.StatusAddress == nil { + return nil, nil + } + + if p.StatusAddress.IP != "" { + return []gatev1.GatewayStatusAddress{{ + Type: ptr.To(gatev1.IPAddressType), + Value: p.StatusAddress.IP, + }}, nil + } + + if p.StatusAddress.Hostname != "" { + return []gatev1.GatewayStatusAddress{{ + Type: ptr.To(gatev1.HostnameAddressType), + Value: p.StatusAddress.Hostname, + }}, nil + } + + svcRef := p.StatusAddress.Service + if svcRef.Name != "" && svcRef.Namespace != "" { + svc, exists, err := client.GetService(svcRef.Namespace, svcRef.Name) + if err != nil { + return nil, fmt.Errorf("unable to get service: %w", err) + } + if !exists { + return nil, fmt.Errorf("could not find a service with name %s in namespace %s", svcRef.Name, svcRef.Namespace) + } + + var addresses []gatev1.GatewayStatusAddress + for _, addr := range svc.Status.LoadBalancer.Ingress { + switch { + case addr.IP != "": + addresses = append(addresses, gatev1.GatewayStatusAddress{ + Type: ptr.To(gatev1.IPAddressType), + Value: addr.IP, + }) + + case addr.Hostname != "": + addresses = append(addresses, gatev1.GatewayStatusAddress{ + Type: ptr.To(gatev1.HostnameAddressType), + Value: addr.Hostname, + }) + } + } + return addresses, nil + } + + return nil, errors.New("empty Gateway status address configuration") +} + func (p *Provider) entryPointName(port gatev1.PortNumber, protocol gatev1.ProtocolType) (string, error) { portStr := strconv.FormatInt(int64(port), 10) diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 64f7c7f96..bfc9e3072 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -6296,30 +6296,6 @@ func Test_makeListenerKey(t *testing.T) { } } -func hostnamePtr(hostname gatev1.Hostname) *gatev1.Hostname { - return &hostname -} - -func groupPtr(group gatev1.Group) *gatev1.Group { - return &group -} - -func sectionNamePtr(sectionName gatev1.SectionName) *gatev1.SectionName { - return §ionName -} - -func namespacePtr(namespace gatev1.Namespace) *gatev1.Namespace { - return &namespace -} - -func kindPtr(kind gatev1.Kind) *gatev1.Kind { - return &kind -} - -func pathMatchTypePtr(p gatev1.PathMatchType) *gatev1.PathMatchType { return &p } - -func headerMatchTypePtr(h gatev1.HeaderMatchType) *gatev1.HeaderMatchType { return &h } - func Test_referenceGrantMatchesFrom(t *testing.T) { testCases := []struct { desc string @@ -6558,6 +6534,131 @@ func Test_referenceGrantMatchesTo(t *testing.T) { } } +func Test_gatewayAddresses(t *testing.T) { + testCases := []struct { + desc string + statusAddress *StatusAddress + paths []string + wantErr require.ErrorAssertionFunc + want []gatev1.GatewayStatusAddress + }{ + { + desc: "nothing", + wantErr: require.NoError, + }, + { + desc: "empty configuration", + statusAddress: &StatusAddress{}, + wantErr: require.Error, + }, + { + desc: "IP address", + statusAddress: &StatusAddress{ + IP: "1.2.3.4", + }, + wantErr: require.NoError, + want: []gatev1.GatewayStatusAddress{ + { + Type: ptr.To(gatev1.IPAddressType), + Value: "1.2.3.4", + }, + }, + }, + { + desc: "hostname address", + statusAddress: &StatusAddress{ + Hostname: "foo.bar", + }, + wantErr: require.NoError, + want: []gatev1.GatewayStatusAddress{ + { + Type: ptr.To(gatev1.HostnameAddressType), + Value: "foo.bar", + }, + }, + }, + { + desc: "service", + statusAddress: &StatusAddress{ + Service: ServiceRef{ + Name: "status-address", + Namespace: "default", + }, + }, + paths: []string{"services.yml"}, + wantErr: require.NoError, + want: []gatev1.GatewayStatusAddress{ + { + Type: ptr.To(gatev1.HostnameAddressType), + Value: "foo.bar", + }, + { + Type: ptr.To(gatev1.IPAddressType), + Value: "1.2.3.4", + }, + }, + }, + { + desc: "missing service", + statusAddress: &StatusAddress{ + Service: ServiceRef{ + Name: "status-address2", + Namespace: "default", + }, + }, + wantErr: require.Error, + }, + { + desc: "service without load-balancer status", + statusAddress: &StatusAddress{ + Service: ServiceRef{ + Name: "whoamitcp-bar", + Namespace: "bar", + }, + }, + paths: []string{"services.yml"}, + wantErr: require.NoError, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + p := Provider{StatusAddress: test.statusAddress} + + got, err := p.gatewayAddresses(newClientMock(test.paths...)) + test.wantErr(t, err) + + assert.Equal(t, test.want, got) + }) + } +} + +func hostnamePtr(hostname gatev1.Hostname) *gatev1.Hostname { + return &hostname +} + +func groupPtr(group gatev1.Group) *gatev1.Group { + return &group +} + +func sectionNamePtr(sectionName gatev1.SectionName) *gatev1.SectionName { + return §ionName +} + +func namespacePtr(namespace gatev1.Namespace) *gatev1.Namespace { + return &namespace +} + +func kindPtr(kind gatev1.Kind) *gatev1.Kind { + return &kind +} + +func pathMatchTypePtr(p gatev1.PathMatchType) *gatev1.PathMatchType { return &p } + +func headerMatchTypePtr(h gatev1.HeaderMatchType) *gatev1.HeaderMatchType { return &h } + func objectNamePtr(objectName gatev1.ObjectName) *gatev1.ObjectName { return &objectName } From 19e6170fa5fea248268fb60eac1257702be75a72 Mon Sep 17 00:00:00 2001 From: Massimiliano D <126668030+mdeliatf@users.noreply.github.com> Date: Wed, 10 Apr 2024 09:50:04 +0200 Subject: [PATCH 07/18] Modify the Hub Button --- webui/public/traefiklabs-hub-button-app/main-v1.js | 4 ++-- webui/public/traefiklabs-hub-button-app/main-v1.js.map | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webui/public/traefiklabs-hub-button-app/main-v1.js b/webui/public/traefiklabs-hub-button-app/main-v1.js index 9d36a8b62..2c912be53 100644 --- a/webui/public/traefiklabs-hub-button-app/main-v1.js +++ b/webui/public/traefiklabs-hub-button-app/main-v1.js @@ -1,3 +1,3 @@ /* eslint-disable */ -!function(){var e={110:function(e,t,n){"use strict";var r=n(441),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},l={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},o={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},i={};function u(e){return r.isMemo(e)?o:i[e.$$typeof]||a}i[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},i[r.Memo]=o;var s=Object.defineProperty,c=Object.getOwnPropertyNames,f=Object.getOwnPropertySymbols,d=Object.getOwnPropertyDescriptor,p=Object.getPrototypeOf,h=Object.prototype;e.exports=function e(t,n,r){if("string"!==typeof n){if(h){var a=p(n);a&&a!==h&&e(t,a,r)}var o=c(n);f&&(o=o.concat(f(n)));for(var i=u(t),m=u(n),g=0;g