| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- package policy
- import (
- "context"
- "errors"
- "fmt"
- "io"
- "log/slog"
- "os"
- "sync/atomic"
- "time"
- "github.com/TecharoHQ/anubis/internal"
- "github.com/TecharoHQ/anubis/internal/dns"
- "github.com/TecharoHQ/anubis/lib/config"
- "github.com/TecharoHQ/anubis/lib/policy/checker"
- "github.com/TecharoHQ/anubis/lib/store"
- "github.com/TecharoHQ/anubis/lib/thoth"
- "github.com/fahedouch/go-logrotate"
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promauto"
- _ "github.com/TecharoHQ/anubis/lib/store/all"
- )
- var (
- Applications = promauto.NewCounterVec(prometheus.CounterOpts{
- Name: "anubis_policy_results",
- Help: "The results of each policy rule",
- }, []string{"rule", "action"})
- ErrChallengeRuleHasWrongAlgorithm = errors.New("config.Bot.ChallengeRules: algorithm is invalid")
- warnedAboutThresholds = &atomic.Bool{}
- )
- type ParsedConfig struct {
- Store store.Interface
- orig *config.Config
- Impressum *config.Impressum
- OpenGraph config.OpenGraph
- Bots []Bot
- Thresholds []*Threshold
- StatusCodes config.StatusCodes
- DefaultDifficulty int
- DNSBL bool
- DnsCache *dns.DnsCache
- Dns *dns.Dns
- Logger *slog.Logger
- }
- func newParsedConfig(orig *config.Config) *ParsedConfig {
- return &ParsedConfig{
- orig: orig,
- OpenGraph: orig.OpenGraph,
- StatusCodes: orig.StatusCodes,
- }
- }
- func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDifficulty int, logLevel string) (*ParsedConfig, error) {
- c, err := config.Load(fin, fname)
- if err != nil {
- return nil, err
- }
- var validationErrs []error
- tc, hasThothClient := thoth.FromContext(ctx)
- result := newParsedConfig(c)
- result.DefaultDifficulty = defaultDifficulty
- if c.Logging.Level != nil {
- logLevel = c.Logging.Level.String()
- }
- switch c.Logging.Sink {
- case config.LogSinkStdio:
- result.Logger = internal.InitSlog(logLevel, os.Stderr)
- case config.LogSinkFile:
- out := &logrotate.Logger{
- Filename: c.Logging.Parameters.Filename,
- FilenameTimeFormat: time.RFC3339,
- MaxBytes: c.Logging.Parameters.MaxBytes,
- MaxAge: c.Logging.Parameters.MaxAge,
- MaxBackups: c.Logging.Parameters.MaxBackups,
- LocalTime: c.Logging.Parameters.UseLocalTime,
- Compress: c.Logging.Parameters.Compress,
- }
- result.Logger = internal.InitSlog(logLevel, out)
- }
- lg := result.Logger.With("at", "config-validate")
- stFac, ok := store.Get(c.Store.Backend)
- switch ok {
- case true:
- store, err := stFac.Build(ctx, c.Store.Parameters)
- if err != nil {
- validationErrs = append(validationErrs, err)
- } else {
- result.Store = store
- }
- case false:
- validationErrs = append(validationErrs, config.ErrUnknownStoreBackend)
- }
- result.DnsCache = dns.NewDNSCache(result.orig.DNSTTL.Forward, result.orig.DNSTTL.Reverse, result.Store)
- result.Dns = dns.New(ctx, result.DnsCache)
- for _, b := range c.Bots {
- if berr := b.Valid(); berr != nil {
- validationErrs = append(validationErrs, berr)
- continue
- }
- parsedBot := Bot{
- Name: b.Name,
- Action: b.Action,
- }
- cl := checker.List{}
- if len(b.RemoteAddr) > 0 {
- c, err := NewRemoteAddrChecker(b.RemoteAddr)
- if err != nil {
- validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s remote addr set: %w", b.Name, err))
- } else {
- cl = append(cl, c)
- }
- }
- if b.UserAgentRegex != nil {
- c, err := NewUserAgentChecker(*b.UserAgentRegex)
- if err != nil {
- validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s user agent regex: %w", b.Name, err))
- } else {
- cl = append(cl, c)
- }
- }
- if b.PathRegex != nil {
- c, err := NewPathChecker(*b.PathRegex)
- if err != nil {
- validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s path regex: %w", b.Name, err))
- } else {
- cl = append(cl, c)
- }
- }
- if len(b.HeadersRegex) > 0 {
- c, err := NewHeadersChecker(b.HeadersRegex)
- if err != nil {
- validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s headers regex map: %w", b.Name, err))
- } else {
- cl = append(cl, c)
- }
- }
- if b.Expression != nil {
- c, err := NewCELChecker(b.Expression, result.Dns)
- if err != nil {
- validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s expressions: %w", b.Name, err))
- } else {
- cl = append(cl, c)
- }
- }
- if b.ASNs != nil {
- if !hasThothClient {
- lg.Warn("You have specified a Thoth specific check but you have no Thoth client configured. Please read https://anubis.techaro.lol/docs/admin/thoth for more information", "check", "asn", "settings", b.ASNs)
- continue
- }
- cl = append(cl, tc.ASNCheckerFor(b.ASNs.Match))
- }
- if b.GeoIP != nil {
- if !hasThothClient {
- lg.Warn("You have specified a Thoth specific check but you have no Thoth client configured. Please read https://anubis.techaro.lol/docs/admin/thoth for more information", "check", "geoip", "settings", b.GeoIP)
- continue
- }
- cl = append(cl, tc.GeoIPCheckerFor(b.GeoIP.Countries))
- }
- if b.Challenge == nil {
- parsedBot.Challenge = &config.ChallengeRules{
- Difficulty: defaultDifficulty,
- Algorithm: "fast",
- }
- } else {
- parsedBot.Challenge = b.Challenge
- if parsedBot.Challenge.Algorithm == "" {
- parsedBot.Challenge.Algorithm = config.DefaultAlgorithm
- }
- if parsedBot.Challenge.Algorithm == "slow" {
- lg.Warn("use of deprecated algorithm \"slow\" detected, please update this to \"fast\" when possible", "name", parsedBot.Name)
- }
- }
- if b.Weight != nil {
- parsedBot.Weight = b.Weight
- }
- result.Impressum = c.Impressum
- parsedBot.Rules = cl
- result.Bots = append(result.Bots, parsedBot)
- }
- for _, t := range c.Thresholds {
- if t.Challenge != nil && t.Challenge.Algorithm == "slow" {
- lg.Warn("use of deprecated algorithm \"slow\" detected, please update this to \"fast\" when possible", "name", t.Name)
- }
- if t.Challenge != nil && t.Challenge.ReportAs != 0 {
- lg.Warn("use of deprecated report_as setting detected, please remove this from your policy file when possible", "name", t.Name)
- }
- if t.Name == "legacy-anubis-behaviour" && t.Expression.String() == "true" {
- if !warnedAboutThresholds.Load() {
- lg.Warn("configuration file does not contain thresholds, see docs for details on how to upgrade", "fname", fname, "docs_url", "https://anubis.techaro.lol/docs/admin/configuration/thresholds/")
- warnedAboutThresholds.Store(true)
- }
- t.Challenge.Difficulty = defaultDifficulty
- }
- threshold, err := ParsedThresholdFromConfig(t)
- if err != nil {
- validationErrs = append(validationErrs, fmt.Errorf("can't compile threshold config for %s: %w", t.Name, err))
- continue
- }
- result.Thresholds = append(result.Thresholds, threshold)
- }
- if len(validationErrs) > 0 {
- return nil, fmt.Errorf("errors validating policy config JSON %s: %w", fname, errors.Join(validationErrs...))
- }
- result.DNSBL = c.DNSBL
- return result, nil
- }
|