2017-02-12 23:13:39 +00:00
/ *
Copyright 2016 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package sessionaffinity
import (
"regexp"
2021-08-21 20:42:00 +00:00
networking "k8s.io/api/networking/v1"
2020-08-08 23:31:02 +00:00
"k8s.io/klog/v2"
2017-02-12 23:13:39 +00:00
2017-11-07 22:02:12 +00:00
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
2017-11-08 20:58:57 +00:00
"k8s.io/ingress-nginx/internal/ingress/resolver"
2017-02-12 23:13:39 +00:00
)
const (
2021-07-29 21:23:19 +00:00
annotationAffinityType = "affinity"
annotationAffinityMode = "affinity-mode"
annotationAffinityCanaryBehavior = "affinity-canary-behavior"
2017-02-12 23:13:39 +00:00
// If a cookie with this name exists,
// its value is used as an index into the list of available backends.
2017-11-08 20:58:57 +00:00
annotationAffinityCookieName = "session-cookie-name"
defaultAffinityCookieName = "INGRESSCOOKIE"
2021-09-01 22:23:40 +00:00
// This is used to force the Secure flag on the cookie even if the
// incoming request is not secured. (https://github.com/kubernetes/ingress-nginx/issues/6812)
annotationAffinityCookieSecure = "session-cookie-secure"
2018-11-02 09:05:38 +00:00
// This is used to control the cookie expires, its value is a number of seconds until the
// cookie expires
2018-10-29 07:21:10 +00:00
annotationAffinityCookieExpires = "session-cookie-expires"
// This is used to control the cookie expires, its value is a number of seconds until the
// cookie expires
annotationAffinityCookieMaxAge = "session-cookie-max-age"
2018-11-19 14:15:24 +00:00
// This is used to control the cookie path when use-regex is set to true
annotationAffinityCookiePath = "session-cookie-path"
2019-04-29 13:20:30 +00:00
2022-09-28 14:28:37 +00:00
// This is used to control the cookie Domain
annotationAffinityCookieDomain = "session-cookie-domain"
2020-01-22 20:19:16 +00:00
// This is used to control the SameSite attribute of the cookie
annotationAffinityCookieSameSite = "session-cookie-samesite"
// This is used to control whether SameSite=None should be conditionally applied based on the User-Agent
annotationAffinityCookieConditionalSameSiteNone = "session-cookie-conditional-samesite-none"
2019-04-29 13:20:30 +00:00
// This is used to control the cookie change after request failure
annotationAffinityCookieChangeOnFailure = "session-cookie-change-on-failure"
2023-08-31 07:36:48 +00:00
cookieAffinity = "cookie"
2017-02-12 23:13:39 +00:00
)
2023-07-22 03:32:07 +00:00
var sessionAffinityAnnotations = parser . Annotation {
Group : "affinity" ,
Annotations : parser . AnnotationFields {
annotationAffinityType : {
2023-08-31 07:36:48 +00:00
Validator : parser . ValidateOptions ( [ ] string { cookieAffinity } , true , true ) ,
2023-07-22 03:32:07 +00:00
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskLow ,
Documentation : ` This annotation enables and sets the affinity type in all Upstreams of an Ingress. This way, a request will always be directed to the same upstream server. The only affinity type available for NGINX is cookie ` ,
} ,
annotationAffinityMode : {
Validator : parser . ValidateOptions ( [ ] string { "balanced" , "persistent" } , true , true ) ,
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskMedium ,
Documentation : ` This annotation defines the stickiness of a session .
Setting this to balanced ( default ) will redistribute some sessions if a deployment gets scaled up , therefore rebalancing the load on the servers .
Setting this to persistent will not rebalance sessions to new servers , therefore providing maximum stickiness . ` ,
} ,
annotationAffinityCanaryBehavior : {
Validator : parser . ValidateOptions ( [ ] string { "sticky" , "legacy" } , true , true ) ,
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskLow ,
Documentation : ` This annotation defines the behavior of canaries when session affinity is enabled .
Setting this to sticky ( default ) will ensure that users that were served by canaries , will continue to be served by canaries .
Setting this to legacy will restore original canary behavior , when session affinity was ignored . ` ,
} ,
annotationAffinityCookieName : {
2023-08-31 07:36:48 +00:00
Validator : parser . ValidateRegex ( parser . BasicCharsRegex , true ) ,
2023-07-22 03:32:07 +00:00
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskMedium ,
Documentation : ` This annotation allows to specify the name of the cookie that will be used to route the requests ` ,
} ,
annotationAffinityCookieSecure : {
Validator : parser . ValidateBool ,
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskLow ,
Documentation : ` This annotation set the cookie as secure regardless the protocol of the incoming request ` ,
} ,
annotationAffinityCookieExpires : {
2023-08-31 07:36:48 +00:00
Validator : parser . ValidateRegex ( affinityCookieExpiresRegex , true ) ,
2023-07-22 03:32:07 +00:00
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskMedium ,
Documentation : ` This annotation is a legacy version of "session-cookie-max-age" for compatibility with older browsers, generates an "Expires" cookie directive by adding the seconds to the current date ` ,
} ,
annotationAffinityCookieMaxAge : {
2023-08-31 07:36:48 +00:00
Validator : parser . ValidateRegex ( affinityCookieExpiresRegex , false ) ,
2023-07-22 03:32:07 +00:00
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskMedium ,
Documentation : ` This annotation sets the time until the cookie expires ` ,
} ,
annotationAffinityCookiePath : {
2023-08-31 07:36:48 +00:00
Validator : parser . ValidateRegex ( parser . URLIsValidRegex , true ) ,
2023-07-22 03:32:07 +00:00
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskMedium ,
Documentation : ` This annotation defines the Path that will be set on the cookie (required if your Ingress paths use regular expressions) ` ,
} ,
annotationAffinityCookieDomain : {
2023-08-31 07:36:48 +00:00
Validator : parser . ValidateRegex ( parser . BasicCharsRegex , true ) ,
2023-07-22 03:32:07 +00:00
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskMedium ,
Documentation : ` This annotation defines the Domain attribute of the sticky cookie. ` ,
} ,
annotationAffinityCookieSameSite : {
Validator : parser . ValidateOptions ( [ ] string { "None" , "Lax" , "Strict" } , false , true ) ,
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskLow ,
Documentation : ` This annotation is used to apply a SameSite attribute to the sticky cookie .
Browser accepted values are None , Lax , and Strict ` ,
} ,
annotationAffinityCookieConditionalSameSiteNone : {
Validator : parser . ValidateBool ,
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskLow ,
Documentation : ` This annotation is used to omit SameSite=None from browsers with SameSite attribute incompatibilities ` ,
} ,
annotationAffinityCookieChangeOnFailure : {
Validator : parser . ValidateBool ,
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskLow ,
Documentation : ` This annotation , when set to false will send request to upstream pointed by sticky cookie even if previous attempt failed .
When set to true and previous attempt failed , sticky cookie will be changed to point to another upstream . ` ,
} ,
} ,
}
2023-08-31 07:36:48 +00:00
var affinityCookieExpiresRegex = regexp . MustCompile ( ` (^0|-?[1-9]\d*$) ` )
2017-02-12 23:13:39 +00:00
2017-11-07 16:36:51 +00:00
// Config describes the per ingress session affinity config
type Config struct {
2017-02-12 23:13:39 +00:00
// The type of affinity that will be used
2017-11-07 16:36:51 +00:00
Type string ` json:"type" `
2019-08-30 09:40:29 +00:00
// The affinity mode, i.e. how sticky a session is
Mode string ` json:"mode" `
2021-07-29 21:23:19 +00:00
// Affinity behavior for canaries (sticky or legacy)
CanaryBehavior string ` json:"canaryBehavior" `
2017-11-07 16:36:51 +00:00
Cookie
2017-02-12 23:13:39 +00:00
}
2017-11-07 16:36:51 +00:00
// Cookie describes the Config of cookie type affinity
type Cookie struct {
2017-02-12 23:13:39 +00:00
// The name of the cookie that will be used in case of cookie affinity type.
Name string ` json:"name" `
2018-11-02 09:05:38 +00:00
// The time duration to control cookie expires
2018-10-29 07:21:10 +00:00
Expires string ` json:"expires" `
// The number of seconds until the cookie expires
MaxAge string ` json:"maxage" `
2018-11-19 14:15:24 +00:00
// The path that a cookie will be set on
Path string ` json:"path" `
2022-09-28 14:28:37 +00:00
// The domain that a cookie will be set on
Domain string ` json:"domain" `
2019-04-29 13:20:30 +00:00
// Flag that allows cookie regeneration on request failure
2019-06-06 15:02:51 +00:00
ChangeOnFailure bool ` json:"changeonfailure" `
2021-09-01 22:23:40 +00:00
// Secure flag to be set
Secure bool ` json:"secure" `
2020-01-22 20:19:16 +00:00
// SameSite attribute value
SameSite string ` json:"samesite" `
// Flag that conditionally applies SameSite=None attribute on cookie if user agent accepts it.
ConditionalSameSiteNone bool ` json:"conditional-samesite-none" `
2017-02-12 23:13:39 +00:00
}
2023-08-31 07:36:48 +00:00
type affinity struct {
r resolver . Resolver
annotationConfig parser . Annotation
}
2017-11-07 16:36:51 +00:00
// cookieAffinityParse gets the annotation values related to Cookie Affinity
2017-02-12 23:13:39 +00:00
// It also sets default values when no value or incorrect value is found
2019-06-09 22:49:59 +00:00
func ( a affinity ) cookieAffinityParse ( ing * networking . Ingress ) * Cookie {
2018-11-30 22:56:11 +00:00
var err error
2018-10-29 07:21:10 +00:00
cookie := & Cookie { }
2017-02-12 23:13:39 +00:00
2023-07-22 03:32:07 +00:00
cookie . Name , err = parser . GetStringAnnotation ( annotationAffinityCookieName , ing , a . annotationConfig . Annotations )
2018-12-02 18:35:12 +00:00
if err != nil {
2021-03-26 06:14:27 +00:00
klog . V ( 3 ) . InfoS ( "Invalid or no annotation value found. Ignoring" , "ingress" , klog . KObj ( ing ) , "annotation" , annotationAffinityCookieName , "default" , defaultAffinityCookieName )
2018-11-30 22:56:11 +00:00
cookie . Name = defaultAffinityCookieName
2017-02-12 23:13:39 +00:00
}
2023-07-22 03:32:07 +00:00
cookie . Expires , err = parser . GetStringAnnotation ( annotationAffinityCookieExpires , ing , a . annotationConfig . Annotations )
2018-11-30 22:56:11 +00:00
if err != nil || ! affinityCookieExpiresRegex . MatchString ( cookie . Expires ) {
2020-10-26 19:08:55 +00:00
klog . V ( 3 ) . InfoS ( "Invalid or no annotation value found. Ignoring" , "ingress" , klog . KObj ( ing ) , "annotation" , annotationAffinityCookieExpires )
2018-11-30 22:56:11 +00:00
cookie . Expires = ""
2018-10-29 07:21:10 +00:00
}
2017-02-12 23:13:39 +00:00
2023-07-22 03:32:07 +00:00
cookie . MaxAge , err = parser . GetStringAnnotation ( annotationAffinityCookieMaxAge , ing , a . annotationConfig . Annotations )
2018-11-30 22:56:11 +00:00
if err != nil || ! affinityCookieExpiresRegex . MatchString ( cookie . MaxAge ) {
2020-10-26 19:08:55 +00:00
klog . V ( 3 ) . InfoS ( "Invalid or no annotation value found. Ignoring" , "ingress" , klog . KObj ( ing ) , "annotation" , annotationAffinityCookieMaxAge )
2018-11-30 22:56:11 +00:00
cookie . MaxAge = ""
2017-02-12 23:13:39 +00:00
}
2018-11-19 14:15:24 +00:00
2023-07-22 03:32:07 +00:00
cookie . Path , err = parser . GetStringAnnotation ( annotationAffinityCookiePath , ing , a . annotationConfig . Annotations )
2018-12-02 18:35:12 +00:00
if err != nil {
2021-03-26 06:14:27 +00:00
klog . V ( 3 ) . InfoS ( "Invalid or no annotation value found. Ignoring" , "ingress" , klog . KObj ( ing ) , "annotation" , annotationAffinityCookiePath )
2018-11-19 14:15:24 +00:00
}
2018-11-30 22:56:11 +00:00
2023-07-22 03:32:07 +00:00
cookie . Domain , err = parser . GetStringAnnotation ( annotationAffinityCookieDomain , ing , a . annotationConfig . Annotations )
2022-09-28 14:28:37 +00:00
if err != nil {
klog . V ( 3 ) . InfoS ( "Invalid or no annotation value found. Ignoring" , "ingress" , klog . KObj ( ing ) , "annotation" , annotationAffinityCookieDomain )
}
2023-07-22 03:32:07 +00:00
cookie . SameSite , err = parser . GetStringAnnotation ( annotationAffinityCookieSameSite , ing , a . annotationConfig . Annotations )
2020-01-22 20:19:16 +00:00
if err != nil {
2020-10-26 19:08:55 +00:00
klog . V ( 3 ) . InfoS ( "Invalid or no annotation value found. Ignoring" , "ingress" , klog . KObj ( ing ) , "annotation" , annotationAffinityCookieSameSite )
2020-01-22 20:19:16 +00:00
}
2023-07-22 03:32:07 +00:00
cookie . Secure , err = parser . GetBoolAnnotation ( annotationAffinityCookieSecure , ing , a . annotationConfig . Annotations )
2021-09-01 22:23:40 +00:00
if err != nil {
klog . V ( 3 ) . InfoS ( "Invalid or no annotation value found. Ignoring" , "ingress" , klog . KObj ( ing ) , "annotation" , annotationAffinityCookieSecure )
}
2023-07-22 03:32:07 +00:00
cookie . ConditionalSameSiteNone , err = parser . GetBoolAnnotation ( annotationAffinityCookieConditionalSameSiteNone , ing , a . annotationConfig . Annotations )
2020-01-22 20:19:16 +00:00
if err != nil {
2020-10-26 19:08:55 +00:00
klog . V ( 3 ) . InfoS ( "Invalid or no annotation value found. Ignoring" , "ingress" , klog . KObj ( ing ) , "annotation" , annotationAffinityCookieConditionalSameSiteNone )
2020-01-22 20:19:16 +00:00
}
2023-07-22 03:32:07 +00:00
cookie . ChangeOnFailure , err = parser . GetBoolAnnotation ( annotationAffinityCookieChangeOnFailure , ing , a . annotationConfig . Annotations )
2019-06-09 22:49:59 +00:00
if err != nil {
2020-10-26 19:08:55 +00:00
klog . V ( 3 ) . InfoS ( "Invalid or no annotation value found. Ignoring" , "ingress" , klog . KObj ( ing ) , "annotation" , annotationAffinityCookieChangeOnFailure )
2019-06-09 22:49:59 +00:00
}
2018-10-29 07:21:10 +00:00
return cookie
2017-02-12 23:13:39 +00:00
}
// NewParser creates a new Affinity annotation parser
2017-11-08 20:58:57 +00:00
func NewParser ( r resolver . Resolver ) parser . IngressAnnotation {
2023-07-22 03:32:07 +00:00
return affinity {
r : r ,
annotationConfig : sessionAffinityAnnotations ,
}
2017-02-12 23:13:39 +00:00
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure the affinity directives
2019-06-09 22:49:59 +00:00
func ( a affinity ) Parse ( ing * networking . Ingress ) ( interface { } , error ) {
2017-11-07 16:36:51 +00:00
cookie := & Cookie { }
2017-02-12 23:13:39 +00:00
// Check the type of affinity that will be used
2023-07-22 03:32:07 +00:00
at , err := parser . GetStringAnnotation ( annotationAffinityType , ing , a . annotationConfig . Annotations )
2017-02-12 23:13:39 +00:00
if err != nil {
at = ""
}
2017-02-14 10:49:10 +00:00
2020-11-27 17:26:53 +00:00
// Check the affinity mode that will be used
2023-07-22 03:32:07 +00:00
am , err := parser . GetStringAnnotation ( annotationAffinityMode , ing , a . annotationConfig . Annotations )
2019-08-30 09:40:29 +00:00
if err != nil {
am = ""
}
2023-07-22 03:32:07 +00:00
cb , err := parser . GetStringAnnotation ( annotationAffinityCanaryBehavior , ing , a . annotationConfig . Annotations )
2021-07-29 21:23:19 +00:00
if err != nil {
cb = ""
}
2017-02-12 23:13:39 +00:00
switch at {
2023-08-31 07:36:48 +00:00
case cookieAffinity :
2017-11-08 20:58:57 +00:00
cookie = a . cookieAffinityParse ( ing )
2017-02-12 23:13:39 +00:00
default :
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "No default affinity found" , "ingress" , ing . Name )
2017-02-12 23:13:39 +00:00
}
2017-11-07 16:36:51 +00:00
return & Config {
2021-07-29 21:23:19 +00:00
Type : at ,
Mode : am ,
CanaryBehavior : cb ,
Cookie : * cookie ,
2017-11-07 16:36:51 +00:00
} , nil
2017-02-12 23:13:39 +00:00
}
2023-07-22 03:32:07 +00:00
func ( a affinity ) GetDocumentation ( ) parser . AnnotationFields {
return a . annotationConfig . Annotations
}
func ( a affinity ) Validate ( anns map [ string ] string ) error {
maxrisk := parser . StringRiskToRisk ( a . r . GetSecurityConfiguration ( ) . AnnotationsRiskLevel )
return parser . CheckAnnotationRisk ( anns , maxrisk , sessionAffinityAnnotations . Annotations )
}