Remove global-rate-limit feature (#11851)
This commit is contained in:
parent
5243b9b90a
commit
21cd966d1c
25 changed files with 28 additions and 1326 deletions
|
|
@ -39,7 +39,6 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/annotations/defaultbackend"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/disableproxyintercepterrors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/globalratelimit"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/http2pushpreload"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/ipallowlist"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/ipdenylist"
|
||||
|
|
@ -98,7 +97,6 @@ type Ingress struct {
|
|||
Proxy proxy.Config
|
||||
ProxySSL proxyssl.Config
|
||||
RateLimit ratelimit.Config
|
||||
GlobalRateLimit globalratelimit.Config
|
||||
Redirect redirect.Config
|
||||
Rewrite rewrite.Config
|
||||
Satisfy string
|
||||
|
|
@ -147,7 +145,6 @@ func NewAnnotationFactory(cfg resolver.Resolver) map[string]parser.IngressAnnota
|
|||
"Proxy": proxy.NewParser(cfg),
|
||||
"ProxySSL": proxyssl.NewParser(cfg),
|
||||
"RateLimit": ratelimit.NewParser(cfg),
|
||||
"GlobalRateLimit": globalratelimit.NewParser(cfg),
|
||||
"Redirect": redirect.NewParser(cfg),
|
||||
"Rewrite": rewrite.NewParser(cfg),
|
||||
"Satisfy": satisfy.NewParser(cfg),
|
||||
|
|
|
|||
|
|
@ -1,179 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 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 globalratelimit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
networking "k8s.io/api/networking/v1"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
ing_errors "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"
|
||||
)
|
||||
|
||||
const defaultKey = "$remote_addr"
|
||||
|
||||
const (
|
||||
globalRateLimitAnnotation = "global-rate-limit"
|
||||
globalRateLimitWindowAnnotation = "global-rate-limit-window"
|
||||
globalRateLimitKeyAnnotation = "global-rate-limit-key"
|
||||
globalRateLimitIgnoredCidrsAnnotation = "global-rate-limit-ignored-cidrs"
|
||||
)
|
||||
|
||||
var globalRateLimitAnnotationConfig = parser.Annotation{
|
||||
Group: "ratelimit",
|
||||
Annotations: parser.AnnotationFields{
|
||||
globalRateLimitAnnotation: {
|
||||
Validator: parser.ValidateInt,
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation configures maximum allowed number of requests per window`,
|
||||
},
|
||||
globalRateLimitWindowAnnotation: {
|
||||
Validator: parser.ValidateDuration,
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `Configures a time window (i.e 1m) that the limit is applied`,
|
||||
},
|
||||
globalRateLimitKeyAnnotation: {
|
||||
Validator: parser.ValidateRegex(parser.NGINXVariable, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskHigh,
|
||||
Documentation: `This annotation Configures a key for counting the samples. Defaults to $remote_addr.
|
||||
You can also combine multiple NGINX variables here, like ${remote_addr}-${http_x_api_client} which would mean the limit will be applied to
|
||||
requests coming from the same API client (indicated by X-API-Client HTTP request header) with the same source IP address`,
|
||||
},
|
||||
globalRateLimitIgnoredCidrsAnnotation: {
|
||||
Validator: parser.ValidateCIDRs,
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation defines a comma separated list of IPs and CIDRs to match client IP against.
|
||||
When there's a match request is not considered for rate limiting.`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Config encapsulates all global rate limit attributes
|
||||
type Config struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Limit int `json:"limit"`
|
||||
WindowSize int `json:"window-size"`
|
||||
Key string `json:"key"`
|
||||
IgnoredCIDRs []string `json:"ignored-cidrs"`
|
||||
}
|
||||
|
||||
// Equal tests for equality between two Config types
|
||||
func (l *Config) Equal(r *Config) bool {
|
||||
if l.Namespace != r.Namespace {
|
||||
return false
|
||||
}
|
||||
if l.Limit != r.Limit {
|
||||
return false
|
||||
}
|
||||
if l.WindowSize != r.WindowSize {
|
||||
return false
|
||||
}
|
||||
if l.Key != r.Key {
|
||||
return false
|
||||
}
|
||||
if len(l.IgnoredCIDRs) != len(r.IgnoredCIDRs) || !sets.StringElementsMatch(l.IgnoredCIDRs, r.IgnoredCIDRs) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type globalratelimit struct {
|
||||
r resolver.Resolver
|
||||
annotationConfig parser.Annotation
|
||||
}
|
||||
|
||||
// NewParser creates a new globalratelimit annotation parser
|
||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||
return globalratelimit{
|
||||
r: r,
|
||||
annotationConfig: globalRateLimitAnnotationConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse extracts globalratelimit annotations from the given ingress
|
||||
// and returns them structured as Config type
|
||||
func (a globalratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
config := &Config{}
|
||||
|
||||
limit, err := parser.GetIntAnnotation(globalRateLimitAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil && ing_errors.IsInvalidContent(err) {
|
||||
return nil, err
|
||||
}
|
||||
rawWindowSize, err := parser.GetStringAnnotation(globalRateLimitWindowAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil && ing_errors.IsValidationError(err) {
|
||||
return config, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("failed to parse 'global-rate-limit-window' value: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
if limit == 0 || rawWindowSize == "" {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
windowSize, err := time.ParseDuration(rawWindowSize)
|
||||
if err != nil {
|
||||
return config, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("failed to parse 'global-rate-limit-window' value: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
key, err := parser.GetStringAnnotation(globalRateLimitKeyAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
klog.Warningf("invalid %s, defaulting to %s", globalRateLimitKeyAnnotation, defaultKey)
|
||||
}
|
||||
if key == "" {
|
||||
key = defaultKey
|
||||
}
|
||||
|
||||
rawIgnoredCIDRs, err := parser.GetStringAnnotation(globalRateLimitIgnoredCidrsAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil && ing_errors.IsInvalidContent(err) {
|
||||
return nil, err
|
||||
}
|
||||
ignoredCIDRs, err := net.ParseCIDRs(rawIgnoredCIDRs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.Namespace = strings.ReplaceAll(string(ing.UID), "-", "")
|
||||
config.Limit = limit
|
||||
config.WindowSize = int(windowSize.Seconds())
|
||||
config.Key = key
|
||||
config.IgnoredCIDRs = ignoredCIDRs
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (a globalratelimit) GetDocumentation() parser.AnnotationFields {
|
||||
return a.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a globalratelimit) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, globalRateLimitAnnotationConfig.Annotations)
|
||||
}
|
||||
|
|
@ -1,211 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 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 globalratelimit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
networking "k8s.io/api/networking/v1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
const (
|
||||
UID = "31285d47-b150-4dcf-bd6f-12c46d769f6e"
|
||||
expectedUID = "31285d47b1504dcfbd6f12c46d769f6e"
|
||||
)
|
||||
|
||||
func buildIngress() *networking.Ingress {
|
||||
defaultBackend := networking.IngressBackend{
|
||||
Service: &networking.IngressServiceBackend{
|
||||
Name: "default-backend",
|
||||
Port: networking.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &networking.Ingress{
|
||||
ObjectMeta: meta_v1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
UID: UID,
|
||||
},
|
||||
Spec: networking.IngressSpec{
|
||||
DefaultBackend: &networking.IngressBackend{
|
||||
Service: &networking.IngressServiceBackend{
|
||||
Name: "default-backend",
|
||||
Port: networking.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
Rules: []networking.IngressRule{
|
||||
{
|
||||
Host: "foo.bar.com",
|
||||
IngressRuleValue: networking.IngressRuleValue{
|
||||
HTTP: &networking.HTTPIngressRuleValue{
|
||||
Paths: []networking.HTTPIngressPath{
|
||||
{
|
||||
Path: "/foo",
|
||||
Backend: defaultBackend,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type mockBackend struct {
|
||||
resolver.Mock
|
||||
}
|
||||
|
||||
func TestGlobalRateLimiting(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
annRateLimit := parser.GetAnnotationWithPrefix("global-rate-limit")
|
||||
annRateLimitWindow := parser.GetAnnotationWithPrefix("global-rate-limit-window")
|
||||
annRateLimitKey := parser.GetAnnotationWithPrefix("global-rate-limit-key")
|
||||
annRateLimitIgnoredCIDRs := parser.GetAnnotationWithPrefix("global-rate-limit-ignored-cidrs")
|
||||
|
||||
testCases := []struct {
|
||||
title string
|
||||
annotations map[string]string
|
||||
expectedConfig *Config
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
"no annotation",
|
||||
nil,
|
||||
&Config{},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"minimum required annotations",
|
||||
map[string]string{
|
||||
annRateLimit: "100",
|
||||
annRateLimitWindow: "2m",
|
||||
},
|
||||
&Config{
|
||||
Namespace: expectedUID,
|
||||
Limit: 100,
|
||||
WindowSize: 120,
|
||||
Key: "$remote_addr",
|
||||
IgnoredCIDRs: make([]string, 0),
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"global-rate-limit-key annotation",
|
||||
map[string]string{
|
||||
annRateLimit: "100",
|
||||
annRateLimitWindow: "2m",
|
||||
annRateLimitKey: "$http_x_api_user",
|
||||
},
|
||||
&Config{
|
||||
Namespace: expectedUID,
|
||||
Limit: 100,
|
||||
WindowSize: 120,
|
||||
Key: "$http_x_api_user",
|
||||
IgnoredCIDRs: make([]string, 0),
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"global-rate-limit-ignored-cidrs annotation",
|
||||
map[string]string{
|
||||
annRateLimit: "100",
|
||||
annRateLimitWindow: "2m",
|
||||
annRateLimitKey: "$http_x_api_user",
|
||||
annRateLimitIgnoredCIDRs: "127.0.0.1, 200.200.24.0/24",
|
||||
},
|
||||
&Config{
|
||||
Namespace: expectedUID,
|
||||
Limit: 100,
|
||||
WindowSize: 120,
|
||||
Key: "$http_x_api_user",
|
||||
IgnoredCIDRs: []string{"127.0.0.1", "200.200.24.0/24"},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"global-rate-limit-complex-key",
|
||||
map[string]string{
|
||||
annRateLimit: "100",
|
||||
annRateLimitWindow: "2m",
|
||||
annRateLimitKey: "${http_x_api_user}${otherinfo}",
|
||||
},
|
||||
&Config{
|
||||
Namespace: expectedUID,
|
||||
Limit: 100,
|
||||
WindowSize: 120,
|
||||
Key: "${http_x_api_user}${otherinfo}",
|
||||
IgnoredCIDRs: make([]string, 0),
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"incorrect duration for window",
|
||||
map[string]string{
|
||||
annRateLimit: "100",
|
||||
annRateLimitWindow: "2mb",
|
||||
annRateLimitKey: "$http_x_api_user",
|
||||
},
|
||||
&Config{},
|
||||
ing_errors.ValidationError{
|
||||
Reason: fmt.Errorf("failed to parse 'global-rate-limit-window' value: annotation nginx.ingress.kubernetes.io/global-rate-limit-window contains invalid value"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
ing.SetAnnotations(testCase.annotations)
|
||||
|
||||
i, actualErr := NewParser(mockBackend{}).Parse(ing)
|
||||
if (testCase.expectedErr == nil || actualErr == nil) && testCase.expectedErr != actualErr {
|
||||
t.Errorf("%s expected error '%v' but got '%v'", testCase.title, testCase.expectedErr, actualErr)
|
||||
} else if testCase.expectedErr != nil && actualErr != nil &&
|
||||
testCase.expectedErr.Error() != actualErr.Error() {
|
||||
t.Errorf("expected error '%v' but got '%v'", testCase.expectedErr, actualErr)
|
||||
}
|
||||
|
||||
actualConfig, ok := i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected Config type but got %T", i)
|
||||
}
|
||||
if !testCase.expectedConfig.Equal(actualConfig) {
|
||||
expectedJSON, err := json.Marshal(testCase.expectedConfig)
|
||||
if err != nil {
|
||||
t.Errorf("failed to marshal expected config: %v", err)
|
||||
}
|
||||
actualJSON, err := json.Marshal(actualConfig)
|
||||
if err != nil {
|
||||
t.Errorf("failed to marshal actual config: %v", err)
|
||||
}
|
||||
t.Errorf("%v: expected config '%s' but got '%s'", testCase.title, expectedJSON, actualJSON)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -718,31 +718,6 @@ type Configuration struct {
|
|||
// Default: text/html
|
||||
DefaultType string `json:"default-type"`
|
||||
|
||||
// GlobalRateLimitMemcachedHost configures memcached host.
|
||||
GlobalRateLimitMemcachedHost string `json:"global-rate-limit-memcached-host"`
|
||||
|
||||
// GlobalRateLimitMemcachedPort configures memcached port.
|
||||
GlobalRateLimitMemcachedPort int `json:"global-rate-limit-memcached-port"`
|
||||
|
||||
// GlobalRateLimitMemcachedConnectTimeout configures timeout when connecting to memcached.
|
||||
// The unit is millisecond.
|
||||
GlobalRateLimitMemcachedConnectTimeout int `json:"global-rate-limit-memcached-connect-timeout"`
|
||||
|
||||
// GlobalRateLimitMemcachedMaxIdleTimeout configured how long connections
|
||||
// should be kept alive in idle state. The unit is millisecond.
|
||||
GlobalRateLimitMemcachedMaxIdleTimeout int `json:"global-rate-limit-memcached-max-idle-timeout"`
|
||||
|
||||
// GlobalRateLimitMemcachedPoolSize configures how many connections
|
||||
// should be kept alive in the pool.
|
||||
// Note that this is per NGINX worker. Make sure your memcached server can
|
||||
// handle `MemcachedPoolSize * <nginx worker count> * <nginx replica count>`
|
||||
// simultaneous connections.
|
||||
GlobalRateLimitMemcachedPoolSize int `json:"global-rate-limit-memcached-pool-size"`
|
||||
|
||||
// GlobalRateLimitStatusCode determines the HTTP status code to return
|
||||
// when limit is exceeding during global rate limiting.
|
||||
GlobalRateLimitStatusCode int `json:"global-rate-limit-status-code"`
|
||||
|
||||
// DebugConnections Enables debugging log for selected client connections
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#debug_connection
|
||||
// Default: ""
|
||||
|
|
@ -893,39 +868,34 @@ func NewDefault() Configuration {
|
|||
ServiceUpstream: false,
|
||||
AllowedResponseHeaders: []string{},
|
||||
},
|
||||
UpstreamKeepaliveConnections: 320,
|
||||
UpstreamKeepaliveTime: "1h",
|
||||
UpstreamKeepaliveTimeout: 60,
|
||||
UpstreamKeepaliveRequests: 10000,
|
||||
LimitConnZoneVariable: defaultLimitConnZoneVariable,
|
||||
BindAddressIpv4: defBindAddress,
|
||||
BindAddressIpv6: defBindAddress,
|
||||
OpentelemetryTrustIncomingSpan: true,
|
||||
OpentelemetryConfig: "/etc/ingress-controller/telemetry/opentelemetry.toml",
|
||||
OtlpCollectorPort: "4317",
|
||||
OtelServiceName: "nginx",
|
||||
OtelSampler: "AlwaysOn",
|
||||
OtelSamplerRatio: 0.01,
|
||||
OtelSamplerParentBased: true,
|
||||
OtelScheduleDelayMillis: 5000,
|
||||
OtelMaxExportBatchSize: 512,
|
||||
OtelMaxQueueSize: 2048,
|
||||
LimitReqStatusCode: 503,
|
||||
LimitConnStatusCode: 503,
|
||||
SyslogPort: 514,
|
||||
NoTLSRedirectLocations: "/.well-known/acme-challenge",
|
||||
NoAuthLocations: "/.well-known/acme-challenge",
|
||||
GlobalExternalAuth: defGlobalExternalAuth,
|
||||
ProxySSLLocationOnly: false,
|
||||
DefaultType: "text/html",
|
||||
GlobalRateLimitMemcachedPort: 11211,
|
||||
GlobalRateLimitMemcachedConnectTimeout: 50,
|
||||
GlobalRateLimitMemcachedMaxIdleTimeout: 10000,
|
||||
GlobalRateLimitMemcachedPoolSize: 50,
|
||||
GlobalRateLimitStatusCode: 429,
|
||||
DebugConnections: []string{},
|
||||
StrictValidatePathType: true,
|
||||
GRPCBufferSizeKb: 0,
|
||||
UpstreamKeepaliveConnections: 320,
|
||||
UpstreamKeepaliveTime: "1h",
|
||||
UpstreamKeepaliveTimeout: 60,
|
||||
UpstreamKeepaliveRequests: 10000,
|
||||
LimitConnZoneVariable: defaultLimitConnZoneVariable,
|
||||
BindAddressIpv4: defBindAddress,
|
||||
BindAddressIpv6: defBindAddress,
|
||||
OpentelemetryTrustIncomingSpan: true,
|
||||
OpentelemetryConfig: "/etc/ingress-controller/telemetry/opentelemetry.toml",
|
||||
OtlpCollectorPort: "4317",
|
||||
OtelServiceName: "nginx",
|
||||
OtelSampler: "AlwaysOn",
|
||||
OtelSamplerRatio: 0.01,
|
||||
OtelSamplerParentBased: true,
|
||||
OtelScheduleDelayMillis: 5000,
|
||||
OtelMaxExportBatchSize: 512,
|
||||
OtelMaxQueueSize: 2048,
|
||||
LimitReqStatusCode: 503,
|
||||
LimitConnStatusCode: 503,
|
||||
SyslogPort: 514,
|
||||
NoTLSRedirectLocations: "/.well-known/acme-challenge",
|
||||
NoAuthLocations: "/.well-known/acme-challenge",
|
||||
GlobalExternalAuth: defGlobalExternalAuth,
|
||||
ProxySSLLocationOnly: false,
|
||||
DefaultType: "text/html",
|
||||
DebugConnections: []string{},
|
||||
StrictValidatePathType: true,
|
||||
GRPCBufferSizeKb: 0,
|
||||
}
|
||||
|
||||
if klog.V(5).Enabled() {
|
||||
|
|
|
|||
|
|
@ -379,10 +379,6 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
|
|||
if !cfg.AllowSnippetAnnotations && strings.HasSuffix(key, "-snippet") {
|
||||
return fmt.Errorf("%s annotation cannot be used. Snippet directives are disabled by the Ingress administrator", key)
|
||||
}
|
||||
|
||||
if cfg.GlobalRateLimitMemcachedHost == "" && strings.HasPrefix(key, fmt.Sprintf("%s/%s", parser.AnnotationsPrefix, "global-rate-limit")) {
|
||||
return fmt.Errorf("'global-rate-limit*' annotations require 'global-rate-limit-memcached-host' settings configured in the global configmap")
|
||||
}
|
||||
}
|
||||
|
||||
k8s.SetDefaultNGINXPathType(ing)
|
||||
|
|
@ -1516,7 +1512,6 @@ func locationApplyAnnotations(loc *ingress.Location, anns *annotations.Ingress)
|
|||
loc.Proxy = anns.Proxy
|
||||
loc.ProxySSL = anns.ProxySSL
|
||||
loc.RateLimit = anns.RateLimit
|
||||
loc.GlobalRateLimit = anns.GlobalRateLimit
|
||||
loc.Redirect = anns.Redirect
|
||||
loc.Rewrite = anns.Rewrite
|
||||
loc.UpstreamVhost = anns.UpstreamVhost
|
||||
|
|
|
|||
|
|
@ -82,7 +82,6 @@ var (
|
|||
"balancer_ewma_locks": 1024,
|
||||
"certificate_servers": 5120,
|
||||
"ocsp_response_cache": 5120, // keep this same as certificate_servers
|
||||
"global_throttle_cache": 10240,
|
||||
}
|
||||
defaultGlobalAuthRedirectParam = "rd"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -403,12 +403,6 @@ func configForLua(input interface{}) string {
|
|||
hsts_include_subdomains = %t,
|
||||
hsts_preload = %t,
|
||||
|
||||
global_throttle = {
|
||||
memcached = {
|
||||
host = "%v", port = %d, connect_timeout = %d, max_idle_timeout = %d, pool_size = %d,
|
||||
},
|
||||
status_code = %d,
|
||||
}
|
||||
}`,
|
||||
all.Cfg.UseForwardedHeaders,
|
||||
all.Cfg.UseProxyProtocol,
|
||||
|
|
@ -421,13 +415,6 @@ func configForLua(input interface{}) string {
|
|||
all.Cfg.HSTSMaxAge,
|
||||
all.Cfg.HSTSIncludeSubdomains,
|
||||
all.Cfg.HSTSPreload,
|
||||
|
||||
all.Cfg.GlobalRateLimitMemcachedHost,
|
||||
all.Cfg.GlobalRateLimitMemcachedPort,
|
||||
all.Cfg.GlobalRateLimitMemcachedConnectTimeout,
|
||||
all.Cfg.GlobalRateLimitMemcachedMaxIdleTimeout,
|
||||
all.Cfg.GlobalRateLimitMemcachedPoolSize,
|
||||
all.Cfg.GlobalRateLimitStatusCode,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -445,30 +432,18 @@ func locationConfigForLua(l, a interface{}) string {
|
|||
return "{}"
|
||||
}
|
||||
|
||||
ignoredCIDRs, err := convertGoSliceIntoLuaTable(location.GlobalRateLimit.IgnoredCIDRs, false)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to convert %v into Lua table: %q", location.GlobalRateLimit.IgnoredCIDRs, err)
|
||||
ignoredCIDRs = "{}"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`{
|
||||
force_ssl_redirect = %t,
|
||||
ssl_redirect = %t,
|
||||
force_no_ssl_redirect = %t,
|
||||
preserve_trailing_slash = %t,
|
||||
use_port_in_redirects = %t,
|
||||
global_throttle = { namespace = "%v", limit = %d, window_size = %d, key = %v, ignored_cidrs = %v },
|
||||
}`,
|
||||
location.Rewrite.ForceSSLRedirect,
|
||||
location.Rewrite.SSLRedirect,
|
||||
isLocationInLocationList(l, all.Cfg.NoTLSRedirectLocations),
|
||||
location.Rewrite.PreserveTrailingSlash,
|
||||
location.UsePortInRedirects,
|
||||
location.GlobalRateLimit.Namespace,
|
||||
location.GlobalRateLimit.Limit,
|
||||
location.GlobalRateLimit.WindowSize,
|
||||
parseComplexNginxVarIntoLuaTable(location.GlobalRateLimit.Key),
|
||||
ignoredCIDRs,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1690,54 +1665,6 @@ func buildServerName(hostname string) string {
|
|||
return `~^(?<subdomain>[\w-]+)\.` + strings.Join(parts, "\\.") + `$`
|
||||
}
|
||||
|
||||
// parseComplexNginxVarIntoLuaTable parses things like "$my${complex}ngx\$var" into
|
||||
// [["$var", "complex", "my", "ngx"]]. In other words, 2nd and 3rd elements
|
||||
// in the result are actual NGINX variable names, whereas first and 4th elements
|
||||
// are string literals.
|
||||
func parseComplexNginxVarIntoLuaTable(ngxVar string) string {
|
||||
r := regexp.MustCompile(`(\\\$[0-9a-zA-Z_]+)|\$\{([0-9a-zA-Z_]+)\}|\$([0-9a-zA-Z_]+)|(\$|[^$\\]+)`)
|
||||
matches := r.FindAllStringSubmatch(ngxVar, -1)
|
||||
components := make([][]string, len(matches))
|
||||
for i, match := range matches {
|
||||
components[i] = match[1:]
|
||||
}
|
||||
|
||||
luaTable, err := convertGoSliceIntoLuaTable(components, true)
|
||||
if err != nil {
|
||||
klog.Errorf("unexpected error: %v", err)
|
||||
luaTable = "{}"
|
||||
}
|
||||
return luaTable
|
||||
}
|
||||
|
||||
func convertGoSliceIntoLuaTable(goSliceInterface interface{}, emptyStringAsNil bool) (string, error) {
|
||||
goSlice := reflect.ValueOf(goSliceInterface)
|
||||
kind := goSlice.Kind()
|
||||
|
||||
switch kind {
|
||||
case reflect.String:
|
||||
if emptyStringAsNil && goSlice.Interface().(string) == "" {
|
||||
return "nil", nil
|
||||
}
|
||||
return fmt.Sprintf(`"%v"`, goSlice.Interface()), nil
|
||||
case reflect.Int, reflect.Bool:
|
||||
return fmt.Sprintf(`%v`, goSlice.Interface()), nil
|
||||
case reflect.Slice, reflect.Array:
|
||||
luaTable := "{ "
|
||||
for i := 0; i < goSlice.Len(); i++ {
|
||||
luaEl, err := convertGoSliceIntoLuaTable(goSlice.Index(i).Interface(), emptyStringAsNil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
luaTable = luaTable + luaEl + ", "
|
||||
}
|
||||
luaTable += "}"
|
||||
return luaTable, nil
|
||||
default:
|
||||
return "", fmt.Errorf("could not process type: %s", kind)
|
||||
}
|
||||
}
|
||||
|
||||
func buildOriginRegex(origin string) string {
|
||||
origin = regexp.QuoteMeta(origin)
|
||||
origin = strings.Replace(origin, "\\*", `[A-Za-z0-9\-]+`, 1)
|
||||
|
|
|
|||
|
|
@ -1926,89 +1926,6 @@ func TestBuildServerName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParseComplexNginxVarIntoLuaTable(t *testing.T) {
|
||||
testCases := []struct {
|
||||
ngxVar string
|
||||
expectedLuaTable string
|
||||
}{
|
||||
{"foo", `{ { nil, nil, nil, "foo", }, }`},
|
||||
{"$foo", `{ { nil, nil, "foo", nil, }, }`},
|
||||
{"${foo}", `{ { nil, "foo", nil, nil, }, }`},
|
||||
{"\\$foo", `{ { "\$foo", nil, nil, nil, }, }`},
|
||||
{
|
||||
"foo\\$bar$baz${daz}xiyar$pomidor",
|
||||
`{ { nil, nil, nil, "foo", }, { "\$bar", nil, nil, nil, }, { nil, nil, "baz", nil, }, ` +
|
||||
`{ nil, "daz", nil, nil, }, { nil, nil, nil, "xiyar", }, { nil, nil, "pomidor", nil, }, }`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
actualLuaTable := parseComplexNginxVarIntoLuaTable(testCase.ngxVar)
|
||||
if actualLuaTable != testCase.expectedLuaTable {
|
||||
t.Errorf("expected %v but returned %v", testCase.expectedLuaTable, actualLuaTable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertGoSliceIntoLuaTablet(t *testing.T) {
|
||||
testCases := []struct {
|
||||
title string
|
||||
goSlice interface{}
|
||||
emptyStringAsNil bool
|
||||
expectedLuaTable string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
"flat string slice",
|
||||
[]string{"one", "two", "three"},
|
||||
false,
|
||||
`{ "one", "two", "three", }`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"nested string slice",
|
||||
[][]string{{"one", "", "three"}, {"foo", "bar"}},
|
||||
false,
|
||||
`{ { "one", "", "three", }, { "foo", "bar", }, }`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"converts empty string to nil when enabled",
|
||||
[][]string{{"one", "", "three"}, {"foo", "bar"}},
|
||||
true,
|
||||
`{ { "one", nil, "three", }, { "foo", "bar", }, }`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"boolean slice",
|
||||
[]bool{true, true, false},
|
||||
false,
|
||||
`{ true, true, false, }`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"integer slice",
|
||||
[]int{4, 3, 6},
|
||||
false,
|
||||
`{ 4, 3, 6, }`,
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
actualLuaTable, err := convertGoSliceIntoLuaTable(testCase.goSlice, testCase.emptyStringAsNil)
|
||||
if testCase.expectedErr != nil && err != nil && testCase.expectedErr.Error() != err.Error() {
|
||||
t.Errorf("expected error '%v' but returned '%v'", testCase.expectedErr, err)
|
||||
}
|
||||
if testCase.expectedErr == nil && err != nil {
|
||||
t.Errorf("expected error to be nil but returned '%v'", err)
|
||||
}
|
||||
if testCase.expectedLuaTable != actualLuaTable {
|
||||
t.Errorf("%v: expected '%v' but returned '%v'", testCase.title, testCase.expectedLuaTable, actualLuaTable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanConf(t *testing.T) {
|
||||
testDataDir, err := getTestDataDir()
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue