diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index 1c9bbb97d..93a785ff4 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -170,6 +170,7 @@ - "traefik.http.services.service02.loadbalancer.sticky=true" - "traefik.http.services.service02.loadbalancer.sticky.cookie=true" - "traefik.http.services.service02.loadbalancer.sticky.cookie.httponly=true" +- "traefik.http.services.service02.loadbalancer.sticky.cookie.maxage=42" - "traefik.http.services.service02.loadbalancer.sticky.cookie.name=foobar" - "traefik.http.services.service02.loadbalancer.sticky.cookie.samesite=foobar" - "traefik.http.services.service02.loadbalancer.sticky.cookie.secure=true" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 59a101d10..a8264d2ed 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -52,6 +52,7 @@ secure = true httpOnly = true sameSite = "foobar" + maxAge = 42 [[http.services.Service02.loadBalancer.servers]] url = "foobar" @@ -103,6 +104,7 @@ secure = true httpOnly = true sameSite = "foobar" + maxAge = 42 [http.services.Service04.weighted.healthCheck] [http.middlewares] [http.middlewares.Middleware01] diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index 44dfd6beb..7ae72ad7d 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -60,6 +60,7 @@ http: secure: true httpOnly: true sameSite: foobar + maxAge: 42 servers: - url: foobar - url: foobar @@ -104,6 +105,7 @@ http: secure: true httpOnly: true sameSite: foobar + maxAge: 42 healthCheck: {} middlewares: Middleware01: 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 2e3ae67a5..5a4c63c3c 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -160,6 +160,12 @@ spec: can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When + set to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -877,6 +883,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2158,6 +2170,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2249,6 +2267,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds until + the cookie expires. When set to a negative number, the + cookie expires immediately. When set to zero, the cookie + never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2356,6 +2380,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2395,6 +2425,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds until + the cookie expires. When set to a negative number, the + cookie expires immediately. When set to zero, the cookie + never expires. + type: integer name: description: Name defines the Cookie name. type: string diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index c8030e44c..f078b166a 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -241,6 +241,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/services/Service02/loadBalancer/servers/1/url` | `foobar` | | `traefik/http/services/Service02/loadBalancer/serversTransport` | `foobar` | | `traefik/http/services/Service02/loadBalancer/sticky/cookie/httpOnly` | `true` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/maxAge` | `42` | | `traefik/http/services/Service02/loadBalancer/sticky/cookie/name` | `foobar` | | `traefik/http/services/Service02/loadBalancer/sticky/cookie/sameSite` | `foobar` | | `traefik/http/services/Service02/loadBalancer/sticky/cookie/secure` | `true` | @@ -257,6 +258,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/services/Service04/weighted/services/1/name` | `foobar` | | `traefik/http/services/Service04/weighted/services/1/weight` | `42` | | `traefik/http/services/Service04/weighted/sticky/cookie/httpOnly` | `true` | +| `traefik/http/services/Service04/weighted/sticky/cookie/maxAge` | `42` | | `traefik/http/services/Service04/weighted/sticky/cookie/name` | `foobar` | | `traefik/http/services/Service04/weighted/sticky/cookie/sameSite` | `foobar` | | `traefik/http/services/Service04/weighted/sticky/cookie/secure` | `true` | diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml index 94daf2095..41628b58a 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml @@ -160,6 +160,12 @@ spec: can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When + set to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index 9565cf958..b7d8c6b52 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -302,6 +302,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string diff --git a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml index 9724e59b6..8620b4d74 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml @@ -134,6 +134,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -225,6 +231,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds until + the cookie expires. When set to a negative number, the + cookie expires immediately. When set to zero, the cookie + never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -332,6 +344,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -371,6 +389,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds until + the cookie expires. When set to a negative number, the + cookie expires immediately. When set to zero, the cookie + never expires. + type: integer name: description: Name defines the Cookie name. type: string diff --git a/docs/content/routing/providers/consul-catalog.md b/docs/content/routing/providers/consul-catalog.md index 8eb985043..678e23b92 100644 --- a/docs/content/routing/providers/consul-catalog.md +++ b/docs/content/routing/providers/consul-catalog.md @@ -273,6 +273,14 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none ``` +??? info "`traefik.http.services..loadbalancer.sticky.cookie.maxage`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.sticky.cookie.maxage=42 + ``` + ??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" See [response forwarding](../services/index.md#response-forwarding) for more information. diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md index 4a2b32436..0f6df9a9d 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -376,6 +376,14 @@ you'd add the label `traefik.http.services..loadbalancer.pa - "traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none" ``` +??? info "`traefik.http.services..loadbalancer.sticky.cookie.maxage`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + - "traefik.http.services.myservice.loadbalancer.sticky.cookie.maxage=42" + ``` + ??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" See [response forwarding](../services/index.md#response-forwarding) for more information. diff --git a/docs/content/routing/providers/ecs.md b/docs/content/routing/providers/ecs.md index 718303c0b..e5c66a8c3 100644 --- a/docs/content/routing/providers/ecs.md +++ b/docs/content/routing/providers/ecs.md @@ -275,6 +275,14 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none ``` +??? info "`traefik.http.services..loadbalancer.sticky.cookie.maxage`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.sticky.cookie.maxage=42 + ``` + ??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" See [response forwarding](../services/index.md#response-forwarding) for more information. diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index c66cb3d52..55f25494a 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -348,6 +348,7 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne name: cookie secure: true sameSite: none + maxAge: 42 strategy: RoundRobin weight: 10 nativeLB: true # [11] diff --git a/docs/content/routing/providers/kubernetes-ingress.md b/docs/content/routing/providers/kubernetes-ingress.md index 5f9a32073..01f1d5166 100644 --- a/docs/content/routing/providers/kubernetes-ingress.md +++ b/docs/content/routing/providers/kubernetes-ingress.md @@ -351,6 +351,14 @@ which in turn will create the resulting routers, services, handlers, etc. traefik.ingress.kubernetes.io/service.sticky.cookie.httponly: "true" ``` +??? info "`traefik.ingress.kubernetes.io/service.sticky.cookie.maxage`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.ingress.kubernetes.io/service.sticky.cookie.maxage: 42 + ``` + ## Path Types on Kubernetes 1.18+ If the Kubernetes cluster version is 1.18+, diff --git a/docs/content/routing/providers/kv.md b/docs/content/routing/providers/kv.md index a54ef1c61..3a6cc7744 100644 --- a/docs/content/routing/providers/kv.md +++ b/docs/content/routing/providers/kv.md @@ -244,6 +244,14 @@ A Story of key & values |-----------------------------------------------------------------------|--------| | `traefik/http/services/myservice/loadbalancer/sticky/cookie/samesite` | `none` | +??? info "`traefik/http/services//loadbalancer/sticky/cookie/maxage`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + | Key (Path) | Value | + |---------------------------------------------------------------------|-------| + | `traefik/http/services/myservice/loadbalancer/sticky/cookie/maxage` | `42` | + ??? info "`traefik/http/services//loadbalancer/responseforwarding/flushinterval`" See [response forwarding](../services/index.md#response-forwarding) for more information. @@ -306,6 +314,12 @@ A Story of key & values |------------------------------------------------------------------------|--------| | `traefik/http/services//weighted/sticky/cookie/httpOnly` | `true` | +??? info "`traefik/http/services//weighted/sticky/cookie/maxage`" + + | Key (Path) | Value | + |----------------------------------------------------------------------|-------| + | `traefik/http/services//weighted/sticky/cookie/maxage` | `42` | + ### Middleware More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md). diff --git a/docs/content/routing/providers/nomad.md b/docs/content/routing/providers/nomad.md index b0143d094..e1176c51d 100644 --- a/docs/content/routing/providers/nomad.md +++ b/docs/content/routing/providers/nomad.md @@ -265,6 +265,14 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none ``` +??? info "`traefik.http.services..loadbalancer.sticky.cookie.maxage`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.sticky.cookie.maxage=42 + ``` + ??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" See [response forwarding](../services/index.md#response-forwarding) for more information. diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md index fb6af7563..7658195e1 100644 --- a/docs/content/routing/services/index.md +++ b/docs/content/routing/services/index.md @@ -187,6 +187,13 @@ On subsequent requests, to keep the session alive with the same server, the clie The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`). +!!! info "MaxAge" + + By default, the affinity cookie will never expire as the `MaxAge` option is set to zero. + + This option indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + !!! info "Secure & HTTPOnly & SameSite flags" By default, the affinity cookie is created without those flags. diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 2e3ae67a5..5a4c63c3c 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -160,6 +160,12 @@ spec: can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When + set to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -877,6 +883,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2158,6 +2170,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2249,6 +2267,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds until + the cookie expires. When set to a negative number, the + cookie expires immediately. When set to zero, the cookie + never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2356,6 +2380,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2395,6 +2425,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds until + the cookie expires. When set to a negative number, the + cookie expires immediately. When set to zero, the cookie + never expires. + type: integer name: description: Name defines the Cookie name. type: string diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index 0c31c8871..a447e94eb 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -155,6 +155,10 @@ type Cookie struct { // SameSite defines the same site policy. // More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite SameSite string `json:"sameSite,omitempty" toml:"sameSite,omitempty" yaml:"sameSite,omitempty" export:"true"` + // MaxAge indicates the number of seconds until the cookie expires. + // When set to a negative number, the cookie expires immediately. + // When set to zero, the cookie never expires. + MaxAge int `json:"maxAge,omitempty" toml:"maxAge,omitempty" yaml:"maxAge,omitempty" export:"true"` } // +k8s:deepcopy-gen=true diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go index c55a73c15..28606cdbc 100644 --- a/pkg/config/label/label_test.go +++ b/pkg/config/label/label_test.go @@ -1334,6 +1334,7 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Services.Service0.LoadBalancer.Sticky.Cookie.Name": "foobar", "traefik.HTTP.Services.Service0.LoadBalancer.Sticky.Cookie.HTTPOnly": "true", "traefik.HTTP.Services.Service0.LoadBalancer.Sticky.Cookie.Secure": "false", + "traefik.HTTP.Services.Service0.LoadBalancer.Sticky.Cookie.MaxAge": "0", "traefik.HTTP.Services.Service0.LoadBalancer.ServersTransport": "foobar", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Headers.name0": "foobar", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Headers.name1": "foobar", diff --git a/pkg/server/service/loadbalancer/wrr/wrr.go b/pkg/server/service/loadbalancer/wrr/wrr.go index 343c525d2..f38facf35 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr.go +++ b/pkg/server/service/loadbalancer/wrr/wrr.go @@ -25,6 +25,7 @@ type stickyCookie struct { secure bool httpOnly bool sameSite string + maxAge int } func convertSameSite(sameSite string) http.SameSite { @@ -77,6 +78,7 @@ func New(sticky *dynamic.Sticky, wantHealthCheck bool) *Balancer { secure: sticky.Cookie.Secure, httpOnly: sticky.Cookie.HTTPOnly, sameSite: sticky.Cookie.SameSite, + maxAge: sticky.Cookie.MaxAge, } } @@ -238,6 +240,7 @@ func (b *Balancer) ServeHTTP(w http.ResponseWriter, req *http.Request) { HttpOnly: b.stickyCookie.httpOnly, Secure: b.stickyCookie.secure, SameSite: convertSameSite(b.stickyCookie.sameSite), + MaxAge: b.stickyCookie.maxAge, } http.SetCookie(w, cookie) } diff --git a/pkg/server/service/loadbalancer/wrr/wrr_test.go b/pkg/server/service/loadbalancer/wrr/wrr_test.go index 2392ad84b..3708fa618 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr_test.go +++ b/pkg/server/service/loadbalancer/wrr/wrr_test.go @@ -225,6 +225,7 @@ func TestSticky(t *testing.T) { Secure: true, HTTPOnly: true, SameSite: "none", + MaxAge: 42, }, }, false) @@ -261,6 +262,7 @@ func TestSticky(t *testing.T) { assert.True(t, recorder.cookies["test"].HttpOnly) assert.True(t, recorder.cookies["test"].Secure) assert.Equal(t, http.SameSiteNoneMode, recorder.cookies["test"].SameSite) + assert.Equal(t, 42, recorder.cookies["test"].MaxAge) } func TestSticky_FallBack(t *testing.T) {