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