diff --git a/internal/ingress/annotations/sessionaffinity/main.go b/internal/ingress/annotations/sessionaffinity/main.go index 49de25d33d..acafa3111e 100644 --- a/internal/ingress/annotations/sessionaffinity/main.go +++ b/internal/ingress/annotations/sessionaffinity/main.go @@ -39,6 +39,8 @@ const ( // one isn't supplied and affinity is set to "cookie". annotationAffinityCookieHash = "session-cookie-hash" defaultAffinityCookieHash = "md5" + + annotationAffinityCookieExpires = "session-cookie-expires" ) var ( @@ -58,6 +60,8 @@ type Cookie struct { Name string `json:"name"` // The hash that will be used to encode the cookie in case of cookie affinity type Hash string `json:"hash"` + // The time that will be used in case of cookie expire. + Expires string `json:"expires"` } // cookieAffinityParse gets the annotation values related to Cookie Affinity @@ -77,9 +81,12 @@ func (a affinity) cookieAffinityParse(ing *extensions.Ingress) *Cookie { sh = defaultAffinityCookieHash } + se, err := parser.GetStringAnnotation(annotationAffinityCookieExpires, ing) + return &Cookie{ - Name: sn, - Hash: sh, + Name: sn, + Hash: sh, + Expires: se, } } diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index ea9c06a61a..4225eba898 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -533,6 +533,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([] if anns.SessionAffinity.Type == "cookie" { ups.SessionAffinity.CookieSessionAffinity.Name = anns.SessionAffinity.Cookie.Name ups.SessionAffinity.CookieSessionAffinity.Hash = anns.SessionAffinity.Cookie.Hash + ups.SessionAffinity.CookieSessionAffinity.Expires = anns.SessionAffinity.Cookie.Expires locs := ups.SessionAffinity.CookieSessionAffinity.Locations if _, ok := locs[host]; !ok { diff --git a/internal/ingress/types.go b/internal/ingress/types.go index 57f9ac6b02..6221bbb0ec 100644 --- a/internal/ingress/types.go +++ b/internal/ingress/types.go @@ -120,6 +120,7 @@ type SessionAffinityConfig struct { type CookieSessionAffinity struct { Name string `json:"name"` Hash string `json:"hash"` + Expires string `json:"expires"` Locations map[string][]string `json:"locations,omitempty"` } diff --git a/rootfs/etc/nginx/lua/balancer/sticky.lua b/rootfs/etc/nginx/lua/balancer/sticky.lua index 4d46841466..2e2d220b17 100644 --- a/rootfs/etc/nginx/lua/balancer/sticky.lua +++ b/rootfs/etc/nginx/lua/balancer/sticky.lua @@ -15,6 +15,7 @@ function _M.new(self, backend) local o = { instance = self.factory:new(nodes), cookie_name = backend["sessionAffinityConfig"]["cookieSessionAffinity"]["name"] or "route", + expires = backend["sessionAffinityConfig"]["cookieSessionAffinity"]["expires"], digest_func = digest_func, } setmetatable(o, self) @@ -37,14 +38,20 @@ local function set_cookie(self, value) ngx.log(ngx.ERR, err) end - local ok - ok, err = cookie:set({ + local cookie_data={ key = self.cookie_name, value = value, path = ngx.var.location_path, domain = ngx.var.host, httponly = true, - }) + } + + if self.expires ~= "" then + cookie_data.expires = self.expires + end + + local ok + ok, err = cookie:set(cookie_data) if not ok then ngx.log(ngx.ERR, err) end diff --git a/rootfs/etc/nginx/lua/test/balancer/sticky_test.lua b/rootfs/etc/nginx/lua/test/balancer/sticky_test.lua index d5c402236e..d844f6e8a6 100644 --- a/rootfs/etc/nginx/lua/test/balancer/sticky_test.lua +++ b/rootfs/etc/nginx/lua/test/balancer/sticky_test.lua @@ -33,7 +33,7 @@ local function get_test_backend() }, sessionAffinityConfig = { name = "cookie", - cookieSessionAffinity = { name = "test_name", hash = "sha1" } + cookieSessionAffinity = { name = "test_name", hash = "sha1", expires = "1h" } }, } end diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index 8895068609..6f20712ba9 100644 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -411,7 +411,7 @@ http { {{ range $upstream := $backends }} {{ if eq $upstream.SessionAffinity.AffinityType "cookie" }} upstream sticky-{{ $upstream.Name }} { - sticky hash={{ $upstream.SessionAffinity.CookieSessionAffinity.Hash }} name={{ $upstream.SessionAffinity.CookieSessionAffinity.Name }}{{if eq (len $upstream.SessionAffinity.CookieSessionAffinity.Locations) 1 }}{{ range $locationName, $locationPaths := $upstream.SessionAffinity.CookieSessionAffinity.Locations }}{{ if eq (len $locationPaths) 1 }} path={{ index $locationPaths 0 }}{{ end }}{{ end }}{{ end }} httponly; + sticky hash={{ $upstream.SessionAffinity.CookieSessionAffinity.Hash }} name={{ $upstream.SessionAffinity.CookieSessionAffinity.Name }}{{ if eq (len $upstream.SessionAffinity.CookieSessionAffinity.Locations) 1 }}{{ range $locationName, $locationPaths := $upstream.SessionAffinity.CookieSessionAffinity.Locations }}{{ if eq (len $locationPaths) 1 }} path={{ index $locationPaths 0 }}{{ end }}{{ end }}{{ end }}{{ if $upstream.SessionAffinity.CookieSessionAffinity.Expires }} expires={{ $upstream.SessionAffinity.CookieSessionAffinity.Expires }}{{ end }} httponly; {{ if (gt $cfg.UpstreamKeepaliveConnections 0) }} keepalive {{ $cfg.UpstreamKeepaliveConnections }}; diff --git a/test/e2e/annotations/affinity.go b/test/e2e/annotations/affinity.go index 1db4be4f8f..fd30894b97 100644 --- a/test/e2e/annotations/affinity.go +++ b/test/e2e/annotations/affinity.go @@ -265,4 +265,57 @@ var _ = framework.IngressNginxDescribe("Annotations - Affinity", func() { Expect(resp.StatusCode).Should(Equal(http.StatusOK)) Expect(resp.Header.Get("Set-Cookie")).Should(ContainSubstring("Path=/;")) }) + + It("should set expires time on the generated cookie", func() { + host := "example.com" + + ing, err := f.EnsureIngress(&v1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: host, + Namespace: f.IngressController.Namespace, + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/affinity": "cookie", + "nginx.ingress.kubernetes.io/session-cookie-name": "SERVERID", + "nginx.ingress.kubernetes.io/session-cookie-expires": "1h", + }, + }, + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ + { + Host: host, + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ + { + Path: "/", + Backend: v1beta1.IngressBackend{ + ServiceName: "http-svc", + ServicePort: intstr.FromInt(80), + }, + }, + }, + }, + }, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(ing).NotTo(BeNil()) + + err = f.WaitForNginxServer(host, + func(server string) bool { + return strings.Contains(server, "proxy_pass http://sticky-"+f.IngressController.Namespace+"-http-svc-80;") + }) + Expect(err).NotTo(HaveOccurred()) + + resp, _, errs := gorequest.New(). + Get(f.IngressController.HTTPURL). + Set("Host", host). + End() + + Expect(len(errs)).Should(BeNumerically("==", 0)) + Expect(resp.StatusCode).Should(Equal(http.StatusOK)) + Expect(resp.Header.Get("Set-Cookie")).Should(ContainSubstring("Expires=")) + }) })