Merge pull request #1210 from sethpollack/whitelist
add rate limit whitelist
This commit is contained in:
commit
def5155aa6
4 changed files with 98 additions and 9 deletions
|
@ -225,6 +225,8 @@ The annotations `ingress.kubernetes.io/limit-connections`, `ingress.kubernetes.i
|
|||
|
||||
`ingress.kubernetes.io/limit-rpm`: number of connections that may be accepted from a given IP each minute.
|
||||
|
||||
You can specify the client IP source ranges to be excluded from rate-limiting through the `ingress.kubernetes.io/limit-whitelist` annotation. The value is a comma separated list of CIDRs.
|
||||
|
||||
If you specify multiple annotations in a single Ingress rule, `limit-rpm`, and then `limit-rps` takes precedence.
|
||||
|
||||
The annotation `ingress.kubernetes.io/limit-rate`, `ingress.kubernetes.io/limit-rate-after` define a limit 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.
|
||||
|
@ -239,7 +241,7 @@ To configure this setting globally for all Ingress rules, the `limit-rate-after`
|
|||
|
||||
The annotation `ingress.kubernetes.io/ssl-passthrough` allows to configure TLS termination in the pod and not in NGINX.
|
||||
|
||||
**Important:**
|
||||
**Important:**
|
||||
- Using the annotation `ingress.kubernetes.io/ssl-passthrough` invalidates all the other available annotations. This is because SSL Passthrough works in L4 (TCP).
|
||||
- The use of this annotation requires the flag `--enable-ssl-passthrough` (By default it is disabled)
|
||||
|
||||
|
|
|
@ -132,6 +132,7 @@ var (
|
|||
"buildAuthLocation": buildAuthLocation,
|
||||
"buildAuthResponseHeaders": buildAuthResponseHeaders,
|
||||
"buildProxyPass": buildProxyPass,
|
||||
"buildWhitelistVariable": buildWhitelistVariable,
|
||||
"buildRateLimitZones": buildRateLimitZones,
|
||||
"buildRateLimit": buildRateLimit,
|
||||
"buildResolvers": buildResolvers,
|
||||
|
@ -335,10 +336,23 @@ func buildProxyPass(host string, b interface{}, loc interface{}) string {
|
|||
return defProxyPass
|
||||
}
|
||||
|
||||
var (
|
||||
whitelistVarMap = map[string]string{}
|
||||
)
|
||||
|
||||
func buildWhitelistVariable(s string) string {
|
||||
if _, ok := whitelistVarMap[s]; !ok {
|
||||
str := base64.URLEncoding.EncodeToString([]byte(s))
|
||||
whitelistVarMap[s] = strings.Replace(str, "=", "", -1)
|
||||
}
|
||||
|
||||
return whitelistVarMap[s]
|
||||
}
|
||||
|
||||
// buildRateLimitZones produces an array of limit_conn_zone in order to allow
|
||||
// rate limiting of request. Each Ingress rule could have up to two zones, one
|
||||
// for connection limit by IP address and other for limiting request per second
|
||||
func buildRateLimitZones(variable string, input interface{}) []string {
|
||||
func buildRateLimitZones(input interface{}) []string {
|
||||
zones := sets.String{}
|
||||
|
||||
servers, ok := input.([]*ingress.Server)
|
||||
|
@ -349,9 +363,11 @@ func buildRateLimitZones(variable string, input interface{}) []string {
|
|||
for _, server := range servers {
|
||||
for _, loc := range server.Locations {
|
||||
|
||||
whitelistVar := buildWhitelistVariable(loc.RateLimit.Name)
|
||||
|
||||
if loc.RateLimit.Connections.Limit > 0 {
|
||||
zone := fmt.Sprintf("limit_conn_zone %v zone=%v:%vm;",
|
||||
variable,
|
||||
zone := fmt.Sprintf("limit_conn_zone $%s_limit zone=%v:%vm;",
|
||||
whitelistVar,
|
||||
loc.RateLimit.Connections.Name,
|
||||
loc.RateLimit.Connections.SharedSize)
|
||||
if !zones.Has(zone) {
|
||||
|
@ -360,8 +376,8 @@ func buildRateLimitZones(variable string, input interface{}) []string {
|
|||
}
|
||||
|
||||
if loc.RateLimit.RPM.Limit > 0 {
|
||||
zone := fmt.Sprintf("limit_req_zone %v zone=%v:%vm rate=%vr/m;",
|
||||
variable,
|
||||
zone := fmt.Sprintf("limit_req_zone $%s_limit zone=%v:%vm rate=%vr/m;",
|
||||
whitelistVar,
|
||||
loc.RateLimit.RPM.Name,
|
||||
loc.RateLimit.RPM.SharedSize,
|
||||
loc.RateLimit.RPM.Limit)
|
||||
|
@ -371,8 +387,8 @@ func buildRateLimitZones(variable string, input interface{}) []string {
|
|||
}
|
||||
|
||||
if loc.RateLimit.RPS.Limit > 0 {
|
||||
zone := fmt.Sprintf("limit_req_zone %v zone=%v:%vm rate=%vr/s;",
|
||||
variable,
|
||||
zone := fmt.Sprintf("limit_req_zone $%s_limit zone=%v:%vm rate=%vr/s;",
|
||||
whitelistVar,
|
||||
loc.RateLimit.RPS.Name,
|
||||
loc.RateLimit.RPS.SharedSize,
|
||||
loc.RateLimit.RPS.Limit)
|
||||
|
|
|
@ -288,12 +288,24 @@ http {
|
|||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if ne $location.RateLimit.Name "" }}
|
||||
geo ${{ buildWhitelistVariable $location.RateLimit.Name }}_whitelist {
|
||||
default 0;
|
||||
{{ range $ip := $location.RateLimit.Whitelist }}
|
||||
{{ $ip }} 1;{{ end }}
|
||||
}
|
||||
|
||||
map ${{ buildWhitelistVariable $location.RateLimit.Name }}_whitelist ${{ buildWhitelistVariable $location.RateLimit.Name }}_limit {
|
||||
0 {{ $cfg.LimitConnZoneVariable }};
|
||||
1 "";
|
||||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* build all the required rate limit zones. Each annotation requires a dedicated zone */}}
|
||||
{{/* 1MB -> 16 thousand 64-byte states or about 8 thousand 128-byte states */}}
|
||||
{{ range $zone := (buildRateLimitZones $cfg.LimitConnZoneVariable $servers) }}
|
||||
{{ range $zone := (buildRateLimitZones $servers) }}
|
||||
{{ $zone }}
|
||||
{{ end }}
|
||||
|
||||
|
|
|
@ -18,11 +18,14 @@ package ratelimit
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||
"k8s.io/ingress/core/pkg/net"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -31,6 +34,7 @@ const (
|
|||
limitRPM = "ingress.kubernetes.io/limit-rpm"
|
||||
limitRATE = "ingress.kubernetes.io/limit-rate"
|
||||
limitRATEAFTER = "ingress.kubernetes.io/limit-rate-after"
|
||||
limitWhitelist = "ingress.kubernetes.io/limit-whitelist"
|
||||
|
||||
// allow 5 times the specified limit as burst
|
||||
defBurst = 5
|
||||
|
@ -55,6 +59,10 @@ type RateLimit struct {
|
|||
LimitRate int `json:"limit-rate"`
|
||||
|
||||
LimitRateAfter int `json:"limit-rate-after"`
|
||||
|
||||
Name string `json:"name"`
|
||||
|
||||
Whitelist []string `json:"whitelist"`
|
||||
}
|
||||
|
||||
// Equal tests for equality between two RateLimit types
|
||||
|
@ -80,6 +88,22 @@ func (rt1 *RateLimit) Equal(rt2 *RateLimit) bool {
|
|||
if rt1.LimitRateAfter != rt2.LimitRateAfter {
|
||||
return false
|
||||
}
|
||||
if rt1.Name != rt2.Name {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, r1l := range rt1.Whitelist {
|
||||
found := false
|
||||
for _, rl2 := range rt2.Whitelist {
|
||||
if r1l == rl2 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -144,6 +168,13 @@ func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|||
rps, _ := parser.GetIntAnnotation(limitRPS, ing)
|
||||
conn, _ := parser.GetIntAnnotation(limitIP, ing)
|
||||
|
||||
val, _ := parser.GetStringAnnotation(limitWhitelist, ing)
|
||||
|
||||
cidrs, err := parseCIDRs(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rpm == 0 && rps == 0 && conn == 0 {
|
||||
return &RateLimit{
|
||||
Connections: Zone{},
|
||||
|
@ -177,5 +208,33 @@ func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|||
},
|
||||
LimitRate: lr,
|
||||
LimitRateAfter: lra,
|
||||
Name: zoneName,
|
||||
Whitelist: cidrs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseCIDRs(s string) ([]string, error) {
|
||||
if s == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
values := strings.Split(s, ",")
|
||||
|
||||
ipnets, ips, err := net.ParseIPNets(values...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cidrs := []string{}
|
||||
for k := range ipnets {
|
||||
cidrs = append(cidrs, k)
|
||||
}
|
||||
|
||||
for k := range ips {
|
||||
cidrs = append(cidrs, k)
|
||||
}
|
||||
|
||||
sort.Strings(cidrs)
|
||||
|
||||
return cidrs, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue