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
|
|
@ -24,6 +24,7 @@ import (
|
|||
networking "k8s.io/api/networking/v1"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
"k8s.io/ingress-nginx/internal/net"
|
||||
"k8s.io/ingress-nginx/pkg/util/sets"
|
||||
|
|
@ -58,7 +59,7 @@ type Config struct {
|
|||
|
||||
ID string `json:"id"`
|
||||
|
||||
Whitelist []string `json:"whitelist"`
|
||||
Allowlist []string `json:"allowlist"`
|
||||
}
|
||||
|
||||
// Equal tests for equality between two RateLimit types
|
||||
|
|
@ -90,11 +91,11 @@ func (rt1 *Config) Equal(rt2 *Config) bool {
|
|||
if rt1.Name != rt2.Name {
|
||||
return false
|
||||
}
|
||||
if len(rt1.Whitelist) != len(rt2.Whitelist) {
|
||||
if len(rt1.Allowlist) != len(rt2.Allowlist) {
|
||||
return false
|
||||
}
|
||||
|
||||
return sets.StringElementsMatch(rt1.Whitelist, rt2.Whitelist)
|
||||
return sets.StringElementsMatch(rt1.Allowlist, rt2.Allowlist)
|
||||
}
|
||||
|
||||
// Zone returns information about the NGINX rate limit (limit_req_zone)
|
||||
|
|
@ -131,43 +132,121 @@ func (z1 *Zone) Equal(z2 *Zone) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
const (
|
||||
limitRateAnnotation = "limit-rate"
|
||||
limitRateAfterAnnotation = "limit-rate-after"
|
||||
limitRateRPMAnnotation = "limit-rpm"
|
||||
limitRateRPSAnnotation = "limit-rps"
|
||||
limitRateConnectionsAnnotation = "limit-connections"
|
||||
limitRateBurstMultiplierAnnotation = "limit-burst-multiplier"
|
||||
limitWhitelistAnnotation = "limit-whitelist" // This annotation is an alias for limit-allowlist
|
||||
limitAllowlistAnnotation = "limit-allowlist"
|
||||
)
|
||||
|
||||
var rateLimitAnnotations = parser.Annotation{
|
||||
Group: "rate-limit",
|
||||
Annotations: parser.AnnotationFields{
|
||||
limitRateAnnotation: {
|
||||
Validator: parser.ValidateInt,
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options
|
||||
Documentation: `Limits the rate of response transmission to a client. The rate is specified in bytes per second.
|
||||
The zero value disables rate limiting. The limit is set per a request, and so if a client simultaneously opens two connections, the overall rate will be twice as much as the specified limit.
|
||||
References: https://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate`,
|
||||
},
|
||||
limitRateAfterAnnotation: {
|
||||
Validator: parser.ValidateInt,
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options
|
||||
Documentation: `Sets the initial amount after which the further transmission of a response to a client will be rate limited.`,
|
||||
},
|
||||
limitRateRPMAnnotation: {
|
||||
Validator: parser.ValidateInt,
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options
|
||||
Documentation: `Requests per minute that will be allowed.`,
|
||||
},
|
||||
limitRateRPSAnnotation: {
|
||||
Validator: parser.ValidateInt,
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options
|
||||
Documentation: `Requests per second that will be allowed.`,
|
||||
},
|
||||
limitRateConnectionsAnnotation: {
|
||||
Validator: parser.ValidateInt,
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options
|
||||
Documentation: `Number of connections that will be allowed`,
|
||||
},
|
||||
limitRateBurstMultiplierAnnotation: {
|
||||
Validator: parser.ValidateInt,
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options
|
||||
Documentation: `Burst multiplier for a limit-rate enabled location.`,
|
||||
},
|
||||
limitAllowlistAnnotation: {
|
||||
Validator: parser.ValidateCIDRs,
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options
|
||||
Documentation: `List of CIDR/IP addresses that will not be rate-limited.`,
|
||||
AnnotationAliases: []string{limitWhitelistAnnotation},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type ratelimit struct {
|
||||
r resolver.Resolver
|
||||
r resolver.Resolver
|
||||
annotationConfig parser.Annotation
|
||||
}
|
||||
|
||||
// NewParser creates a new ratelimit annotation parser
|
||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||
return ratelimit{r}
|
||||
return ratelimit{
|
||||
r: r,
|
||||
annotationConfig: rateLimitAnnotations,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to rewrite the defined paths
|
||||
func (a ratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
defBackend := a.r.GetDefaultBackend()
|
||||
lr, err := parser.GetIntAnnotation("limit-rate", ing)
|
||||
lr, err := parser.GetIntAnnotation(limitRateAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
lr = defBackend.LimitRate
|
||||
}
|
||||
lra, err := parser.GetIntAnnotation("limit-rate-after", ing)
|
||||
lra, err := parser.GetIntAnnotation(limitRateAfterAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
lra = defBackend.LimitRateAfter
|
||||
}
|
||||
|
||||
rpm, _ := parser.GetIntAnnotation("limit-rpm", ing)
|
||||
rps, _ := parser.GetIntAnnotation("limit-rps", ing)
|
||||
conn, _ := parser.GetIntAnnotation("limit-connections", ing)
|
||||
burstMultiplier, err := parser.GetIntAnnotation("limit-burst-multiplier", ing)
|
||||
rpm, err := parser.GetIntAnnotation(limitRateRPMAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil && errors.IsValidationError(err) {
|
||||
return nil, err
|
||||
}
|
||||
rps, err := parser.GetIntAnnotation(limitRateRPSAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil && errors.IsValidationError(err) {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := parser.GetIntAnnotation(limitRateConnectionsAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil && errors.IsValidationError(err) {
|
||||
return nil, err
|
||||
}
|
||||
burstMultiplier, err := parser.GetIntAnnotation(limitRateBurstMultiplierAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
burstMultiplier = defBurst
|
||||
}
|
||||
|
||||
val, _ := parser.GetStringAnnotation("limit-whitelist", ing)
|
||||
|
||||
cidrs, err := net.ParseCIDRs(val)
|
||||
if err != nil {
|
||||
val, err := parser.GetStringAnnotation(limitAllowlistAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil && errors.IsValidationError(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cidrs, errCidr := net.ParseCIDRs(val)
|
||||
if errCidr != nil {
|
||||
return nil, errCidr
|
||||
}
|
||||
|
||||
if rpm == 0 && rps == 0 && conn == 0 {
|
||||
return &Config{
|
||||
Connections: Zone{},
|
||||
|
|
@ -203,7 +282,7 @@ func (a ratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
LimitRateAfter: lra,
|
||||
Name: zoneName,
|
||||
ID: encode(zoneName),
|
||||
Whitelist: cidrs,
|
||||
Allowlist: cidrs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -211,3 +290,12 @@ func encode(s string) string {
|
|||
str := base64.URLEncoding.EncodeToString([]byte(s))
|
||||
return strings.Replace(str, "=", "", -1)
|
||||
}
|
||||
|
||||
func (a ratelimit) GetDocumentation() parser.AnnotationFields {
|
||||
return a.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a ratelimit) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, rateLimitAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/ingress/defaults"
|
||||
"k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
|
|
@ -85,8 +86,8 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|||
func TestWithoutAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
_, err := NewParser(mockBackend{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Error("unexpected error with ingress without annotations")
|
||||
if err != nil && !errors.IsMissingAnnotations(err) {
|
||||
t.Errorf("unexpected error with ingress without annotations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,22 +95,22 @@ func TestRateLimiting(t *testing.T) {
|
|||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("limit-connections")] = "0"
|
||||
data[parser.GetAnnotationWithPrefix("limit-rps")] = "0"
|
||||
data[parser.GetAnnotationWithPrefix("limit-rpm")] = "0"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateConnectionsAnnotation)] = "0"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateRPSAnnotation)] = "0"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateRPMAnnotation)] = "0"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
_, err := NewParser(mockBackend{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error with invalid limits (0)")
|
||||
t.Errorf("unexpected error with invalid limits (0): %s", err)
|
||||
}
|
||||
|
||||
data = map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("limit-connections")] = "5"
|
||||
data[parser.GetAnnotationWithPrefix("limit-rps")] = "100"
|
||||
data[parser.GetAnnotationWithPrefix("limit-rpm")] = "10"
|
||||
data[parser.GetAnnotationWithPrefix("limit-rate-after")] = "100"
|
||||
data[parser.GetAnnotationWithPrefix("limit-rate")] = "10"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateConnectionsAnnotation)] = "5"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateRPSAnnotation)] = "100"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateRPMAnnotation)] = "10"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateAfterAnnotation)] = "100"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateAnnotation)] = "10"
|
||||
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
|
|
@ -147,12 +148,12 @@ func TestRateLimiting(t *testing.T) {
|
|||
}
|
||||
|
||||
data = map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("limit-connections")] = "5"
|
||||
data[parser.GetAnnotationWithPrefix("limit-rps")] = "100"
|
||||
data[parser.GetAnnotationWithPrefix("limit-rpm")] = "10"
|
||||
data[parser.GetAnnotationWithPrefix("limit-rate-after")] = "100"
|
||||
data[parser.GetAnnotationWithPrefix("limit-rate")] = "10"
|
||||
data[parser.GetAnnotationWithPrefix("limit-burst-multiplier")] = "3"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateConnectionsAnnotation)] = "5"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateRPSAnnotation)] = "100"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateRPMAnnotation)] = "10"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateAfterAnnotation)] = "100"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateAnnotation)] = "10"
|
||||
data[parser.GetAnnotationWithPrefix(limitRateBurstMultiplierAnnotation)] = "3"
|
||||
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
|
|
@ -189,3 +190,61 @@ func TestRateLimiting(t *testing.T) {
|
|||
t.Errorf("expected 10 in limit by limitrate but %v was returned", rateLimit.LimitRate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotationCIDR(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(limitRateConnectionsAnnotation)] = "5"
|
||||
data[parser.GetAnnotationWithPrefix(limitAllowlistAnnotation)] = "192.168.0.5, 192.168.50.32/24"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
rateLimit, ok := i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a RateLimit type")
|
||||
}
|
||||
if len(rateLimit.Allowlist) != 2 {
|
||||
t.Errorf("expected 2 cidrs in limit by ip but %v was returned", len(rateLimit.Allowlist))
|
||||
}
|
||||
|
||||
data = map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(limitRateConnectionsAnnotation)] = "5"
|
||||
data[parser.GetAnnotationWithPrefix(limitWhitelistAnnotation)] = "192.168.0.5, 192.168.50.32/24, 10.10.10.1"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err = NewParser(mockBackend{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
rateLimit, ok = i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a RateLimit type")
|
||||
}
|
||||
if len(rateLimit.Allowlist) != 3 {
|
||||
t.Errorf("expected 3 cidrs in limit by ip but %v was returned", len(rateLimit.Allowlist))
|
||||
}
|
||||
|
||||
// Parent annotation surpasses any alias
|
||||
data = map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(limitRateConnectionsAnnotation)] = "5"
|
||||
data[parser.GetAnnotationWithPrefix(limitWhitelistAnnotation)] = "192.168.0.5, 192.168.50.32/24, 10.10.10.1"
|
||||
data[parser.GetAnnotationWithPrefix(limitAllowlistAnnotation)] = "192.168.0.9"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err = NewParser(mockBackend{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
rateLimit, ok = i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a RateLimit type")
|
||||
}
|
||||
if len(rateLimit.Allowlist) != 1 {
|
||||
t.Errorf("expected 1 cidrs in limit by ip but %v was returned", len(rateLimit.Allowlist))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue