Anubis module for Caddy
1package caddy_anubis 2 3import ( 4 "fmt" 5 "net" 6 "net/http" 7 "strconv" 8 9 "github.com/TecharoHQ/anubis" 10 libanubis "github.com/TecharoHQ/anubis/lib" 11 "github.com/caddyserver/caddy/v2" 12 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" 13 "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" 14 "github.com/caddyserver/caddy/v2/modules/caddyhttp" 15 "go.uber.org/zap" 16) 17 18func init() { 19 caddy.RegisterModule(AnubisMiddleware{}) 20 httpcaddyfile.RegisterHandlerDirective("anubis", parseCaddyfileHandler) 21 httpcaddyfile.RegisterDirectiveOrder("anubis", httpcaddyfile.Before, "push") 22} 23 24func (AnubisMiddleware) CaddyModule() caddy.ModuleInfo { 25 return caddy.ModuleInfo{ 26 ID: "http.handlers.anubis", 27 New: func() caddy.Module { return new(AnubisMiddleware) }, 28 } 29} 30 31type AnubisMiddleware struct { 32 Options libanubis.Options `json:"options"` 33 PolicyFname string `json:"policy_fname,omitempty"` 34 DefaultDifficulty int `json:"default_difficulty,omitempty"` 35 36 anubis *libanubis.Server 37 log *zap.Logger 38 next caddyhttp.Handler 39} 40 41// Interface guards 42var ( 43 _ caddyhttp.MiddlewareHandler = (*AnubisMiddleware)(nil) 44 _ caddyfile.Unmarshaler = (*AnubisMiddleware)(nil) 45 _ caddy.Provisioner = (*AnubisMiddleware)(nil) 46) 47 48func (m *AnubisMiddleware) Provision(ctx caddy.Context) error { 49 m.log = ctx.Logger(m) 50 51 // slog.SetLogLoggerLevel(slog.LevelDebug) // TODO: customizable log level 52 53 m.log.Debug("loading anubis policies", zap.String("policy_file", m.PolicyFname), zap.Int("default_difficulty", m.DefaultDifficulty)) 54 policy, err := libanubis.LoadPoliciesOrDefault(ctx, m.PolicyFname, m.DefaultDifficulty) 55 if err != nil { 56 return fmt.Errorf("failed to load anubis policies from '%s': %w", m.PolicyFname, err) 57 } 58 59 m.Options.Next = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 60 if err := m.next.ServeHTTP(w, r); err != nil { 61 m.log.Error("error from next handler", zap.Error(err)) 62 } 63 }) 64 m.Options.Policy = policy 65 m.anubis, err = libanubis.New(m.Options) 66 67 if err != nil { 68 return err 69 } 70 71 return nil 72} 73 74func (m *AnubisMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { 75 remoteHost, _, err := net.SplitHostPort(r.RemoteAddr) 76 if err != nil { 77 return err 78 } 79 r.Header.Set("X-Real-Ip", remoteHost) 80 81 m.next = next 82 m.anubis.ServeHTTP(w, r) 83 84 return nil 85} 86 87func (m *AnubisMiddleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { 88 d.Next() 89 90 m.DefaultDifficulty = anubis.DefaultDifficulty 91 m.Options.CookieExpiration = anubis.CookieDefaultExpirationTime 92 m.Options.CookieSecure = true // TODO: temporary 93 94 for nesting := d.Nesting(); d.NextBlock(nesting); { 95 var err error 96 97 switch d.Val() { 98 case "difficulty": 99 if !d.Next() { 100 return d.ArgErr() 101 } 102 m.DefaultDifficulty, err = strconv.Atoi(d.Val()) 103 if err != nil { 104 return d.WrapErr(err) 105 } 106 case "policy_fname": 107 if !d.Next() { 108 return d.ArgErr() 109 } 110 m.PolicyFname = d.Val() 111 } 112 } // anubis options 113 114 if d.NextArg() { 115 return d.ArgErr() 116 } // too many args 117 118 return nil 119} 120 121func parseCaddyfileHandler(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { 122 var m AnubisMiddleware 123 err := m.UnmarshalCaddyfile(h.Dispenser) 124 return &m, err 125}