Commit b1d49a94 authored by ale's avatar ale

Add possibility to bypass ratelimits with user-defined criteria

parent 1325dff1
Pipeline #4788 passed with stages
in 1 minute and 52 seconds
......@@ -167,6 +167,17 @@ func ipAddrKey(_ *backend.User, req *auth.Request) string {
return ""
}
func parseKeyFunc(k string) (ratelimitKeyFunc, error) {
switch k {
case "ip":
return ipAddrKey, nil
case "user":
return usernameKey, nil
default:
return nil, fmt.Errorf("unknown key %s", k)
}
}
// Configuration for a rate limiter or blacklist (depending on whether
// BlacklistTime is set or not). All times are specified in seconds.
type authRatelimiterConfig struct {
......@@ -175,19 +186,38 @@ type authRatelimiterConfig struct {
BlacklistTime int `yaml:"blacklist_for"`
OnFailure bool `yaml:"on_failure"`
Keys []string `yaml:"keys"`
Bypass []struct {
Key string `yaml:"key"`
Value string `yaml:"value"`
} `yaml:"bypass"`
}
type bypassEvaluator struct {
keyFn ratelimitKeyFunc
value string
}
func (r *authRatelimiterConfig) bypassFunctions() ([]bypassEvaluator, error) {
out := make([]bypassEvaluator, 0, len(r.Bypass))
for _, b := range r.Bypass {
f, err := parseKeyFunc(b.Key)
if err != nil {
return nil, err
}
out = append(out, bypassEvaluator{
keyFn: f,
value: b.Value,
})
}
return out, nil
}
func (r *authRatelimiterConfig) keyFunctions() ([]ratelimitKeyFunc, error) {
var keyFuncs []ratelimitKeyFunc
for _, k := range r.Keys {
var f ratelimitKeyFunc
switch k {
case "ip":
f = ipAddrKey
case "user":
f = usernameKey
default:
return nil, fmt.Errorf("unknown key %s", k)
f, err := parseKeyFunc(k)
if err != nil {
return nil, err
}
keyFuncs = append(keyFuncs, f)
}
......@@ -198,6 +228,31 @@ const rlKeySep = ";"
type authRatelimiterBase struct {
keyFuncs []ratelimitKeyFunc
bypass []bypassEvaluator
}
func newAuthRatelimiterBase(config *authRatelimiterConfig) (*authRatelimiterBase, error) {
kf, err := config.keyFunctions()
if err != nil {
return nil, err
}
be, err := config.bypassFunctions()
if err != nil {
return nil, err
}
return &authRatelimiterBase{
keyFuncs: kf,
bypass: be,
}, nil
}
func (r *authRatelimiterBase) shouldBypass(user *backend.User, req *auth.Request) bool {
for _, b := range r.bypass {
if b.value == b.keyFn(user, req) {
return true
}
}
return false
}
func (r *authRatelimiterBase) key(user *backend.User, req *auth.Request) string {
......@@ -219,17 +274,20 @@ type authRatelimiter struct {
}
func newAuthRatelimiter(config *authRatelimiterConfig) (*authRatelimiter, error) {
kf, err := config.keyFunctions()
r, err := newAuthRatelimiterBase(config)
if err != nil {
return nil, err
}
return &authRatelimiter{
authRatelimiterBase: &authRatelimiterBase{keyFuncs: kf},
authRatelimiterBase: r,
rl: newRatelimiter(config.Limit, config.Period),
}, nil
}
func (r *authRatelimiter) AllowIncr(user *backend.User, req *auth.Request) bool {
if r.shouldBypass(user, req) {
return true
}
return r.rl.AllowIncr(r.key(user, req))
}
......@@ -241,18 +299,21 @@ type authBlacklist struct {
}
func newAuthBlacklist(config *authRatelimiterConfig) (*authBlacklist, error) {
kf, err := config.keyFunctions()
r, err := newAuthRatelimiterBase(config)
if err != nil {
return nil, err
}
return &authBlacklist{
authRatelimiterBase: &authRatelimiterBase{keyFuncs: kf},
authRatelimiterBase: r,
bl: newBlacklist(config.Limit, config.Period, config.BlacklistTime),
onFailure: config.OnFailure,
}, nil
}
func (b *authBlacklist) Allow(user *backend.User, req *auth.Request) bool {
if b.shouldBypass(user, req) {
return true
}
return b.bl.Allow(b.key(user, req))
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment