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:
parent
86c00a2310
commit
c5f348ea2e
109 changed files with 4320 additions and 586 deletions
|
|
@ -18,14 +18,82 @@ package canary
|
|||
|
||||
import (
|
||||
networking "k8s.io/api/networking/v1"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
const (
|
||||
canaryAnnotation = "canary"
|
||||
canaryWeightAnnotation = "canary-weight"
|
||||
canaryWeightTotalAnnotation = "canary-weight-total"
|
||||
canaryByHeaderAnnotation = "canary-by-header"
|
||||
canaryByHeaderValueAnnotation = "canary-by-header-value"
|
||||
canaryByHeaderPatternAnnotation = "canary-by-header-pattern"
|
||||
canaryByCookieAnnotation = "canary-by-cookie"
|
||||
)
|
||||
|
||||
var CanaryAnnotations = parser.Annotation{
|
||||
Group: "canary",
|
||||
Annotations: parser.AnnotationFields{
|
||||
canaryAnnotation: {
|
||||
Validator: parser.ValidateBool,
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation enables the Ingress spec to act as an alternative service for requests to route to depending on the rules applied`,
|
||||
},
|
||||
canaryWeightAnnotation: {
|
||||
Validator: parser.ValidateInt,
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation defines the integer based (0 - ) percent of random requests that should be routed to the service specified in the canary Ingress`,
|
||||
},
|
||||
canaryWeightTotalAnnotation: {
|
||||
Validator: parser.ValidateInt,
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation The total weight of traffic. If unspecified, it defaults to 100`,
|
||||
},
|
||||
canaryByHeaderAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation defines the header that should be used for notifying the Ingress to route the request to the service specified in the Canary Ingress.
|
||||
When the request header is set to 'always', it will be routed to the canary. When the header is set to 'never', it will never be routed to the canary.
|
||||
For any other value, the header will be ignored and the request compared against the other canary rules by precedence`,
|
||||
},
|
||||
canaryByHeaderValueAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation defines the header value to match for notifying the Ingress to route the request to the service specified in the Canary Ingress.
|
||||
When the request header is set to this value, it will be routed to the canary. For any other header value, the header will be ignored and the request compared against the other canary rules by precedence.
|
||||
This annotation has to be used together with 'canary-by-header'. The annotation is an extension of the 'canary-by-header' to allow customizing the header value instead of using hardcoded values.
|
||||
It doesn't have any effect if the 'canary-by-header' annotation is not defined`,
|
||||
},
|
||||
canaryByHeaderPatternAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.IsValidRegex, false),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation works the same way as canary-by-header-value except it does PCRE Regex matching.
|
||||
Note that when 'canary-by-header-value' is set this annotation will be ignored.
|
||||
When the given Regex causes error during request processing, the request will be considered as not matching.`,
|
||||
},
|
||||
canaryByCookieAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation defines the cookie that should be used for notifying the Ingress to route the request to the service specified in the Canary Ingress.
|
||||
When the cookie is set to 'always', it will be routed to the canary. When the cookie is set to 'never', it will never be routed to the canary`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type canary struct {
|
||||
r resolver.Resolver
|
||||
r resolver.Resolver
|
||||
annotationConfig parser.Annotation
|
||||
}
|
||||
|
||||
// Config returns the configuration rules for setting up the Canary
|
||||
|
|
@ -41,7 +109,10 @@ type Config struct {
|
|||
|
||||
// NewParser parses the ingress for canary related annotations
|
||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||
return canary{r}
|
||||
return canary{
|
||||
r: r,
|
||||
annotationConfig: CanaryAnnotations,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse parses the annotations contained in the ingress
|
||||
|
|
@ -50,45 +121,75 @@ func (c canary) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
config := &Config{}
|
||||
var err error
|
||||
|
||||
config.Enabled, err = parser.GetBoolAnnotation("canary", ing)
|
||||
config.Enabled, err = parser.GetBoolAnnotation(canaryAnnotation, ing, c.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
if errors.IsValidationError(err) {
|
||||
klog.Warningf("%s is invalid, defaulting to 'false'", canaryAnnotation)
|
||||
}
|
||||
config.Enabled = false
|
||||
}
|
||||
|
||||
config.Weight, err = parser.GetIntAnnotation("canary-weight", ing)
|
||||
config.Weight, err = parser.GetIntAnnotation(canaryWeightAnnotation, ing, c.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
if errors.IsValidationError(err) {
|
||||
klog.Warningf("%s is invalid, defaulting to '0'", canaryWeightAnnotation)
|
||||
}
|
||||
config.Weight = 0
|
||||
}
|
||||
|
||||
config.WeightTotal, err = parser.GetIntAnnotation("canary-weight-total", ing)
|
||||
config.WeightTotal, err = parser.GetIntAnnotation(canaryWeightTotalAnnotation, ing, c.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
if errors.IsValidationError(err) {
|
||||
klog.Warningf("%s is invalid, defaulting to '100'", canaryWeightTotalAnnotation)
|
||||
}
|
||||
config.WeightTotal = 100
|
||||
}
|
||||
|
||||
config.Header, err = parser.GetStringAnnotation("canary-by-header", ing)
|
||||
config.Header, err = parser.GetStringAnnotation(canaryByHeaderAnnotation, ing, c.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
if errors.IsValidationError(err) {
|
||||
klog.Warningf("%s is invalid, defaulting to ''", canaryByHeaderAnnotation)
|
||||
}
|
||||
config.Header = ""
|
||||
}
|
||||
|
||||
config.HeaderValue, err = parser.GetStringAnnotation("canary-by-header-value", ing)
|
||||
config.HeaderValue, err = parser.GetStringAnnotation(canaryByHeaderValueAnnotation, ing, c.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
if errors.IsValidationError(err) {
|
||||
klog.Warningf("%s is invalid, defaulting to ''", canaryByHeaderValueAnnotation)
|
||||
}
|
||||
config.HeaderValue = ""
|
||||
}
|
||||
|
||||
config.HeaderPattern, err = parser.GetStringAnnotation("canary-by-header-pattern", ing)
|
||||
config.HeaderPattern, err = parser.GetStringAnnotation(canaryByHeaderPatternAnnotation, ing, c.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
if errors.IsValidationError(err) {
|
||||
klog.Warningf("%s is invalid, defaulting to ''", canaryByHeaderPatternAnnotation)
|
||||
}
|
||||
config.HeaderPattern = ""
|
||||
}
|
||||
|
||||
config.Cookie, err = parser.GetStringAnnotation("canary-by-cookie", ing)
|
||||
config.Cookie, err = parser.GetStringAnnotation(canaryByCookieAnnotation, ing, c.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
if errors.IsValidationError(err) {
|
||||
klog.Warningf("%s is invalid, defaulting to ''", canaryByCookieAnnotation)
|
||||
}
|
||||
config.Cookie = ""
|
||||
}
|
||||
|
||||
if !config.Enabled && (config.Weight > 0 || len(config.Header) > 0 || len(config.HeaderValue) > 0 || len(config.Cookie) > 0 ||
|
||||
len(config.HeaderPattern) > 0) {
|
||||
return nil, errors.NewInvalidAnnotationConfiguration("canary", "configured but not enabled")
|
||||
return nil, errors.NewInvalidAnnotationConfiguration(canaryAnnotation, "configured but not enabled")
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c canary) GetDocumentation() parser.AnnotationFields {
|
||||
return c.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a canary) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, CanaryAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue