diff options
| author | Xe Iaso <me@xeiaso.net> | 2025-04-23 07:01:28 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-04-23 07:01:28 -0400 |
| commit | 74e11505c6133ee1107811e81a0fd53e1d7876dd (patch) | |
| tree | 9169e3fccc32657a9a84358bf7e6d7779fa704df /lib/policy/config/config_test.go | |
| parent | 4e2c9de7085fbc8e5abe8d0659d807881d69769c (diff) | |
| download | anubis-74e11505c6133ee1107811e81a0fd53e1d7876dd.tar.xz anubis-74e11505c6133ee1107811e81a0fd53e1d7876dd.zip | |
feat: enable loading config fragments (#321)
* feat(config): support importing bot policy snippets
This changes the grammar of the Anubis bot policy config to allow
importing from internal shared rules or external rules on the
filesystem.
This lets you create a file at `/data/policies/block-evilbot.yaml` and
then import it with:
```yaml
bots:
- import: /data/policies/block-evilbot.yaml
```
This also explodes the default policy file into a bunch of composable
snippets.
Thank you @Aibrew for your example gitea Atom / RSS feed rules!
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(data): update botPolicies.json to use imports
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(cmd/anubis): extract bot policies with --extract-resources
This allows a user that doesn't have anything but the Anubis binary to
figure out what the default configuration does.
* docs(data/botPolices.yaml): document import syntax in-line
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(lib/policy): better test importing from JSON snippets
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs(admin): Add import syntax documentation
This documents the import syntax and is based on the block comment at
the top of the default bot policy file.
* docs(changelog): add note about importing snippets
Signed-off-by: Xe Iaso <me@xeiaso.net>
* style(lib/policy/config): use an error value instead of an inline error
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'lib/policy/config/config_test.go')
| -rw-r--r-- | lib/policy/config/config_test.go | 114 |
1 files changed, 109 insertions, 5 deletions
diff --git a/lib/policy/config/config_test.go b/lib/policy/config/config_test.go index 4176126..86c490e 100644 --- a/lib/policy/config/config_test.go +++ b/lib/policy/config/config_test.go @@ -2,10 +2,12 @@ package config import ( "errors" + "io/fs" "os" "path/filepath" "testing" + "github.com/TecharoHQ/anubis/data" "k8s.io/apimachinery/pkg/util/yaml" ) @@ -219,13 +221,69 @@ func TestConfigValidKnownGood(t *testing.T) { } defer fin.Close() - var c Config - if err := yaml.NewYAMLToJSONDecoder(fin).Decode(&c); err != nil { - t.Fatalf("can't decode file: %v", err) + c, err := Load(fin, st.Name()) + if err != nil { + t.Fatal(err) } if err := c.Valid(); err != nil { - t.Fatal(err) + t.Error(err) + } + + if len(c.Bots) == 0 { + t.Error("wanted more than 0 bots, got zero") + } + }) + } +} + +func TestImportStatement(t *testing.T) { + type testCase struct { + name string + importPath string + err error + } + + var tests []testCase + + for _, folderName := range []string{ + "apps", + "bots", + "common", + "crawlers", + } { + if err := fs.WalkDir(data.BotPolicies, folderName, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + + tests = append(tests, testCase{ + name: "(data)/" + path, + importPath: "(data)/" + path, + err: nil, + }) + + return nil + }); err != nil { + t.Fatal(err) + } + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + is := &ImportStatement{ + Import: tt.importPath, + } + + if err := is.Valid(); err != nil { + t.Errorf("validation error: %v", err) + } + + if len(is.Bots) == 0 { + t.Error("wanted bot definitions, but got none") } }) } @@ -246,7 +304,7 @@ func TestConfigValidBad(t *testing.T) { } defer fin.Close() - var c Config + var c fileConfig if err := yaml.NewYAMLToJSONDecoder(fin).Decode(&c); err != nil { t.Fatalf("can't decode file: %v", err) } @@ -259,3 +317,49 @@ func TestConfigValidBad(t *testing.T) { }) } } + +func TestBotConfigZero(t *testing.T) { + var b BotConfig + if !b.Zero() { + t.Error("zero value BotConfig is not zero value") + } + + b.Name = "hi" + if b.Zero() { + t.Error("BotConfig with name is zero value") + } + + b.UserAgentRegex = p(".*") + if b.Zero() { + t.Error("BotConfig with user agent regex is zero value") + } + + b.PathRegex = p(".*") + if b.Zero() { + t.Error("BotConfig with path regex is zero value") + } + + b.HeadersRegex = map[string]string{"hi": "there"} + if b.Zero() { + t.Error("BotConfig with headers regex is zero value") + } + + b.Action = RuleAllow + if b.Zero() { + t.Error("BotConfig with action is zero value") + } + + b.RemoteAddr = []string{"::/0"} + if b.Zero() { + t.Error("BotConfig with remote addresses is zero value") + } + + b.Challenge = &ChallengeRules{ + Difficulty: 4, + ReportAs: 4, + Algorithm: AlgorithmFast, + } + if b.Zero() { + t.Error("BotConfig with challenge rules is zero value") + } +} |
