From f96ccfcb92b0917c8a3bbf5828fd452d859d8fb0 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 1 Apr 2021 12:13:38 -0600 Subject: [PATCH] Add Caddyfile support Still needs matchers but that can come later --- caddyfile.go | 171 ++++++++++++++++++++++++++++++++++++++++++++++++--- go.mod | 2 +- go.sum | 4 +- 3 files changed, 165 insertions(+), 12 deletions(-) diff --git a/caddyfile.go b/caddyfile.go index 14d7bb3..7fa1abc 100644 --- a/caddyfile.go +++ b/caddyfile.go @@ -1,8 +1,10 @@ package caddyrl import ( - "fmt" + "strconv" + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/modules/caddyhttp" @@ -12,17 +14,168 @@ func init() { httpcaddyfile.RegisterHandlerDirective("rate_limit", parseCaddyfile) } -// UnmarshalCaddyfile implements caddyfile.Unmarshaler. -func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - return fmt.Errorf("TODO: not yet implemented") - } - return nil -} - // parseCaddyfile unmarshals tokens from h into a new Middleware. func parseCaddyfile(helper httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { var h Handler err := h.UnmarshalCaddyfile(helper.Dispenser) return h, err } + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. Syntax: +// +// rate_limit { +// zone { +// key +// window +// events +// } +// distributed { +// read_interval +// write_interval +// } +// storage +// jitter +// sweep_interval +// } +// +func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + for d.Next() { + if d.NextArg() { + return d.ArgErr() + } + + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "zone": + if !d.NextArg() { + return d.ArgErr() + } + zoneName := d.Val() + + var zone RateLimit + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "key": + if !d.NextArg() { + return d.ArgErr() + } + if zone.Key != "" { + return d.Errf("zone key already specified: %s", zone.Key) + } + zone.Key = d.Val() + + case "window": + if !d.NextArg() { + return d.ArgErr() + } + if zone.Window != 0 { + return d.Errf("zone window already specified: %s", zone.Window) + } + window, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("invalid window duration '%s': %v", d.Val(), err) + } + zone.Window = caddy.Duration(window) + + case "events": + if !d.NextArg() { + return d.ArgErr() + } + if zone.MaxEvents != 0 { + return d.Errf("zone max events already specified: %s", zone.MaxEvents) + } + maxEvents, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("invalid max events integer '%s': %v", d.Val(), err) + } + zone.MaxEvents = maxEvents + } + } + if zone.Window == 0 || zone.MaxEvents == 0 { + return d.Err("a rate limit zone requires both a window and maximum events") + } + + if h.RateLimits == nil { + h.RateLimits = make(map[string]*RateLimit) + } + h.RateLimits[zoneName] = &zone + + case "distributed": + h.Distributed = new(DistributedRateLimiting) + + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "read_interval": + if !d.NextArg() { + return d.ArgErr() + } + if h.Distributed.ReadInterval != 0 { + return d.Errf("read interval already specified: %s", h.Distributed.ReadInterval) + } + interval, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("invalid read interval '%s': %v", d.Val(), err) + } + h.Distributed.ReadInterval = caddy.Duration(interval) + + case "write_interval": + if !d.NextArg() { + return d.ArgErr() + } + if h.Distributed.WriteInterval != 0 { + return d.Errf("write interval already specified: %s", h.Distributed.WriteInterval) + } + interval, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("invalid write interval '%s': %v", d.Val(), err) + } + h.Distributed.WriteInterval = caddy.Duration(interval) + } + } + + case "storage": + if !d.NextArg() { + return d.ArgErr() + } + modID := "caddy.storage." + d.Val() + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + storage, ok := unm.(caddy.StorageConverter) + if !ok { + return d.Errf("module %s is not a caddy.StorageConverter", modID) + } + h.StorageRaw = caddyconfig.JSONModuleObject(storage, "module", storage.(caddy.Module).CaddyModule().ID.Name(), nil) + + case "jitter": + if !d.NextArg() { + return d.ArgErr() + } + if h.Jitter != 0 { + return d.Errf("jitter already specified: %s", h.Jitter) + } + jitter, err := strconv.ParseFloat(d.Val(), 64) + if err != nil { + return d.Errf("invalid jitter percentage '%s': %v", d.Val(), err) + } + h.Jitter = jitter + + case "sweep_interval": + if !d.NextArg() { + return d.ArgErr() + } + if h.SweepInterval != 0 { + return d.Errf("sweep interval already specified: %s", h.SweepInterval) + } + interval, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("invalid sweep interval '%s': %v", d.Val(), err) + } + h.SweepInterval = caddy.Duration(interval) + } + } + } + + return nil +} diff --git a/go.mod b/go.mod index 46077f8..e376ffa 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/mholt/caddy-ratelimit go 1.16 require ( - github.com/caddyserver/caddy/v2 v2.4.0-beta.1.0.20210330201520-aac1ccf12d00 + github.com/caddyserver/caddy/v2 v2.4.0-beta.1.0.20210401181228-7da9241fd7d1 github.com/caddyserver/certmagic v0.12.1-0.20210224184602-7550222c4a6a go.uber.org/zap v1.16.0 ) diff --git a/go.sum b/go.sum index 0d38401..f4980bc 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,8 @@ github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTK github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/caddyserver/caddy/v2 v2.4.0-beta.1.0.20210330201520-aac1ccf12d00 h1:myf2INadYSYDQis6BrAcAqUIjCoxidatsVdx7oQJAg8= -github.com/caddyserver/caddy/v2 v2.4.0-beta.1.0.20210330201520-aac1ccf12d00/go.mod h1:YBJFK0UJhl86DT7EgeZrmZ7R923h6CHQ/3U8tHB0Av0= +github.com/caddyserver/caddy/v2 v2.4.0-beta.1.0.20210401181228-7da9241fd7d1 h1:pAZEcBgskvHSUmp1ajKq99pcKrlpMIhe9jarStTmh00= +github.com/caddyserver/caddy/v2 v2.4.0-beta.1.0.20210401181228-7da9241fd7d1/go.mod h1:YBJFK0UJhl86DT7EgeZrmZ7R923h6CHQ/3U8tHB0Av0= github.com/caddyserver/certmagic v0.12.1-0.20210224184602-7550222c4a6a h1:gcqCMCPuHlQPY+XZzOilNouNeRpH40UhMfSp0tmulxI= github.com/caddyserver/certmagic v0.12.1-0.20210224184602-7550222c4a6a/go.mod h1:dNOzF4iOB7H9E51xTooMB90vs+2XNVtpnx0liQNsQY4= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=