package caddy_anubis import ( "encoding/hex" "fmt" "net" "net/http" "os" "strconv" "time" "github.com/TecharoHQ/anubis" libanubis "github.com/TecharoHQ/anubis/lib" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/modules/caddyhttp" "go.uber.org/zap" ) func init() { caddy.RegisterModule(AnubisMiddleware{}) httpcaddyfile.RegisterHandlerDirective("anubis", parseCaddyfileHandler) httpcaddyfile.RegisterDirectiveOrder("anubis", httpcaddyfile.Before, "push") } func (AnubisMiddleware) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ ID: "http.handlers.anubis", New: func() caddy.Module { return new(AnubisMiddleware) }, } } type AnubisMiddleware struct { Options libanubis.Options `json:"options,omitempty"` PolicyFname string `json:"policy_fname,omitempty"` DefaultDifficulty int `json:"default_difficulty,omitempty"` anubis *libanubis.Server log *zap.Logger next caddyhttp.Handler } // Interface guards var ( _ caddyhttp.MiddlewareHandler = (*AnubisMiddleware)(nil) _ caddyfile.Unmarshaler = (*AnubisMiddleware)(nil) _ caddy.Provisioner = (*AnubisMiddleware)(nil) ) func (m *AnubisMiddleware) Provision(ctx caddy.Context) error { m.log = ctx.Logger(m) m.log.Debug("provisioning anubis middleware") if m.DefaultDifficulty < 1 { m.log.Debug("default difficulty unset or invalid, using default", zap.Int("default", anubis.DefaultDifficulty)) m.DefaultDifficulty = anubis.DefaultDifficulty } m.log.Debug("loading anubis policies", zap.String("policy_file", m.PolicyFname), zap.Int("default_difficulty", m.DefaultDifficulty)) policy, err := libanubis.LoadPoliciesOrDefault(m.PolicyFname, m.DefaultDifficulty) if err != nil { return fmt.Errorf("failed to load anubis policies from '%s': %w", m.PolicyFname, err) } m.Options.Next = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := m.next.ServeHTTP(w, r); err != nil { m.log.Error("error from next handler", zap.Error(err)) } }) m.Options.Policy = policy m.anubis, err = libanubis.New(m.Options) if err != nil { return err } return nil } func (m *AnubisMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { remoteHost, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return err } r.Header.Set("X-Real-Ip", remoteHost) m.next = next m.anubis.ServeHTTP(w, r) return nil } func (m *AnubisMiddleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { d.Next() m.DefaultDifficulty = anubis.DefaultDifficulty for nesting := d.Nesting(); d.NextBlock(nesting); { var err error switch d.Val() { // Numerical flags case "difficulty": if !d.NextArg() { return d.ArgErr() } if m.DefaultDifficulty, err = strconv.Atoi(d.Val()); err != nil { return d.WrapErr(err) } case "og_expiry_time": if !d.NextArg() { return d.ArgErr() } if m.Options.OGTimeToLive, err = time.ParseDuration(d.Val()); err != nil { return d.WrapErr(err) } // Boolean flags case "serve_robots_txt": if d.NextArg() { return d.ArgErr() } m.Options.ServeRobotsTXT = true case "cookie_partitioned": if d.NextArg() { return d.ArgErr() } m.Options.CookiePartitioned = true case "og_passthrough": if d.NextArg() { return d.ArgErr() } m.Options.OGPassthrough = true // String flags case "policy_fname": if !d.NextArg() { return d.ArgErr() } m.PolicyFname = d.Val() case "webmaster_email": if !d.NextArg() { return d.ArgErr() } m.Options.WebmasterEmail = d.Val() case "cookie_domain": if !d.NextArg() { return d.ArgErr() } m.Options.CookieDomain = d.Val() case "cookie_name": if !d.NextArg() { return d.ArgErr() } m.Options.CookieName = d.Val() case "target": if !d.NextArg() { return d.ArgErr() } m.Options.Target = d.Val() case "private_key": if !d.NextArg() { return d.ArgErr() } if m.Options.PrivateKey, err = hex.DecodeString(d.Val()); err != nil { return d.WrapErr(err) } case "private_key_file": if !d.NextArg() { return d.ArgErr() } hexFile, err := os.ReadFile(d.Val()) if err != nil { return d.WrapErr(err) } if _, err = hex.Decode([]byte(m.Options.PrivateKey), hexFile); err != nil { return d.WrapErr(err) } default: return d.SyntaxErr("unknown directive") } } // anubis options if d.NextArg() { return d.ArgErr() } // too many args return nil } func parseCaddyfileHandler(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { var m AnubisMiddleware err := m.UnmarshalCaddyfile(h.Dispenser) return &m, err }