diff --git a/Gopkg.lock b/Gopkg.lock index 2281d851f..38703b524 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1937,8 +1937,7 @@ version = "v0.1.0" [[projects]] - branch = "master" - digest = "1:86f14aadf288fe3ad8ac060bcb2b5083cec3829dd883803486ec834d031060c9" + digest = "1:a31155114d2977ef48d2cca6810c4e055545d676bf13b604c2debd42de44cad0" name = "github.com/vulcand/oxy" packages = [ "buffer", @@ -1951,7 +1950,8 @@ "utils", ] pruneopts = "NUT" - revision = "0d102f45103cf49a95b5c6e810e092973cbcb68c" + revision = "9dbd22c030f2187f590fea61ebe84ab78a8146a2" + version = "v1.1.0" [[projects]] digest = "1:ca6bac407fedc14fbeeba861dd33a821ba3a1624c10126ec6003b0a28d4139c5" diff --git a/Gopkg.toml b/Gopkg.toml index bff44b2cf..916049633 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -175,7 +175,7 @@ version = "0.1.0" [[constraint]] - branch = "master" + version = "v1.1.0" name = "github.com/vulcand/oxy" [[constraint]] diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index 06bf3c153..1fd467916 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -159,6 +159,9 @@ var _templatesConsul_catalogTmpl = []byte(`[backends] {{if $loadBalancer.Stickiness }} [backends."backend-{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} @@ -654,6 +657,9 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} {{if $loadBalancer.Stickiness }} [backends."backend-{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} @@ -1000,6 +1006,9 @@ var _templatesEcsTmpl = []byte(`[backends] {{if $loadBalancer.Stickiness }} [backends."backend-{{ $serviceName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} @@ -1325,6 +1334,9 @@ var _templatesKubernetesTmpl = []byte(`[backends] {{if $backend.LoadBalancer.Stickiness }} [backends."{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $backend.LoadBalancer.Stickiness.CookieName }}" + secure = {{ $backend.Stickiness.Secure }} + httpOnly = {{ $backend.Stickiness.HTTPOnly }} + sameSite = "{{ $backend.Stickiness.SameSite }}" {{end}} {{if $backend.MaxConn }} @@ -1582,6 +1594,9 @@ var _templatesKvTmpl = []byte(`[backends] {{if $loadBalancer.Stickiness }} [backends."{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} @@ -1970,6 +1985,9 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }} {{if $loadBalancer.Stickiness }} [backends."{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} @@ -2302,6 +2320,9 @@ var _templatesMesosTmpl = []byte(`[backends] {{if $loadBalancer.Stickiness }} [backends."backend-{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} @@ -2688,6 +2709,9 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }} {{if $loadBalancer.Stickiness }} [backends."backend-{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} diff --git a/docs/basics.md b/docs/basics.md index 6037c1604..50589fe57 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -452,6 +452,27 @@ If not, a new backend will be assigned. # Default: a sha1 (6 chars) # # cookieName = "my_cookie" + + # Customize secure option + # + # Optional + # Default: false + # + # secure = true + + # Customize http only option + # + # Optional + # Default: false + # + # httpOnly = true + + # Customize same site option. + # Can be: "none", "lax", "strict" + # + # Optional + # + # sameSite = "none" ``` The deprecated way: diff --git a/docs/configuration/backends/consulcatalog.md b/docs/configuration/backends/consulcatalog.md index 2a72419c7..433239149 100644 --- a/docs/configuration/backends/consulcatalog.md +++ b/docs/configuration/backends/consulcatalog.md @@ -124,6 +124,9 @@ Additional settings can be defined using Consul Catalog tags. | `.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm. | | `.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions. | | `.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions. | +| `.backend.loadbalancer.stickiness.secure=true` | Sets secure cookie option for sticky sessions. | +| `.backend.loadbalancer.stickiness.httpOnly=true` | Sets http only cookie option for sticky sessions. | +| `.backend.loadbalancer.stickiness.sameSite=none` | Sets same site cookie option for sticky sessions. (`none`, `lax`, `strict`) | | `.backend.loadbalancer.sticky=true` | Enables backend sticky sessions. (DEPRECATED) | | `.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.
Must be used in conjunction with the below label to take effect. | | `.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.
Must be used in conjunction with the above label to take effect. | diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index da1d5ecbd..2a0e3bf25 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -313,6 +313,9 @@ Labels can be used on containers to override default behavior. | `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm | | `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions | | `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions | +| `traefik.backend.loadbalancer.stickiness.secure=true` | Sets secure cookie option for sticky sessions. | +| `traefik.backend.loadbalancer.stickiness.httpOnly=true` | Sets http only cookie option for sticky sessions. | +| `traefik.backend.loadbalancer.stickiness.sameSite=none` | Sets same site cookie option for sticky sessions. (`none`, `lax`, `strict`) | | `traefik.backend.loadbalancer.sticky=true` | Enables backend sticky sessions (DEPRECATED) | | `traefik.backend.loadbalancer.swarm=true` | Uses Swarm's inbuilt load balancer (only relevant under Swarm Mode) [3]. | | `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.
Must be used in conjunction with the below label to take effect. | diff --git a/docs/configuration/backends/ecs.md b/docs/configuration/backends/ecs.md index 6ed80b630..825770010 100644 --- a/docs/configuration/backends/ecs.md +++ b/docs/configuration/backends/ecs.md @@ -160,6 +160,9 @@ Labels can be used on task containers to override default behaviour: | `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm | | `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions | | `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie manually name for sticky sessions | +| `traefik.backend.loadbalancer.stickiness.secure=true` | Sets secure cookie option for sticky sessions. | +| `traefik.backend.loadbalancer.stickiness.httpOnly=true` | Sets http only cookie option for sticky sessions. | +| `traefik.backend.loadbalancer.stickiness.sameSite=none` | Sets same site cookie option for sticky sessions. (`none`, `lax`, `strict`) | | `traefik.backend.loadbalancer.sticky=true` | Enables backend sticky sessions (DEPRECATED) | | `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.
Must be used in conjunction with the below label to take effect. | | `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.
Must be used in conjunction with the above label to take effect. | diff --git a/docs/configuration/backends/file.md b/docs/configuration/backends/file.md index 59dcda7cf..3542f020a 100644 --- a/docs/configuration/backends/file.md +++ b/docs/configuration/backends/file.md @@ -31,6 +31,9 @@ Traefik can be configured with a file. method = "drr" [backends.backend1.loadBalancer.stickiness] cookieName = "foobar" + secure = true + httpOnly = true + sameSite = "foobar" [backends.backend1.maxConn] amount = 10 diff --git a/docs/configuration/backends/marathon.md b/docs/configuration/backends/marathon.md index 381a9c7a4..a5959be9e 100644 --- a/docs/configuration/backends/marathon.md +++ b/docs/configuration/backends/marathon.md @@ -218,6 +218,9 @@ The following labels can be defined on Marathon applications. They adjust the be | `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm | | `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions | | `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions | +| `traefik.backend.loadbalancer.stickiness.secure=true` | Sets secure cookie option for sticky sessions. | +| `traefik.backend.loadbalancer.stickiness.httpOnly=true` | Sets http only cookie option for sticky sessions. | +| `traefik.backend.loadbalancer.stickiness.sameSite=none` | Sets same site cookie option for sticky sessions. (`none`, `lax`, `strict`) | | `traefik.backend.loadbalancer.sticky=true` | Enables backend sticky sessions (DEPRECATED) | | `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.
Must be used in conjunction with the below label to take effect. | | `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.
Must be used in conjunction with the above label to take effect. | diff --git a/docs/configuration/backends/mesos.md b/docs/configuration/backends/mesos.md index ecb595ca6..44ccd937d 100644 --- a/docs/configuration/backends/mesos.md +++ b/docs/configuration/backends/mesos.md @@ -132,6 +132,9 @@ The following labels can be defined on Mesos tasks. They adjust the behavior for | `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm | | `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions | | `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie manually name for sticky sessions | +| `traefik.backend.loadbalancer.stickiness.secure=true` | Sets secure cookie option for sticky sessions. | +| `traefik.backend.loadbalancer.stickiness.httpOnly=true` | Sets http only cookie option for sticky sessions. | +| `traefik.backend.loadbalancer.stickiness.sameSite=none` | Sets same site cookie option for sticky sessions. (`none`, `lax`, `strict`) | | `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.
Must be used in conjunction with the below label to take effect. | | `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.
Must be used in conjunction with the above label to take effect. | | `traefik.frontend.auth.basic=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash` (DEPRECATED). | diff --git a/docs/configuration/backends/rancher.md b/docs/configuration/backends/rancher.md index 7eefb9a04..086842e4c 100644 --- a/docs/configuration/backends/rancher.md +++ b/docs/configuration/backends/rancher.md @@ -167,6 +167,9 @@ Labels can be used on task containers to override default behavior: | `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm | | `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions | | `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions | +| `traefik.backend.loadbalancer.stickiness.secure=true` | Sets secure cookie option for sticky sessions. | +| `traefik.backend.loadbalancer.stickiness.httpOnly=true` | Sets http only cookie option for sticky sessions. | +| `traefik.backend.loadbalancer.stickiness.sameSite=none` | Sets same site cookie option for sticky sessions. (`none`, `lax`, `strict`) | | `traefik.backend.loadbalancer.sticky=true` | Enables backend sticky sessions (DEPRECATED) | | `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.
Must be used in conjunction with the below label to take effect. | | `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.
Must be used in conjunction with the above label to take effect. | diff --git a/provider/consulcatalog/config_test.go b/provider/consulcatalog/config_test.go index e8c409f04..c6c9160c4 100644 --- a/provider/consulcatalog/config_test.go +++ b/provider/consulcatalog/config_test.go @@ -254,7 +254,8 @@ func TestProviderBuildConfiguration(t *testing.T) { }, }, }, - }, { + }, + { desc: "Should build config with a digest auth", nodes: []catalogUpdate{ { @@ -416,6 +417,9 @@ func TestProviderBuildConfiguration(t *testing.T) { label.TraefikBackendLoadBalancerSticky + "=true", label.TraefikBackendLoadBalancerStickiness + "=true", label.TraefikBackendLoadBalancerStickinessCookieName + "=chocolate", + label.TraefikBackendLoadBalancerStickinessSecure + "=true", + label.TraefikBackendLoadBalancerStickinessHTTPOnly + "=true", + label.TraefikBackendLoadBalancerStickinessSameSite + "=none", label.TraefikBackendMaxConnAmount + "=666", label.TraefikBackendMaxConnExtractorFunc + "=client.ip", label.TraefikBackendBufferingMaxResponseBodyBytes + "=10485760", @@ -700,6 +704,9 @@ func TestProviderBuildConfiguration(t *testing.T) { Sticky: true, Stickiness: &types.Stickiness{ CookieName: "chocolate", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, MaxConn: &types.MaxConn{ diff --git a/provider/docker/config_container_docker_test.go b/provider/docker/config_container_docker_test.go index 53d27b8f7..fd4c8f4a5 100644 --- a/provider/docker/config_container_docker_test.go +++ b/provider/docker/config_container_docker_test.go @@ -463,6 +463,9 @@ func TestDockerBuildConfiguration(t *testing.T) { label.TraefikBackendLoadBalancerSticky: "true", label.TraefikBackendLoadBalancerStickiness: "true", label.TraefikBackendLoadBalancerStickinessCookieName: "chocolate", + label.TraefikBackendLoadBalancerStickinessSecure: "true", + label.TraefikBackendLoadBalancerStickinessHTTPOnly: "true", + label.TraefikBackendLoadBalancerStickinessSameSite: "none", label.TraefikBackendMaxConnAmount: "666", label.TraefikBackendMaxConnExtractorFunc: "client.ip", label.TraefikBackendBufferingMaxResponseBodyBytes: "10485760", @@ -711,6 +714,9 @@ func TestDockerBuildConfiguration(t *testing.T) { Sticky: true, Stickiness: &types.Stickiness{ CookieName: "chocolate", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, MaxConn: &types.MaxConn{ diff --git a/provider/docker/config_container_swarm_test.go b/provider/docker/config_container_swarm_test.go index 6cbddef00..ec309259f 100644 --- a/provider/docker/config_container_swarm_test.go +++ b/provider/docker/config_container_swarm_test.go @@ -412,6 +412,9 @@ func TestSwarmBuildConfiguration(t *testing.T) { label.TraefikBackendLoadBalancerSticky: "true", label.TraefikBackendLoadBalancerStickiness: "true", label.TraefikBackendLoadBalancerStickinessCookieName: "chocolate", + label.TraefikBackendLoadBalancerStickinessSecure: "true", + label.TraefikBackendLoadBalancerStickinessHTTPOnly: "true", + label.TraefikBackendLoadBalancerStickinessSameSite: "none", label.TraefikBackendMaxConnAmount: "666", label.TraefikBackendMaxConnExtractorFunc: "client.ip", label.TraefikBackendBufferingMaxResponseBodyBytes: "10485760", @@ -611,6 +614,9 @@ func TestSwarmBuildConfiguration(t *testing.T) { Sticky: true, Stickiness: &types.Stickiness{ CookieName: "chocolate", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, MaxConn: &types.MaxConn{ diff --git a/provider/ecs/config_test.go b/provider/ecs/config_test.go index a2d0e4a80..b197498b3 100644 --- a/provider/ecs/config_test.go +++ b/provider/ecs/config_test.go @@ -353,6 +353,9 @@ func TestBuildConfiguration(t *testing.T) { label.TraefikBackendLoadBalancerSticky: aws.String("true"), label.TraefikBackendLoadBalancerStickiness: aws.String("true"), label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"), + label.TraefikBackendLoadBalancerStickinessSecure: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessHTTPOnly: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessSameSite: aws.String("none"), label.TraefikBackendMaxConnAmount: aws.String("666"), label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), label.TraefikBackendBufferingMaxResponseBodyBytes: aws.String("10485760"), @@ -475,6 +478,9 @@ func TestBuildConfiguration(t *testing.T) { Sticky: true, Stickiness: &types.Stickiness{ CookieName: "chocolate", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, MaxConn: &types.MaxConn{ @@ -662,6 +668,9 @@ func TestBuildConfiguration(t *testing.T) { label.TraefikBackendLoadBalancerSticky: aws.String("true"), label.TraefikBackendLoadBalancerStickiness: aws.String("true"), label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"), + label.TraefikBackendLoadBalancerStickinessSecure: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessHTTPOnly: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessSameSite: aws.String("none"), label.TraefikBackendMaxConnAmount: aws.String("666"), label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), label.TraefikBackendBufferingMaxResponseBodyBytes: aws.String("10485760"), @@ -750,6 +759,9 @@ func TestBuildConfiguration(t *testing.T) { label.TraefikBackendLoadBalancerSticky: aws.String("true"), label.TraefikBackendLoadBalancerStickiness: aws.String("true"), label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"), + label.TraefikBackendLoadBalancerStickinessSecure: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessHTTPOnly: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessSameSite: aws.String("none"), label.TraefikBackendMaxConnAmount: aws.String("666"), label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), label.TraefikBackendBufferingMaxResponseBodyBytes: aws.String("10485760"), @@ -839,6 +851,9 @@ func TestBuildConfiguration(t *testing.T) { Sticky: true, Stickiness: &types.Stickiness{ CookieName: "chocolate", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, MaxConn: &types.MaxConn{ diff --git a/provider/kubernetes/annotations.go b/provider/kubernetes/annotations.go index 042b5708e..dd2c73d99 100644 --- a/provider/kubernetes/annotations.go +++ b/provider/kubernetes/annotations.go @@ -30,6 +30,9 @@ const ( annotationKubernetesLoadBalancerMethod = "ingress.kubernetes.io/load-balancer-method" annotationKubernetesAffinity = "ingress.kubernetes.io/affinity" annotationKubernetesSessionCookieName = "ingress.kubernetes.io/session-cookie-name" + annotationKubernetesSessionSecure = "ingress.kubernetes.io/session-secure" + annotationKubernetesSessionHTTPOnly = "ingress.kubernetes.io/session-http-only" + annotationKubernetesSessionSameSite = "ingress.kubernetes.io/session-same-site" annotationKubernetesRuleType = "ingress.kubernetes.io/rule-type" annotationKubernetesRedirectEntryPoint = "ingress.kubernetes.io/redirect-entry-point" annotationKubernetesRedirectPermanent = "ingress.kubernetes.io/redirect-permanent" diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index d7899ac15..55d035aaf 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -1151,14 +1151,16 @@ func getLoadBalancer(service *corev1.Service) *types.LoadBalancer { } func getStickiness(service *corev1.Service) *types.Stickiness { - if getBoolValue(service.Annotations, annotationKubernetesAffinity, false) { - stickiness := &types.Stickiness{} - if cookieName := getStringValue(service.Annotations, annotationKubernetesSessionCookieName, ""); len(cookieName) > 0 { - stickiness.CookieName = cookieName - } - return stickiness + if !getBoolValue(service.Annotations, annotationKubernetesAffinity, false) { + return nil + } + + return &types.Stickiness{ + CookieName: getStringValue(service.Annotations, annotationKubernetesSessionCookieName, ""), + Secure: getBoolValue(service.Annotations, annotationKubernetesSessionSecure, false), + HTTPOnly: getBoolValue(service.Annotations, annotationKubernetesSessionHTTPOnly, false), + SameSite: getStringValue(service.Annotations, annotationKubernetesSessionSameSite, ""), } - return nil } func getHeader(i *extensionsv1beta1.Ingress) *types.Headers { diff --git a/provider/kv/keynames.go b/provider/kv/keynames.go index 23cf654f9..f44af9ad8 100644 --- a/provider/kv/keynames.go +++ b/provider/kv/keynames.go @@ -14,6 +14,9 @@ const ( pathBackendLoadBalancerSticky = "/loadbalancer/sticky" pathBackendLoadBalancerStickiness = "/loadbalancer/stickiness" pathBackendLoadBalancerStickinessCookieName = "/loadbalancer/stickiness/cookiename" + pathBackendLoadBalancerStickinessSecure = "/loadbalancer/stickiness/secure" + pathBackendLoadBalancerStickinessHTTPOnly = "/loadbalancer/stickiness/httponly" + pathBackendLoadBalancerStickinessSameSite = "/loadbalancer/stickiness/samesite" pathBackendMaxConnAmount = "/maxconn/amount" pathBackendMaxConnExtractorFunc = "/maxconn/extractorfunc" pathBackendServers = "/servers/" diff --git a/provider/kv/kv_config.go b/provider/kv/kv_config.go index c17ca3dca..019dfdb2b 100644 --- a/provider/kv/kv_config.go +++ b/provider/kv/kv_config.go @@ -276,6 +276,9 @@ func (p *Provider) getLoadBalancer(rootPath string) *types.LoadBalancer { if p.getBool(false, rootPath, pathBackendLoadBalancerStickiness) { lb.Stickiness = &types.Stickiness{ CookieName: p.get("", rootPath, pathBackendLoadBalancerStickinessCookieName), + Secure: p.getBool(false, rootPath, pathBackendLoadBalancerStickinessSecure), + HTTPOnly: p.getBool(false, rootPath, pathBackendLoadBalancerStickinessHTTPOnly), + SameSite: p.get("", rootPath, pathBackendLoadBalancerStickinessSameSite), } } diff --git a/provider/kv/kv_config_test.go b/provider/kv/kv_config_test.go index 8e61e339f..4290a25c9 100644 --- a/provider/kv/kv_config_test.go +++ b/provider/kv/kv_config_test.go @@ -260,6 +260,9 @@ func TestProviderBuildConfiguration(t *testing.T) { withPair(pathBackendLoadBalancerSticky, "true"), withPair(pathBackendLoadBalancerStickiness, "true"), withPair(pathBackendLoadBalancerStickinessCookieName, "tomate"), + withPair(pathBackendLoadBalancerStickinessSecure, "true"), + withPair(pathBackendLoadBalancerStickinessHTTPOnly, "true"), + withPair(pathBackendLoadBalancerStickinessSameSite, "none"), withPair(pathBackendHealthCheckScheme, "http"), withPair(pathBackendHealthCheckPath, "/health"), withPair(pathBackendHealthCheckPort, "80"), @@ -389,6 +392,9 @@ func TestProviderBuildConfiguration(t *testing.T) { Sticky: true, Stickiness: &types.Stickiness{ CookieName: "tomate", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, MaxConn: &types.MaxConn{ @@ -1949,12 +1955,18 @@ func TestProviderGetLoadBalancer(t *testing.T) { withPair(pathBackendLoadBalancerMethod, "drr"), withPair(pathBackendLoadBalancerSticky, "true"), withPair(pathBackendLoadBalancerStickiness, "true"), - withPair(pathBackendLoadBalancerStickinessCookieName, "aubergine"))), + withPair(pathBackendLoadBalancerStickinessCookieName, "aubergine"), + withPair(pathBackendLoadBalancerStickinessSecure, "true"), + withPair(pathBackendLoadBalancerStickinessHTTPOnly, "true"), + withPair(pathBackendLoadBalancerStickinessSameSite, "none"))), expected: &types.LoadBalancer{ Method: "drr", Sticky: true, Stickiness: &types.Stickiness{ CookieName: "aubergine", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, }, diff --git a/provider/label/label.go b/provider/label/label.go index 8eb03291e..d038b8b57 100644 --- a/provider/label/label.go +++ b/provider/label/label.go @@ -26,6 +26,9 @@ const ( DefaultBackendLoadBalancerMethod = "wrr" DefaultBackendMaxconnExtractorFunc = "request.host" DefaultBackendLoadbalancerStickinessCookieName = "" + DefaultBackendLoadbalancerStickinessSecure = false + DefaultBackendLoadbalancerStickinessHTTPOnly = false + DefaultBackendLoadbalancerStickinessSameSite = "" DefaultBackendHealthCheckPort = 0 ) diff --git a/provider/label/names.go b/provider/label/names.go index 0e574e612..6f591a1eb 100644 --- a/provider/label/names.go +++ b/provider/label/names.go @@ -2,142 +2,150 @@ package label // Traefik labels const ( - Prefix = "traefik." - SuffixBackend = "backend" - SuffixDomain = "domain" - SuffixEnable = "enable" - SuffixPort = "port" - SuffixPortName = "portName" - SuffixPortIndex = "portIndex" - SuffixProtocol = "protocol" - SuffixTags = "tags" - SuffixWeight = "weight" - SuffixBackendID = "backend.id" - SuffixBackendCircuitBreaker = "backend.circuitbreaker" - SuffixBackendCircuitBreakerExpression = "backend.circuitbreaker.expression" - SuffixBackendHealthCheckScheme = "backend.healthcheck.scheme" - SuffixBackendHealthCheckPath = "backend.healthcheck.path" - SuffixBackendHealthCheckPort = "backend.healthcheck.port" - SuffixBackendHealthCheckInterval = "backend.healthcheck.interval" - SuffixBackendHealthCheckHostname = "backend.healthcheck.hostname" - SuffixBackendHealthCheckHeaders = "backend.healthcheck.headers" - SuffixBackendLoadBalancer = "backend.loadbalancer" - SuffixBackendLoadBalancerMethod = SuffixBackendLoadBalancer + ".method" - SuffixBackendLoadBalancerSticky = SuffixBackendLoadBalancer + ".sticky" - SuffixBackendLoadBalancerStickiness = SuffixBackendLoadBalancer + ".stickiness" - SuffixBackendLoadBalancerStickinessCookieName = SuffixBackendLoadBalancer + ".stickiness.cookieName" - SuffixBackendMaxConnAmount = "backend.maxconn.amount" - SuffixBackendMaxConnExtractorFunc = "backend.maxconn.extractorfunc" - SuffixBackendBuffering = "backend.buffering" - SuffixBackendResponseForwardingFlushInterval = "backend.responseForwarding.flushInterval" - SuffixBackendBufferingMaxRequestBodyBytes = SuffixBackendBuffering + ".maxRequestBodyBytes" - SuffixBackendBufferingMemRequestBodyBytes = SuffixBackendBuffering + ".memRequestBodyBytes" - SuffixBackendBufferingMaxResponseBodyBytes = SuffixBackendBuffering + ".maxResponseBodyBytes" - SuffixBackendBufferingMemResponseBodyBytes = SuffixBackendBuffering + ".memResponseBodyBytes" - SuffixBackendBufferingRetryExpression = SuffixBackendBuffering + ".retryExpression" - SuffixFrontend = "frontend" - SuffixFrontendAuth = SuffixFrontend + ".auth" - SuffixFrontendAuthBasic = SuffixFrontendAuth + ".basic" - SuffixFrontendAuthBasicRemoveHeader = SuffixFrontendAuthBasic + ".removeHeader" - SuffixFrontendAuthBasicUsers = SuffixFrontendAuthBasic + ".users" - SuffixFrontendAuthBasicUsersFile = SuffixFrontendAuthBasic + ".usersFile" - SuffixFrontendAuthDigest = SuffixFrontendAuth + ".digest" - SuffixFrontendAuthDigestRemoveHeader = SuffixFrontendAuthDigest + ".removeHeader" - SuffixFrontendAuthDigestUsers = SuffixFrontendAuthDigest + ".users" - SuffixFrontendAuthDigestUsersFile = SuffixFrontendAuthDigest + ".usersFile" - SuffixFrontendAuthForward = SuffixFrontendAuth + ".forward" - SuffixFrontendAuthForwardAddress = SuffixFrontendAuthForward + ".address" - SuffixFrontendAuthForwardAuthResponseHeaders = SuffixFrontendAuthForward + ".authResponseHeaders" - SuffixFrontendAuthForwardTLS = SuffixFrontendAuthForward + ".tls" - SuffixFrontendAuthForwardTLSCa = SuffixFrontendAuthForwardTLS + ".ca" - SuffixFrontendAuthForwardTLSCaOptional = SuffixFrontendAuthForwardTLS + ".caOptional" - SuffixFrontendAuthForwardTLSCert = SuffixFrontendAuthForwardTLS + ".cert" - SuffixFrontendAuthForwardTLSInsecureSkipVerify = SuffixFrontendAuthForwardTLS + ".insecureSkipVerify" - SuffixFrontendAuthForwardTLSKey = SuffixFrontendAuthForwardTLS + ".key" - SuffixFrontendAuthForwardTrustForwardHeader = SuffixFrontendAuthForward + ".trustForwardHeader" - SuffixFrontendAuthHeaderField = SuffixFrontendAuth + ".headerField" - SuffixFrontendEntryPoints = "frontend.entryPoints" - SuffixFrontendHeaders = "frontend.headers." - SuffixFrontendRequestHeaders = SuffixFrontendHeaders + "customRequestHeaders" - SuffixFrontendResponseHeaders = SuffixFrontendHeaders + "customResponseHeaders" - SuffixFrontendHeadersAllowedHosts = SuffixFrontendHeaders + "allowedHosts" - SuffixFrontendHeadersHostsProxyHeaders = SuffixFrontendHeaders + "hostsProxyHeaders" - SuffixFrontendHeadersSSLForceHost = SuffixFrontendHeaders + "SSLForceHost" - SuffixFrontendHeadersSSLRedirect = SuffixFrontendHeaders + "SSLRedirect" - SuffixFrontendHeadersSSLTemporaryRedirect = SuffixFrontendHeaders + "SSLTemporaryRedirect" - SuffixFrontendHeadersSSLHost = SuffixFrontendHeaders + "SSLHost" - SuffixFrontendHeadersSSLProxyHeaders = SuffixFrontendHeaders + "SSLProxyHeaders" - SuffixFrontendHeadersSTSSeconds = SuffixFrontendHeaders + "STSSeconds" - SuffixFrontendHeadersSTSIncludeSubdomains = SuffixFrontendHeaders + "STSIncludeSubdomains" - SuffixFrontendHeadersSTSPreload = SuffixFrontendHeaders + "STSPreload" - SuffixFrontendHeadersForceSTSHeader = SuffixFrontendHeaders + "forceSTSHeader" - SuffixFrontendHeadersFrameDeny = SuffixFrontendHeaders + "frameDeny" - SuffixFrontendHeadersCustomFrameOptionsValue = SuffixFrontendHeaders + "customFrameOptionsValue" - SuffixFrontendHeadersContentTypeNosniff = SuffixFrontendHeaders + "contentTypeNosniff" - SuffixFrontendHeadersBrowserXSSFilter = SuffixFrontendHeaders + "browserXSSFilter" - SuffixFrontendHeadersCustomBrowserXSSValue = SuffixFrontendHeaders + "customBrowserXSSValue" - SuffixFrontendHeadersContentSecurityPolicy = SuffixFrontendHeaders + "contentSecurityPolicy" - SuffixFrontendHeadersPublicKey = SuffixFrontendHeaders + "publicKey" - SuffixFrontendHeadersReferrerPolicy = SuffixFrontendHeaders + "referrerPolicy" - SuffixFrontendHeadersIsDevelopment = SuffixFrontendHeaders + "isDevelopment" - SuffixFrontendPassHostHeader = "frontend.passHostHeader" - SuffixFrontendPassTLSClientCert = "frontend.passTLSClientCert" - SuffixFrontendPassTLSClientCertPem = SuffixFrontendPassTLSClientCert + ".pem" - SuffixFrontendPassTLSClientCertInfos = SuffixFrontendPassTLSClientCert + ".infos" - SuffixFrontendPassTLSClientCertInfosIssuer = SuffixFrontendPassTLSClientCertInfos + ".issuer" - SuffixFrontendPassTLSClientCertInfosIssuerCommonName = SuffixFrontendPassTLSClientCertInfosIssuer + ".commonName" - SuffixFrontendPassTLSClientCertInfosIssuerCountry = SuffixFrontendPassTLSClientCertInfosIssuer + ".country" - SuffixFrontendPassTLSClientCertInfosIssuerDomainComponent = SuffixFrontendPassTLSClientCertInfosIssuer + ".domainComponent" - SuffixFrontendPassTLSClientCertInfosIssuerLocality = SuffixFrontendPassTLSClientCertInfosIssuer + ".locality" - SuffixFrontendPassTLSClientCertInfosIssuerOrganization = SuffixFrontendPassTLSClientCertInfosIssuer + ".organization" - SuffixFrontendPassTLSClientCertInfosIssuerProvince = SuffixFrontendPassTLSClientCertInfosIssuer + ".province" - SuffixFrontendPassTLSClientCertInfosIssuerSerialNumber = SuffixFrontendPassTLSClientCertInfosIssuer + ".serialNumber" - SuffixFrontendPassTLSClientCertInfosSubject = SuffixFrontendPassTLSClientCertInfos + ".subject" - SuffixFrontendPassTLSClientCertInfosNotAfter = SuffixFrontendPassTLSClientCertInfos + ".notAfter" - SuffixFrontendPassTLSClientCertInfosNotBefore = SuffixFrontendPassTLSClientCertInfos + ".notBefore" - SuffixFrontendPassTLSClientCertInfosSans = SuffixFrontendPassTLSClientCertInfos + ".sans" - SuffixFrontendPassTLSClientCertInfosSubjectCommonName = SuffixFrontendPassTLSClientCertInfosSubject + ".commonName" - SuffixFrontendPassTLSClientCertInfosSubjectCountry = SuffixFrontendPassTLSClientCertInfosSubject + ".country" - SuffixFrontendPassTLSClientCertInfosSubjectDomainComponent = SuffixFrontendPassTLSClientCertInfosSubject + ".domainComponent" - SuffixFrontendPassTLSClientCertInfosSubjectLocality = SuffixFrontendPassTLSClientCertInfosSubject + ".locality" - SuffixFrontendPassTLSClientCertInfosSubjectOrganization = SuffixFrontendPassTLSClientCertInfosSubject + ".organization" - SuffixFrontendPassTLSClientCertInfosSubjectProvince = SuffixFrontendPassTLSClientCertInfosSubject + ".province" - SuffixFrontendPassTLSClientCertInfosSubjectSerialNumber = SuffixFrontendPassTLSClientCertInfosSubject + ".serialNumber" - SuffixFrontendPassTLSCert = "frontend.passTLSCert" // Deprecated - SuffixFrontendPriority = "frontend.priority" - SuffixFrontendRateLimitExtractorFunc = "frontend.rateLimit.extractorFunc" - SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint" - SuffixFrontendRedirectRegex = "frontend.redirect.regex" - SuffixFrontendRedirectReplacement = "frontend.redirect.replacement" - SuffixFrontendRedirectPermanent = "frontend.redirect.permanent" - SuffixFrontendRule = "frontend.rule" - SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange" // Deprecated - SuffixFrontendWhiteList = "frontend.whiteList." - SuffixFrontendWhiteListSourceRange = SuffixFrontendWhiteList + "sourceRange" - SuffixFrontendWhiteListUseXForwardedFor = SuffixFrontendWhiteList + "useXForwardedFor" - TraefikDomain = Prefix + SuffixDomain - TraefikEnable = Prefix + SuffixEnable - TraefikPort = Prefix + SuffixPort - TraefikPortName = Prefix + SuffixPortName - TraefikPortIndex = Prefix + SuffixPortIndex - TraefikProtocol = Prefix + SuffixProtocol - TraefikTags = Prefix + SuffixTags - TraefikWeight = Prefix + SuffixWeight - TraefikBackend = Prefix + SuffixBackend - TraefikBackendID = Prefix + SuffixBackendID - TraefikBackendCircuitBreaker = Prefix + SuffixBackendCircuitBreaker - TraefikBackendCircuitBreakerExpression = Prefix + SuffixBackendCircuitBreakerExpression - TraefikBackendHealthCheckScheme = Prefix + SuffixBackendHealthCheckScheme - TraefikBackendHealthCheckPath = Prefix + SuffixBackendHealthCheckPath - TraefikBackendHealthCheckPort = Prefix + SuffixBackendHealthCheckPort - TraefikBackendHealthCheckInterval = Prefix + SuffixBackendHealthCheckInterval - TraefikBackendHealthCheckHostname = Prefix + SuffixBackendHealthCheckHostname - TraefikBackendHealthCheckHeaders = Prefix + SuffixBackendHealthCheckHeaders - TraefikBackendLoadBalancer = Prefix + SuffixBackendLoadBalancer - TraefikBackendLoadBalancerMethod = Prefix + SuffixBackendLoadBalancerMethod - TraefikBackendLoadBalancerSticky = Prefix + SuffixBackendLoadBalancerSticky - TraefikBackendLoadBalancerStickiness = Prefix + SuffixBackendLoadBalancerStickiness - TraefikBackendLoadBalancerStickinessCookieName = Prefix + SuffixBackendLoadBalancerStickinessCookieName + Prefix = "traefik." + SuffixBackend = "backend" + SuffixDomain = "domain" + SuffixEnable = "enable" + SuffixPort = "port" + SuffixPortName = "portName" + SuffixPortIndex = "portIndex" + SuffixProtocol = "protocol" + SuffixTags = "tags" + SuffixWeight = "weight" + SuffixBackendID = "backend.id" + SuffixBackendCircuitBreaker = "backend.circuitbreaker" + SuffixBackendCircuitBreakerExpression = "backend.circuitbreaker.expression" + SuffixBackendHealthCheckScheme = "backend.healthcheck.scheme" + SuffixBackendHealthCheckPath = "backend.healthcheck.path" + SuffixBackendHealthCheckPort = "backend.healthcheck.port" + SuffixBackendHealthCheckInterval = "backend.healthcheck.interval" + SuffixBackendHealthCheckHostname = "backend.healthcheck.hostname" + SuffixBackendHealthCheckHeaders = "backend.healthcheck.headers" + SuffixBackendLoadBalancer = "backend.loadbalancer" + SuffixBackendLoadBalancerMethod = SuffixBackendLoadBalancer + ".method" + SuffixBackendLoadBalancerSticky = SuffixBackendLoadBalancer + ".sticky" + SuffixBackendLoadBalancerStickiness = SuffixBackendLoadBalancer + ".stickiness" + SuffixBackendLoadBalancerStickinessCookieName = SuffixBackendLoadBalancer + ".stickiness.cookieName" + SuffixBackendLoadBalancerStickinessSecure = SuffixBackendLoadBalancer + ".stickiness.secure" + SuffixBackendLoadBalancerStickinessCHTTPOnly = SuffixBackendLoadBalancer + ".stickiness.httpOnly" + SuffixBackendLoadBalancerStickinessSameSite = SuffixBackendLoadBalancer + ".stickiness.sameSite" + SuffixBackendMaxConnAmount = "backend.maxconn.amount" + SuffixBackendMaxConnExtractorFunc = "backend.maxconn.extractorfunc" + SuffixBackendBuffering = "backend.buffering" + SuffixBackendResponseForwardingFlushInterval = "backend.responseForwarding.flushInterval" + SuffixBackendBufferingMaxRequestBodyBytes = SuffixBackendBuffering + ".maxRequestBodyBytes" + SuffixBackendBufferingMemRequestBodyBytes = SuffixBackendBuffering + ".memRequestBodyBytes" + SuffixBackendBufferingMaxResponseBodyBytes = SuffixBackendBuffering + ".maxResponseBodyBytes" + SuffixBackendBufferingMemResponseBodyBytes = SuffixBackendBuffering + ".memResponseBodyBytes" + SuffixBackendBufferingRetryExpression = SuffixBackendBuffering + ".retryExpression" + SuffixFrontend = "frontend" + SuffixFrontendAuth = SuffixFrontend + ".auth" + SuffixFrontendAuthBasic = SuffixFrontendAuth + ".basic" + SuffixFrontendAuthBasicRemoveHeader = SuffixFrontendAuthBasic + ".removeHeader" + SuffixFrontendAuthBasicUsers = SuffixFrontendAuthBasic + ".users" + SuffixFrontendAuthBasicUsersFile = SuffixFrontendAuthBasic + ".usersFile" + SuffixFrontendAuthDigest = SuffixFrontendAuth + ".digest" + SuffixFrontendAuthDigestRemoveHeader = SuffixFrontendAuthDigest + ".removeHeader" + SuffixFrontendAuthDigestUsers = SuffixFrontendAuthDigest + ".users" + SuffixFrontendAuthDigestUsersFile = SuffixFrontendAuthDigest + ".usersFile" + SuffixFrontendAuthForward = SuffixFrontendAuth + ".forward" + SuffixFrontendAuthForwardAddress = SuffixFrontendAuthForward + ".address" + SuffixFrontendAuthForwardAuthResponseHeaders = SuffixFrontendAuthForward + ".authResponseHeaders" + SuffixFrontendAuthForwardTLS = SuffixFrontendAuthForward + ".tls" + SuffixFrontendAuthForwardTLSCa = SuffixFrontendAuthForwardTLS + ".ca" + SuffixFrontendAuthForwardTLSCaOptional = SuffixFrontendAuthForwardTLS + ".caOptional" + SuffixFrontendAuthForwardTLSCert = SuffixFrontendAuthForwardTLS + ".cert" + SuffixFrontendAuthForwardTLSInsecureSkipVerify = SuffixFrontendAuthForwardTLS + ".insecureSkipVerify" + SuffixFrontendAuthForwardTLSKey = SuffixFrontendAuthForwardTLS + ".key" + SuffixFrontendAuthForwardTrustForwardHeader = SuffixFrontendAuthForward + ".trustForwardHeader" + SuffixFrontendAuthHeaderField = SuffixFrontendAuth + ".headerField" + SuffixFrontendEntryPoints = "frontend.entryPoints" + SuffixFrontendHeaders = "frontend.headers." + SuffixFrontendRequestHeaders = SuffixFrontendHeaders + "customRequestHeaders" + SuffixFrontendResponseHeaders = SuffixFrontendHeaders + "customResponseHeaders" + SuffixFrontendHeadersAllowedHosts = SuffixFrontendHeaders + "allowedHosts" + SuffixFrontendHeadersHostsProxyHeaders = SuffixFrontendHeaders + "hostsProxyHeaders" + SuffixFrontendHeadersSSLForceHost = SuffixFrontendHeaders + "SSLForceHost" + SuffixFrontendHeadersSSLRedirect = SuffixFrontendHeaders + "SSLRedirect" + SuffixFrontendHeadersSSLTemporaryRedirect = SuffixFrontendHeaders + "SSLTemporaryRedirect" + SuffixFrontendHeadersSSLHost = SuffixFrontendHeaders + "SSLHost" + SuffixFrontendHeadersSSLProxyHeaders = SuffixFrontendHeaders + "SSLProxyHeaders" + SuffixFrontendHeadersSTSSeconds = SuffixFrontendHeaders + "STSSeconds" + SuffixFrontendHeadersSTSIncludeSubdomains = SuffixFrontendHeaders + "STSIncludeSubdomains" + SuffixFrontendHeadersSTSPreload = SuffixFrontendHeaders + "STSPreload" + SuffixFrontendHeadersForceSTSHeader = SuffixFrontendHeaders + "forceSTSHeader" + SuffixFrontendHeadersFrameDeny = SuffixFrontendHeaders + "frameDeny" + SuffixFrontendHeadersCustomFrameOptionsValue = SuffixFrontendHeaders + "customFrameOptionsValue" + SuffixFrontendHeadersContentTypeNosniff = SuffixFrontendHeaders + "contentTypeNosniff" + SuffixFrontendHeadersBrowserXSSFilter = SuffixFrontendHeaders + "browserXSSFilter" + SuffixFrontendHeadersCustomBrowserXSSValue = SuffixFrontendHeaders + "customBrowserXSSValue" + SuffixFrontendHeadersContentSecurityPolicy = SuffixFrontendHeaders + "contentSecurityPolicy" + SuffixFrontendHeadersPublicKey = SuffixFrontendHeaders + "publicKey" + SuffixFrontendHeadersReferrerPolicy = SuffixFrontendHeaders + "referrerPolicy" + SuffixFrontendHeadersIsDevelopment = SuffixFrontendHeaders + "isDevelopment" + SuffixFrontendPassHostHeader = "frontend.passHostHeader" + SuffixFrontendPassTLSClientCert = "frontend.passTLSClientCert" + SuffixFrontendPassTLSClientCertPem = SuffixFrontendPassTLSClientCert + ".pem" + SuffixFrontendPassTLSClientCertInfos = SuffixFrontendPassTLSClientCert + ".infos" + SuffixFrontendPassTLSClientCertInfosIssuer = SuffixFrontendPassTLSClientCertInfos + ".issuer" + SuffixFrontendPassTLSClientCertInfosIssuerCommonName = SuffixFrontendPassTLSClientCertInfosIssuer + ".commonName" + SuffixFrontendPassTLSClientCertInfosIssuerCountry = SuffixFrontendPassTLSClientCertInfosIssuer + ".country" + SuffixFrontendPassTLSClientCertInfosIssuerDomainComponent = SuffixFrontendPassTLSClientCertInfosIssuer + ".domainComponent" + SuffixFrontendPassTLSClientCertInfosIssuerLocality = SuffixFrontendPassTLSClientCertInfosIssuer + ".locality" + SuffixFrontendPassTLSClientCertInfosIssuerOrganization = SuffixFrontendPassTLSClientCertInfosIssuer + ".organization" + SuffixFrontendPassTLSClientCertInfosIssuerProvince = SuffixFrontendPassTLSClientCertInfosIssuer + ".province" + SuffixFrontendPassTLSClientCertInfosIssuerSerialNumber = SuffixFrontendPassTLSClientCertInfosIssuer + ".serialNumber" + SuffixFrontendPassTLSClientCertInfosSubject = SuffixFrontendPassTLSClientCertInfos + ".subject" + SuffixFrontendPassTLSClientCertInfosNotAfter = SuffixFrontendPassTLSClientCertInfos + ".notAfter" + SuffixFrontendPassTLSClientCertInfosNotBefore = SuffixFrontendPassTLSClientCertInfos + ".notBefore" + SuffixFrontendPassTLSClientCertInfosSans = SuffixFrontendPassTLSClientCertInfos + ".sans" + SuffixFrontendPassTLSClientCertInfosSubjectCommonName = SuffixFrontendPassTLSClientCertInfosSubject + ".commonName" + SuffixFrontendPassTLSClientCertInfosSubjectCountry = SuffixFrontendPassTLSClientCertInfosSubject + ".country" + SuffixFrontendPassTLSClientCertInfosSubjectDomainComponent = SuffixFrontendPassTLSClientCertInfosSubject + ".domainComponent" + SuffixFrontendPassTLSClientCertInfosSubjectLocality = SuffixFrontendPassTLSClientCertInfosSubject + ".locality" + SuffixFrontendPassTLSClientCertInfosSubjectOrganization = SuffixFrontendPassTLSClientCertInfosSubject + ".organization" + SuffixFrontendPassTLSClientCertInfosSubjectProvince = SuffixFrontendPassTLSClientCertInfosSubject + ".province" + SuffixFrontendPassTLSClientCertInfosSubjectSerialNumber = SuffixFrontendPassTLSClientCertInfosSubject + ".serialNumber" + SuffixFrontendPassTLSCert = "frontend.passTLSCert" // Deprecated + SuffixFrontendPriority = "frontend.priority" + SuffixFrontendRateLimitExtractorFunc = "frontend.rateLimit.extractorFunc" + SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint" + SuffixFrontendRedirectRegex = "frontend.redirect.regex" + SuffixFrontendRedirectReplacement = "frontend.redirect.replacement" + SuffixFrontendRedirectPermanent = "frontend.redirect.permanent" + SuffixFrontendRule = "frontend.rule" + SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange" // Deprecated + SuffixFrontendWhiteList = "frontend.whiteList." + SuffixFrontendWhiteListSourceRange = SuffixFrontendWhiteList + "sourceRange" + SuffixFrontendWhiteListUseXForwardedFor = SuffixFrontendWhiteList + "useXForwardedFor" + TraefikDomain = Prefix + SuffixDomain + TraefikEnable = Prefix + SuffixEnable + TraefikPort = Prefix + SuffixPort + TraefikPortName = Prefix + SuffixPortName + TraefikPortIndex = Prefix + SuffixPortIndex + TraefikProtocol = Prefix + SuffixProtocol + TraefikTags = Prefix + SuffixTags + TraefikWeight = Prefix + SuffixWeight + TraefikBackend = Prefix + SuffixBackend + TraefikBackendID = Prefix + SuffixBackendID + TraefikBackendCircuitBreaker = Prefix + SuffixBackendCircuitBreaker + TraefikBackendCircuitBreakerExpression = Prefix + SuffixBackendCircuitBreakerExpression + TraefikBackendHealthCheckScheme = Prefix + SuffixBackendHealthCheckScheme + TraefikBackendHealthCheckPath = Prefix + SuffixBackendHealthCheckPath + TraefikBackendHealthCheckPort = Prefix + SuffixBackendHealthCheckPort + TraefikBackendHealthCheckInterval = Prefix + SuffixBackendHealthCheckInterval + TraefikBackendHealthCheckHostname = Prefix + SuffixBackendHealthCheckHostname + TraefikBackendHealthCheckHeaders = Prefix + SuffixBackendHealthCheckHeaders + TraefikBackendLoadBalancer = Prefix + SuffixBackendLoadBalancer + TraefikBackendLoadBalancerMethod = Prefix + SuffixBackendLoadBalancerMethod + TraefikBackendLoadBalancerSticky = Prefix + SuffixBackendLoadBalancerSticky + TraefikBackendLoadBalancerStickiness = Prefix + SuffixBackendLoadBalancerStickiness + TraefikBackendLoadBalancerStickinessCookieName = Prefix + SuffixBackendLoadBalancerStickinessCookieName + // FIXME + TraefikBackendLoadBalancerStickinessSecure = Prefix + SuffixBackendLoadBalancerStickinessSecure + TraefikBackendLoadBalancerStickinessHTTPOnly = Prefix + SuffixBackendLoadBalancerStickinessCHTTPOnly + TraefikBackendLoadBalancerStickinessSameSite = Prefix + SuffixBackendLoadBalancerStickinessSameSite + TraefikBackendMaxConnAmount = Prefix + SuffixBackendMaxConnAmount TraefikBackendMaxConnExtractorFunc = Prefix + SuffixBackendMaxConnExtractorFunc TraefikBackendBuffering = Prefix + SuffixBackendBuffering diff --git a/provider/label/partial.go b/provider/label/partial.go index c7c4f4bc5..3726aa43a 100644 --- a/provider/label/partial.go +++ b/provider/label/partial.go @@ -426,8 +426,12 @@ func GetLoadBalancer(labels map[string]string) *types.LoadBalancer { } if GetBoolValue(labels, TraefikBackendLoadBalancerStickiness, false) { - cookieName := GetStringValue(labels, TraefikBackendLoadBalancerStickinessCookieName, DefaultBackendLoadbalancerStickinessCookieName) - lb.Stickiness = &types.Stickiness{CookieName: cookieName} + lb.Stickiness = &types.Stickiness{ + CookieName: GetStringValue(labels, TraefikBackendLoadBalancerStickinessCookieName, DefaultBackendLoadbalancerStickinessCookieName), + Secure: GetBoolValue(labels, TraefikBackendLoadBalancerStickinessSecure, DefaultBackendLoadbalancerStickinessSecure), + HTTPOnly: GetBoolValue(labels, TraefikBackendLoadBalancerStickinessHTTPOnly, DefaultBackendLoadbalancerStickinessHTTPOnly), + SameSite: GetStringValue(labels, TraefikBackendLoadBalancerStickinessSameSite, DefaultBackendLoadbalancerStickinessSameSite), + } } return lb diff --git a/provider/label/partial_test.go b/provider/label/partial_test.go index 306a9d7a1..8d54146ce 100644 --- a/provider/label/partial_test.go +++ b/provider/label/partial_test.go @@ -257,12 +257,18 @@ func TestGetLoadBalancer(t *testing.T) { TraefikBackendLoadBalancerSticky: "true", TraefikBackendLoadBalancerStickiness: "true", TraefikBackendLoadBalancerStickinessCookieName: "foo", + TraefikBackendLoadBalancerStickinessSecure: "true", + TraefikBackendLoadBalancerStickinessHTTPOnly: "true", + TraefikBackendLoadBalancerStickinessSameSite: "none", }, expected: &types.LoadBalancer{ Method: "drr", Sticky: true, Stickiness: &types.Stickiness{ CookieName: "foo", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, }, diff --git a/provider/marathon/config_segment_test.go b/provider/marathon/config_segment_test.go index a8acb8822..87374a360 100644 --- a/provider/marathon/config_segment_test.go +++ b/provider/marathon/config_segment_test.go @@ -101,6 +101,9 @@ func TestBuildConfigurationSegments(t *testing.T) { withLabel(label.TraefikBackendLoadBalancerSticky, "true"), withLabel(label.TraefikBackendLoadBalancerStickiness, "true"), withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"), + withLabel(label.TraefikBackendLoadBalancerStickinessSecure, "true"), + withLabel(label.TraefikBackendLoadBalancerStickinessHTTPOnly, "true"), + withLabel(label.TraefikBackendLoadBalancerStickinessSameSite, "none"), withLabel(label.TraefikBackendMaxConnAmount, "666"), withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"), withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"), @@ -347,6 +350,9 @@ func TestBuildConfigurationSegments(t *testing.T) { Sticky: true, Stickiness: &types.Stickiness{ CookieName: "chocolate", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, MaxConn: &types.MaxConn{ diff --git a/provider/marathon/config_test.go b/provider/marathon/config_test.go index 48d545bdb..b6701a264 100644 --- a/provider/marathon/config_test.go +++ b/provider/marathon/config_test.go @@ -369,6 +369,9 @@ func TestBuildConfiguration(t *testing.T) { withLabel(label.TraefikBackendLoadBalancerSticky, "true"), withLabel(label.TraefikBackendLoadBalancerStickiness, "true"), withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"), + withLabel(label.TraefikBackendLoadBalancerStickinessSecure, "true"), + withLabel(label.TraefikBackendLoadBalancerStickinessHTTPOnly, "true"), + withLabel(label.TraefikBackendLoadBalancerStickinessSameSite, "none"), withLabel(label.TraefikBackendMaxConnAmount, "666"), withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"), withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"), @@ -613,6 +616,9 @@ func TestBuildConfiguration(t *testing.T) { Sticky: true, Stickiness: &types.Stickiness{ CookieName: "chocolate", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, MaxConn: &types.MaxConn{ diff --git a/provider/mesos/config_segment_test.go b/provider/mesos/config_segment_test.go index 52491a0c2..af06ae587 100644 --- a/provider/mesos/config_segment_test.go +++ b/provider/mesos/config_segment_test.go @@ -119,6 +119,9 @@ func TestBuildConfigurationSegments(t *testing.T) { withLabel(label.TraefikBackendLoadBalancerSticky, "true"), withLabel(label.TraefikBackendLoadBalancerStickiness, "true"), withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"), + withLabel(label.TraefikBackendLoadBalancerStickinessSecure, "true"), + withLabel(label.TraefikBackendLoadBalancerStickinessHTTPOnly, "true"), + withLabel(label.TraefikBackendLoadBalancerStickinessSameSite, "none"), withLabel(label.TraefikBackendMaxConnAmount, "666"), withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"), withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"), @@ -368,6 +371,9 @@ func TestBuildConfigurationSegments(t *testing.T) { Sticky: true, Stickiness: &types.Stickiness{ CookieName: "chocolate", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, MaxConn: &types.MaxConn{ diff --git a/provider/mesos/config_test.go b/provider/mesos/config_test.go index 02dcaf9ff..fe2012217 100644 --- a/provider/mesos/config_test.go +++ b/provider/mesos/config_test.go @@ -325,6 +325,9 @@ func TestBuildConfiguration(t *testing.T) { withLabel(label.TraefikBackendLoadBalancerMethod, "drr"), withLabel(label.TraefikBackendLoadBalancerStickiness, "true"), withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"), + withLabel(label.TraefikBackendLoadBalancerStickinessSecure, "true"), + withLabel(label.TraefikBackendLoadBalancerStickinessHTTPOnly, "true"), + withLabel(label.TraefikBackendLoadBalancerStickinessSameSite, "none"), withLabel(label.TraefikBackendMaxConnAmount, "666"), withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"), withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"), @@ -572,6 +575,9 @@ func TestBuildConfiguration(t *testing.T) { Method: "drr", Stickiness: &types.Stickiness{ CookieName: "chocolate", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, MaxConn: &types.MaxConn{ diff --git a/provider/rancher/config_test.go b/provider/rancher/config_test.go index b1f8394c2..ea68801ba 100644 --- a/provider/rancher/config_test.go +++ b/provider/rancher/config_test.go @@ -52,6 +52,9 @@ func TestProviderBuildConfiguration(t *testing.T) { label.TraefikBackendLoadBalancerSticky: "true", label.TraefikBackendLoadBalancerStickiness: "true", label.TraefikBackendLoadBalancerStickinessCookieName: "chocolate", + label.TraefikBackendLoadBalancerStickinessSecure: "true", + label.TraefikBackendLoadBalancerStickinessHTTPOnly: "true", + label.TraefikBackendLoadBalancerStickinessSameSite: "none", label.TraefikBackendMaxConnAmount: "666", label.TraefikBackendMaxConnExtractorFunc: "client.ip", label.TraefikBackendBufferingMaxResponseBodyBytes: "10485760", @@ -304,6 +307,9 @@ func TestProviderBuildConfiguration(t *testing.T) { Sticky: true, Stickiness: &types.Stickiness{ CookieName: "chocolate", + Secure: true, + HTTPOnly: true, + SameSite: "none", }, }, MaxConn: &types.MaxConn{ diff --git a/server/server_loadbalancer.go b/server/server_loadbalancer.go index 192a728a0..27c228742 100644 --- a/server/server_loadbalancer.go +++ b/server/server_loadbalancer.go @@ -123,13 +123,6 @@ func (s *Server) buildLoadBalancer(frontendName string, backendName string, back rr, _ = roundrobin.New(fwd) } - var stickySession *roundrobin.StickySession - var cookieName string - if stickiness := backend.LoadBalancer.Stickiness; stickiness != nil { - cookieName = cookie.GetName(stickiness.CookieName, backendName) - stickySession = roundrobin.NewStickySession(cookieName) - } - lbMethod, err := types.NewLoadBalancerMethod(backend.LoadBalancer) if err != nil { return nil, fmt.Errorf("error loading load balancer method '%+v' for frontend %s: %v", backend.LoadBalancer, frontendName, err) @@ -141,8 +134,10 @@ func (s *Server) buildLoadBalancer(frontendName string, backendName string, back case types.Drr: log.Debug("Creating load-balancer drr") - if stickySession != nil { - log.Debugf("Sticky session with cookie %v", cookieName) + if backend.LoadBalancer.Stickiness != nil { + cookieName := cookie.GetName(backend.LoadBalancer.Stickiness.CookieName, backendName) + + stickySession := newStickySession(cookieName, backend.LoadBalancer.Stickiness) lb, err = roundrobin.NewRebalancer(rr, roundrobin.RebalancerStickySession(stickySession)) if err != nil { @@ -157,16 +152,19 @@ func (s *Server) buildLoadBalancer(frontendName string, backendName string, back case types.Wrr: log.Debug("Creating load-balancer wrr") - if stickySession != nil { - log.Debugf("Sticky session with cookie %v", cookieName) + if backend.LoadBalancer.Stickiness != nil { + cookieName := cookie.GetName(backend.LoadBalancer.Stickiness.CookieName, backendName) + stickySession := newStickySession(cookieName, backend.LoadBalancer.Stickiness) + + option := roundrobin.EnableStickySession(stickySession) if s.accessLoggerMiddleware != nil { - lb, err = roundrobin.New(saveFrontend, roundrobin.EnableStickySession(stickySession)) + lb, err = roundrobin.New(saveFrontend, option) if err != nil { return nil, err } } else { - lb, err = roundrobin.New(fwd, roundrobin.EnableStickySession(stickySession)) + lb, err = roundrobin.New(fwd, option) if err != nil { return nil, err } @@ -185,6 +183,30 @@ func (s *Server) buildLoadBalancer(frontendName string, backendName string, back return lb, nil } +func newStickySession(cookieName string, stickiness *types.Stickiness) *roundrobin.StickySession { + log.Debugf("Sticky session with cookie %v", cookieName) + + opts := roundrobin.CookieOptions{ + HTTPOnly: stickiness.HTTPOnly, + Secure: stickiness.Secure, + SameSite: convertSameSite(stickiness.SameSite), + } + + return roundrobin.NewStickySessionWithOptions(cookieName, opts) +} + +func convertSameSite(sameSite string) http.SameSite { + switch sameSite { + case "none": + return http.SameSiteNoneMode + case "lax": + return http.SameSiteLaxMode + case "strict": + return http.SameSiteStrictMode + default: + return 0 + } +} func (s *Server) configureLBServers(lb healthcheck.BalancerHandler, backend *types.Backend, backendName string) error { for name, srv := range backend.Servers { u, err := url.Parse(srv.URL) diff --git a/templates/consul_catalog.tmpl b/templates/consul_catalog.tmpl index 198797bcd..55a1a6856 100644 --- a/templates/consul_catalog.tmpl +++ b/templates/consul_catalog.tmpl @@ -24,6 +24,9 @@ {{if $loadBalancer.Stickiness }} [backends."backend-{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} diff --git a/templates/docker.tmpl b/templates/docker.tmpl index 60bbcb660..aa8809669 100644 --- a/templates/docker.tmpl +++ b/templates/docker.tmpl @@ -23,6 +23,9 @@ {{if $loadBalancer.Stickiness }} [backends."backend-{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} diff --git a/templates/ecs.tmpl b/templates/ecs.tmpl index 3899f46df..a33e924c5 100644 --- a/templates/ecs.tmpl +++ b/templates/ecs.tmpl @@ -22,6 +22,9 @@ {{if $loadBalancer.Stickiness }} [backends."backend-{{ $serviceName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} diff --git a/templates/kubernetes.tmpl b/templates/kubernetes.tmpl index 961204cdd..e9a80221f 100644 --- a/templates/kubernetes.tmpl +++ b/templates/kubernetes.tmpl @@ -19,6 +19,9 @@ {{if $backend.LoadBalancer.Stickiness }} [backends."{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $backend.LoadBalancer.Stickiness.CookieName }}" + secure = {{ $backend.Stickiness.Secure }} + httpOnly = {{ $backend.Stickiness.HTTPOnly }} + sameSite = "{{ $backend.Stickiness.SameSite }}" {{end}} {{if $backend.MaxConn }} diff --git a/templates/kv.tmpl b/templates/kv.tmpl index b2ffc9273..b9bfccdef 100644 --- a/templates/kv.tmpl +++ b/templates/kv.tmpl @@ -22,6 +22,9 @@ {{if $loadBalancer.Stickiness }} [backends."{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} diff --git a/templates/marathon.tmpl b/templates/marathon.tmpl index 3e95cc5f3..d2986a144 100644 --- a/templates/marathon.tmpl +++ b/templates/marathon.tmpl @@ -25,6 +25,9 @@ {{if $loadBalancer.Stickiness }} [backends."{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} diff --git a/templates/mesos.tmpl b/templates/mesos.tmpl index cd6daaef2..c1ffca973 100644 --- a/templates/mesos.tmpl +++ b/templates/mesos.tmpl @@ -25,6 +25,9 @@ {{if $loadBalancer.Stickiness }} [backends."backend-{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} diff --git a/templates/rancher.tmpl b/templates/rancher.tmpl index 82352db55..31716310c 100644 --- a/templates/rancher.tmpl +++ b/templates/rancher.tmpl @@ -24,6 +24,9 @@ {{if $loadBalancer.Stickiness }} [backends."backend-{{ $backendName }}".loadBalancer.stickiness] cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + secure = {{ $loadBalancer.Stickiness.Secure }} + httpOnly = {{ $loadBalancer.Stickiness.HTTPOnly }} + sameSite = "{{ $loadBalancer.Stickiness.SameSite }}" {{end}} {{end}} diff --git a/types/types.go b/types/types.go index 1435d17a1..04449104d 100644 --- a/types/types.go +++ b/types/types.go @@ -52,6 +52,9 @@ type LoadBalancer struct { // Stickiness holds sticky session configuration. type Stickiness struct { CookieName string `json:"cookieName,omitempty"` + Secure bool `json:"secure,omitempty"` + HTTPOnly bool `json:"httpOnly,omitempty"` + SameSite string `json:"sameSite,omitempty"` } // CircuitBreaker holds circuit breaker configuration. diff --git a/vendor/github.com/vulcand/oxy/roundrobin/stickysessions.go b/vendor/github.com/vulcand/oxy/roundrobin/stickysessions.go index 123fbdfad..7f77faa07 100644 --- a/vendor/github.com/vulcand/oxy/roundrobin/stickysessions.go +++ b/vendor/github.com/vulcand/oxy/roundrobin/stickysessions.go @@ -3,11 +3,26 @@ package roundrobin import ( "net/http" "net/url" + "time" ) +// CookieOptions has all the options one would like to set on the affinity cookie +type CookieOptions struct { + HTTPOnly bool + Secure bool + + Path string + Domain string + Expires time.Time + + MaxAge int + SameSite http.SameSite +} + // StickySession is a mixin for load balancers that implements layer 7 (http cookie) session affinity type StickySession struct { cookieName string + options CookieOptions } // NewStickySession creates a new StickySession @@ -15,6 +30,12 @@ func NewStickySession(cookieName string) *StickySession { return &StickySession{cookieName: cookieName} } +// NewStickySessionWithOptions creates a new StickySession whilst allowing for options to +// shape its affinity cookie such as "httpOnly" or "secure" +func NewStickySessionWithOptions(cookieName string, options CookieOptions) *StickySession { + return &StickySession{cookieName: cookieName, options: options} +} + // GetBackend returns the backend URL stored in the sticky cookie, iff the backend is still in the valid list of servers. func (s *StickySession) GetBackend(req *http.Request, servers []*url.URL) (*url.URL, bool, error) { cookie, err := req.Cookie(s.cookieName) @@ -39,7 +60,24 @@ func (s *StickySession) GetBackend(req *http.Request, servers []*url.URL) (*url. // StickBackend creates and sets the cookie func (s *StickySession) StickBackend(backend *url.URL, w *http.ResponseWriter) { - cookie := &http.Cookie{Name: s.cookieName, Value: backend.String(), Path: "/"} + opt := s.options + + cp := "/" + if opt.Path != "" { + cp = opt.Path + } + + cookie := &http.Cookie{ + Name: s.cookieName, + Value: backend.String(), + Path: cp, + Domain: opt.Domain, + Expires: opt.Expires, + MaxAge: opt.MaxAge, + Secure: opt.Secure, + HttpOnly: opt.HTTPOnly, + SameSite: opt.SameSite, + } http.SetCookie(*w, cookie) }