Anubis module for Caddy
1package caddy_anubis
2
3import (
4 "fmt"
5 "log/slog"
6 "net"
7 "net/http"
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 switch d.Val() {
96 case "target":
97 case "opengraph":
98 m.Options.OpenGraph.Enabled = true
99
100 }
101 } // anubis options
102
103 if d.NextArg() {
104 return d.ArgErr()
105 } // too many args
106
107 return nil
108}
109
110func parseCaddyfileHandler(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
111 var m AnubisMiddleware
112 err := m.UnmarshalCaddyfile(h.Dispenser)
113 return &m, err
114}