HttpOnly and Secure flags on the affinity cookie
This commit is contained in:
parent
cad3704efd
commit
d18edd6f77
7 changed files with 68 additions and 11 deletions
4
Gopkg.lock
generated
4
Gopkg.lock
generated
|
@ -1612,7 +1612,7 @@
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:86f14aadf288fe3ad8ac060bcb2b5083cec3829dd883803486ec834d031060c9"
|
digest = "1:d7ace03de79a9cec30e7a55cc16160602760470c5fe031b780dc2d84234d7f5a"
|
||||||
name = "github.com/vulcand/oxy"
|
name = "github.com/vulcand/oxy"
|
||||||
packages = [
|
packages = [
|
||||||
"buffer",
|
"buffer",
|
||||||
|
@ -1625,7 +1625,7 @@
|
||||||
"utils",
|
"utils",
|
||||||
]
|
]
|
||||||
pruneopts = "NUT"
|
pruneopts = "NUT"
|
||||||
revision = "0d102f45103cf49a95b5c6e810e092973cbcb68c"
|
revision = "3d629cff40b7040e0519628e7774ed11a95d9aff"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:ca6bac407fedc14fbeeba861dd33a821ba3a1624c10126ec6003b0a28d4139c5"
|
digest = "1:ca6bac407fedc14fbeeba861dd33a821ba3a1624c10126ec6003b0a28d4139c5"
|
||||||
|
|
|
@ -103,12 +103,18 @@ On subsequent requests, the client is forwarded to the same server.
|
||||||
|
|
||||||
The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`).
|
The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`).
|
||||||
|
|
||||||
|
!!! note "Secure & HTTPOnly flags"
|
||||||
|
|
||||||
|
By default, the affinity cookie is created without those flags. One however can change that through configuration.
|
||||||
|
|
||||||
??? example "Adding Stickiness"
|
??? example "Adding Stickiness"
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[http.services]
|
[http.services]
|
||||||
[http.services.my-service]
|
[http.services.my-service]
|
||||||
[http.services.my-service.LoadBalancer.stickiness]
|
[http.services.my-service.LoadBalancer.stickiness]
|
||||||
|
secureCookie = true
|
||||||
|
httpOnlyCookie = true
|
||||||
```
|
```
|
||||||
|
|
||||||
??? example "Adding Stickiness with a Custom Cookie Name"
|
??? example "Adding Stickiness with a Custom Cookie Name"
|
||||||
|
@ -118,6 +124,8 @@ On subsequent requests, the client is forwarded to the same server.
|
||||||
[http.services.my-service]
|
[http.services.my-service]
|
||||||
[http.services.my-service.LoadBalancer.stickiness]
|
[http.services.my-service.LoadBalancer.stickiness]
|
||||||
cookieName = "my_stickiness_cookie_name"
|
cookieName = "my_stickiness_cookie_name"
|
||||||
|
secureCookie = true
|
||||||
|
httpOnlyCookie = true
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Health Check
|
#### Health Check
|
||||||
|
|
|
@ -97,7 +97,9 @@ type ResponseForwarding struct {
|
||||||
|
|
||||||
// Stickiness holds the stickiness configuration.
|
// Stickiness holds the stickiness configuration.
|
||||||
type Stickiness struct {
|
type Stickiness struct {
|
||||||
CookieName string `json:"cookieName,omitempty" toml:",omitempty"`
|
CookieName string `json:"cookieName,omitempty" toml:",omitempty"`
|
||||||
|
SecureCookie bool `json:"secureCookie,omitempty" toml:",omitempty"`
|
||||||
|
HTTPOnlyCookie bool `json:"httpOnlyCookie,omitempty" toml:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server holds the server configuration.
|
// Server holds the server configuration.
|
||||||
|
|
|
@ -143,6 +143,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
"traefik.http.services.Service0.loadbalancer.server.scheme": "foobar",
|
"traefik.http.services.Service0.loadbalancer.server.scheme": "foobar",
|
||||||
"traefik.http.services.Service0.loadbalancer.server.port": "8080",
|
"traefik.http.services.Service0.loadbalancer.server.port": "8080",
|
||||||
"traefik.http.services.Service0.loadbalancer.stickiness.cookiename": "foobar",
|
"traefik.http.services.Service0.loadbalancer.stickiness.cookiename": "foobar",
|
||||||
|
"traefik.http.services.Service0.loadbalancer.stickiness.securecookie": "true",
|
||||||
"traefik.http.services.Service1.loadbalancer.healthcheck.headers.name0": "foobar",
|
"traefik.http.services.Service1.loadbalancer.healthcheck.headers.name0": "foobar",
|
||||||
"traefik.http.services.Service1.loadbalancer.healthcheck.headers.name1": "foobar",
|
"traefik.http.services.Service1.loadbalancer.healthcheck.headers.name1": "foobar",
|
||||||
"traefik.http.services.Service1.loadbalancer.healthcheck.hostname": "foobar",
|
"traefik.http.services.Service1.loadbalancer.healthcheck.hostname": "foobar",
|
||||||
|
@ -505,7 +506,9 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
"Service0": {
|
"Service0": {
|
||||||
LoadBalancer: &config.LoadBalancerService{
|
LoadBalancer: &config.LoadBalancerService{
|
||||||
Stickiness: &config.Stickiness{
|
Stickiness: &config.Stickiness{
|
||||||
CookieName: "foobar",
|
CookieName: "foobar",
|
||||||
|
SecureCookie: true,
|
||||||
|
HTTPOnlyCookie: false,
|
||||||
},
|
},
|
||||||
Servers: []config.Server{
|
Servers: []config.Server{
|
||||||
{
|
{
|
||||||
|
@ -897,7 +900,8 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
"Service0": {
|
"Service0": {
|
||||||
LoadBalancer: &config.LoadBalancerService{
|
LoadBalancer: &config.LoadBalancerService{
|
||||||
Stickiness: &config.Stickiness{
|
Stickiness: &config.Stickiness{
|
||||||
CookieName: "foobar",
|
CookieName: "foobar",
|
||||||
|
HTTPOnlyCookie: true,
|
||||||
},
|
},
|
||||||
Servers: []config.Server{
|
Servers: []config.Server{
|
||||||
{
|
{
|
||||||
|
@ -1086,6 +1090,8 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
"traefik.HTTP.Services.Service0.LoadBalancer.server.Port": "8080",
|
"traefik.HTTP.Services.Service0.LoadBalancer.server.Port": "8080",
|
||||||
"traefik.HTTP.Services.Service0.LoadBalancer.server.Scheme": "foobar",
|
"traefik.HTTP.Services.Service0.LoadBalancer.server.Scheme": "foobar",
|
||||||
"traefik.HTTP.Services.Service0.LoadBalancer.Stickiness.CookieName": "foobar",
|
"traefik.HTTP.Services.Service0.LoadBalancer.Stickiness.CookieName": "foobar",
|
||||||
|
"traefik.HTTP.Services.Service0.LoadBalancer.Stickiness.HTTPOnlyCookie": "true",
|
||||||
|
"traefik.HTTP.Services.Service0.LoadBalancer.Stickiness.SecureCookie": "false",
|
||||||
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Headers.name0": "foobar",
|
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Headers.name0": "foobar",
|
||||||
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Headers.name1": "foobar",
|
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Headers.name1": "foobar",
|
||||||
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Hostname": "foobar",
|
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Hostname": "foobar",
|
||||||
|
|
|
@ -192,7 +192,8 @@ func (m *Manager) getLoadBalancer(ctx context.Context, serviceName string, servi
|
||||||
var cookieName string
|
var cookieName string
|
||||||
if stickiness := service.Stickiness; stickiness != nil {
|
if stickiness := service.Stickiness; stickiness != nil {
|
||||||
cookieName = cookie.GetName(stickiness.CookieName, serviceName)
|
cookieName = cookie.GetName(stickiness.CookieName, serviceName)
|
||||||
options = append(options, roundrobin.EnableStickySession(roundrobin.NewStickySession(cookieName)))
|
opts := roundrobin.CookieOptions{HTTPOnly: stickiness.HTTPOnlyCookie, Secure: stickiness.SecureCookie}
|
||||||
|
options = append(options, roundrobin.EnableStickySession(roundrobin.NewStickySessionWithOptions(cookieName, opts)))
|
||||||
logger.Debugf("Sticky session cookie name: %v", cookieName)
|
logger.Debugf("Sticky session cookie name: %v", cookieName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containous/traefik/pkg/config"
|
"github.com/containous/traefik/pkg/config"
|
||||||
|
@ -103,8 +104,10 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
||||||
defer serverPassHostFalse.Close()
|
defer serverPassHostFalse.Close()
|
||||||
|
|
||||||
type ExpectedResult struct {
|
type ExpectedResult struct {
|
||||||
StatusCode int
|
StatusCode int
|
||||||
XFrom string
|
XFrom string
|
||||||
|
SecureCookie bool
|
||||||
|
HTTPOnlyCookie bool
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
@ -192,6 +195,26 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Sticky Cookie's options set correctly",
|
||||||
|
serviceName: "test",
|
||||||
|
service: &config.LoadBalancerService{
|
||||||
|
Stickiness: &config.Stickiness{HTTPOnlyCookie: true, SecureCookie: true},
|
||||||
|
Servers: []config.Server{
|
||||||
|
{
|
||||||
|
URL: server1.URL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []ExpectedResult{
|
||||||
|
{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
XFrom: "first",
|
||||||
|
SecureCookie: true,
|
||||||
|
HTTPOnlyCookie: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "PassHost passes the host instead of the IP",
|
desc: "PassHost passes the host instead of the IP",
|
||||||
serviceName: "test",
|
serviceName: "test",
|
||||||
|
@ -249,8 +272,11 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
||||||
assert.Equal(t, expected.StatusCode, recorder.Code)
|
assert.Equal(t, expected.StatusCode, recorder.Code)
|
||||||
assert.Equal(t, expected.XFrom, recorder.Header().Get("X-From"))
|
assert.Equal(t, expected.XFrom, recorder.Header().Get("X-From"))
|
||||||
|
|
||||||
if len(recorder.Header().Get("Set-Cookie")) > 0 {
|
cookieHeader := recorder.Header().Get("Set-Cookie")
|
||||||
req.Header.Set("Cookie", recorder.Header().Get("Set-Cookie"))
|
if len(cookieHeader) > 0 {
|
||||||
|
req.Header.Set("Cookie", cookieHeader)
|
||||||
|
assert.Equal(t, expected.SecureCookie, strings.Contains(cookieHeader, "Secure"))
|
||||||
|
assert.Equal(t, expected.HTTPOnlyCookie, strings.Contains(cookieHeader, "HttpOnly"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
16
vendor/github.com/vulcand/oxy/roundrobin/stickysessions.go
generated
vendored
16
vendor/github.com/vulcand/oxy/roundrobin/stickysessions.go
generated
vendored
|
@ -8,6 +8,13 @@ import (
|
||||||
// StickySession is a mixin for load balancers that implements layer 7 (http cookie) session affinity
|
// StickySession is a mixin for load balancers that implements layer 7 (http cookie) session affinity
|
||||||
type StickySession struct {
|
type StickySession struct {
|
||||||
cookieName string
|
cookieName string
|
||||||
|
options CookieOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// CookieOptions has all the options one would like to set on the affinity cookie
|
||||||
|
type CookieOptions struct {
|
||||||
|
HTTPOnly bool
|
||||||
|
Secure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStickySession creates a new StickySession
|
// NewStickySession creates a new StickySession
|
||||||
|
@ -15,6 +22,12 @@ func NewStickySession(cookieName string) *StickySession {
|
||||||
return &StickySession{cookieName: cookieName}
|
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.
|
// 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) {
|
func (s *StickySession) GetBackend(req *http.Request, servers []*url.URL) (*url.URL, bool, error) {
|
||||||
cookie, err := req.Cookie(s.cookieName)
|
cookie, err := req.Cookie(s.cookieName)
|
||||||
|
@ -39,7 +52,8 @@ func (s *StickySession) GetBackend(req *http.Request, servers []*url.URL) (*url.
|
||||||
|
|
||||||
// StickBackend creates and sets the cookie
|
// StickBackend creates and sets the cookie
|
||||||
func (s *StickySession) StickBackend(backend *url.URL, w *http.ResponseWriter) {
|
func (s *StickySession) StickBackend(backend *url.URL, w *http.ResponseWriter) {
|
||||||
cookie := &http.Cookie{Name: s.cookieName, Value: backend.String(), Path: "/"}
|
opt := s.options
|
||||||
|
cookie := &http.Cookie{Name: s.cookieName, Value: backend.String(), Path: "/", HttpOnly: opt.HTTPOnly, Secure: opt.Secure}
|
||||||
http.SetCookie(*w, cookie)
|
http.SetCookie(*w, cookie)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue