Implement annotation validation (#9673)

* Add validation to all annotations

* Add annotation validation for fcgi

* Fix reviews and fcgi e2e

* Add flag to disable cross namespace validation

* Add risk, flag for validation, tests

* Add missing formating

* Enable validation by default on tests

* Test validation flag

* remove ajp from list

* Finalize validation changes

* Add validations to CI

* Update helm docs

* Fix code review

* Use a better name for annotation risk
This commit is contained in:
Ricardo Katz 2023-07-22 00:32:07 -03:00 committed by GitHub
parent 86c00a2310
commit c5f348ea2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
109 changed files with 4320 additions and 586 deletions

View file

@ -65,6 +65,90 @@ const (
annotationAffinityCookieChangeOnFailure = "session-cookie-change-on-failure"
)
var sessionAffinityAnnotations = parser.Annotation{
Group: "affinity",
Annotations: parser.AnnotationFields{
annotationAffinityType: {
Validator: parser.ValidateOptions([]string{"cookie"}, true, true),
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: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
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: {
Validator: parser.ValidateRegex(*affinityCookieExpiresRegex, true),
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: {
Validator: parser.ValidateRegex(*affinityCookieExpiresRegex, false),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation sets the time until the cookie expires`,
},
annotationAffinityCookiePath: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
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: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
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.`,
},
},
}
var (
affinityCookieExpiresRegex = regexp.MustCompile(`(^0|-?[1-9]\d*$)`)
)
@ -109,50 +193,50 @@ func (a affinity) cookieAffinityParse(ing *networking.Ingress) *Cookie {
cookie := &Cookie{}
cookie.Name, err = parser.GetStringAnnotation(annotationAffinityCookieName, ing)
cookie.Name, err = parser.GetStringAnnotation(annotationAffinityCookieName, ing, a.annotationConfig.Annotations)
if err != nil {
klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieName, "default", defaultAffinityCookieName)
cookie.Name = defaultAffinityCookieName
}
cookie.Expires, err = parser.GetStringAnnotation(annotationAffinityCookieExpires, ing)
cookie.Expires, err = parser.GetStringAnnotation(annotationAffinityCookieExpires, ing, a.annotationConfig.Annotations)
if err != nil || !affinityCookieExpiresRegex.MatchString(cookie.Expires) {
klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieExpires)
cookie.Expires = ""
}
cookie.MaxAge, err = parser.GetStringAnnotation(annotationAffinityCookieMaxAge, ing)
cookie.MaxAge, err = parser.GetStringAnnotation(annotationAffinityCookieMaxAge, ing, a.annotationConfig.Annotations)
if err != nil || !affinityCookieExpiresRegex.MatchString(cookie.MaxAge) {
klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieMaxAge)
cookie.MaxAge = ""
}
cookie.Path, err = parser.GetStringAnnotation(annotationAffinityCookiePath, ing)
cookie.Path, err = parser.GetStringAnnotation(annotationAffinityCookiePath, ing, a.annotationConfig.Annotations)
if err != nil {
klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookiePath)
}
cookie.Domain, err = parser.GetStringAnnotation(annotationAffinityCookieDomain, ing)
cookie.Domain, err = parser.GetStringAnnotation(annotationAffinityCookieDomain, ing, a.annotationConfig.Annotations)
if err != nil {
klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieDomain)
}
cookie.SameSite, err = parser.GetStringAnnotation(annotationAffinityCookieSameSite, ing)
cookie.SameSite, err = parser.GetStringAnnotation(annotationAffinityCookieSameSite, ing, a.annotationConfig.Annotations)
if err != nil {
klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieSameSite)
}
cookie.Secure, err = parser.GetBoolAnnotation(annotationAffinityCookieSecure, ing)
cookie.Secure, err = parser.GetBoolAnnotation(annotationAffinityCookieSecure, ing, a.annotationConfig.Annotations)
if err != nil {
klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieSecure)
}
cookie.ConditionalSameSiteNone, err = parser.GetBoolAnnotation(annotationAffinityCookieConditionalSameSiteNone, ing)
cookie.ConditionalSameSiteNone, err = parser.GetBoolAnnotation(annotationAffinityCookieConditionalSameSiteNone, ing, a.annotationConfig.Annotations)
if err != nil {
klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieConditionalSameSiteNone)
}
cookie.ChangeOnFailure, err = parser.GetBoolAnnotation(annotationAffinityCookieChangeOnFailure, ing)
cookie.ChangeOnFailure, err = parser.GetBoolAnnotation(annotationAffinityCookieChangeOnFailure, ing, a.annotationConfig.Annotations)
if err != nil {
klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieChangeOnFailure)
}
@ -162,11 +246,15 @@ func (a affinity) cookieAffinityParse(ing *networking.Ingress) *Cookie {
// NewParser creates a new Affinity annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return affinity{r}
return affinity{
r: r,
annotationConfig: sessionAffinityAnnotations,
}
}
type affinity struct {
r resolver.Resolver
r resolver.Resolver
annotationConfig parser.Annotation
}
// ParseAnnotations parses the annotations contained in the ingress
@ -174,18 +262,18 @@ type affinity struct {
func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
cookie := &Cookie{}
// Check the type of affinity that will be used
at, err := parser.GetStringAnnotation(annotationAffinityType, ing)
at, err := parser.GetStringAnnotation(annotationAffinityType, ing, a.annotationConfig.Annotations)
if err != nil {
at = ""
}
// Check the affinity mode that will be used
am, err := parser.GetStringAnnotation(annotationAffinityMode, ing)
am, err := parser.GetStringAnnotation(annotationAffinityMode, ing, a.annotationConfig.Annotations)
if err != nil {
am = ""
}
cb, err := parser.GetStringAnnotation(annotationAffinityCanaryBehavior, ing)
cb, err := parser.GetStringAnnotation(annotationAffinityCanaryBehavior, ing, a.annotationConfig.Annotations)
if err != nil {
cb = ""
}
@ -205,3 +293,12 @@ func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
Cookie: *cookie,
}, nil
}
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)
}