aboutsummaryrefslogtreecommitdiff
path: root/lib/policy/policy.go
blob: 2d610c82c2246153c8df05a2d1547c1ef0e57b39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package policy

import (
	"errors"
	"fmt"
	"io"
	"net"
	"regexp"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/yl2chen/cidranger"
	"k8s.io/apimachinery/pkg/util/yaml"

	"github.com/TecharoHQ/anubis/lib/policy/config"
)

var (
	PolicyApplications = promauto.NewCounterVec(prometheus.CounterOpts{
		Name: "anubis_policy_results",
		Help: "The results of each policy rule",
	}, []string{"rule", "action"})
)

type ParsedConfig struct {
	orig config.Config

	Bots              []Bot
	DNSBL             bool
	DefaultDifficulty int
}

func NewParsedConfig(orig config.Config) *ParsedConfig {
	return &ParsedConfig{
		orig: orig,
	}
}

func ParseConfig(fin io.Reader, fname string, defaultDifficulty int) (*ParsedConfig, error) {
	var c config.Config
	if err := yaml.NewYAMLToJSONDecoder(fin).Decode(&c); err != nil {
		return nil, fmt.Errorf("can't parse policy config YAML %s: %w", fname, err)
	}

	if err := c.Valid(); err != nil {
		return nil, err
	}

	var validationErrs []error

	result := NewParsedConfig(c)
	result.DefaultDifficulty = defaultDifficulty

	for _, b := range c.Bots {
		if berr := b.Valid(); berr != nil {
			validationErrs = append(validationErrs, berr)
			continue
		}

		parsedBot := Bot{
			Name:    b.Name,
			Action:  b.Action,
			Headers: map[string]*regexp.Regexp{},
		}

		if len(b.RemoteAddr) > 0 {
			parsedBot.Ranger = cidranger.NewPCTrieRanger()

			for _, cidr := range b.RemoteAddr {
				_, rng, err := net.ParseCIDR(cidr)
				if err != nil {
					return nil, fmt.Errorf("[unexpected] range %s not parsing: %w", cidr, err)
				}

				parsedBot.Ranger.Insert(cidranger.NewBasicRangerEntry(*rng))
			}
		}

		if b.UserAgentRegex != nil {
			userAgent, err := regexp.Compile(*b.UserAgentRegex)
			if err != nil {
				validationErrs = append(validationErrs, fmt.Errorf("while compiling user agent regexp: %w", err))
				continue
			} else {
				parsedBot.UserAgent = userAgent
			}
		}

		if b.PathRegex != nil {
			path, err := regexp.Compile(*b.PathRegex)
			if err != nil {
				validationErrs = append(validationErrs, fmt.Errorf("while compiling path regexp: %w", err))
				continue
			} else {
				parsedBot.Path = path
			}
		}

		if len(b.HeadersRegex) > 0 {
			for name, expr := range b.HeadersRegex {
				if name == "" {
					continue
				}

				header, err := regexp.Compile(expr)
				if err != nil {
					validationErrs = append(validationErrs, fmt.Errorf("while compiling header regexp: %w", err))
					continue
				} else {
					parsedBot.Headers[name] = header
				}
			}
		}

		if b.Challenge == nil {
			parsedBot.Challenge = &config.ChallengeRules{
				Difficulty: defaultDifficulty,
				ReportAs:   defaultDifficulty,
				Algorithm:  config.AlgorithmFast,
			}
		} else {
			parsedBot.Challenge = b.Challenge
			if parsedBot.Challenge.Algorithm == config.AlgorithmUnknown {
				parsedBot.Challenge.Algorithm = config.AlgorithmFast
			}
		}

		result.Bots = append(result.Bots, parsedBot)
	}

	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
}