diff --git a/nixos/modules/services/networking/anubis.md b/nixos/modules/services/networking/anubis.md index af5d3cf997bc..7c45fa780adf 100644 --- a/nixos/modules/services/networking/anubis.md +++ b/nixos/modules/services/networking/anubis.md @@ -78,15 +78,57 @@ It is possible to configure default settings for all instances of Anubis, via {o ```nix { services.anubis.defaultOptions = { - botPolicy = { - dnsbl = false; - }; settings.DIFFICULTY = 3; }; } ``` -Note that at the moment, a custom bot policy is not merged with the baked-in one. That means to only override a setting -like `dnsbl`, copying the entire bot policy is required. Check -[the upstream repository](https://github.com/TecharoHQ/anubis/blob/1509b06cb921aff842e71fbb6636646be6ed5b46/cmd/anubis/botPolicies.json) -for the policy. +By default, this module uses Anubis's built-in policy (`botPolicies.yaml`), which includes sensible defaults for bot +rules, thresholds, status codes, and storage backend. A custom policy file is only generated when you explicitly +customize the policy via {option}`services.anubis.instances..policy`. + +To add custom bot rules while keeping the defaults: + +```nix +{ + services.anubis.instances.default = { + settings.TARGET = "http://localhost:8000"; + policy.extraBots = [ + { + name = "my-allowed-bot"; + user_agent_regex = "MyBot/.*"; + action = "ALLOW"; + } + ]; + }; +} +``` + +To opt out of the default bot rules entirely and define your own: + +```nix +{ + services.anubis.instances.default = { + settings.TARGET = "http://localhost:8000"; + policy = { + useDefaultBotRules = false; + extraBots = [ + { + name = "my-rule"; + path_regex = ".*"; + action = "CHALLENGE"; + } + ]; + }; + }; +} +``` + +::: {.note} +When you customize the policy, a custom policy file is generated. This file imports the default bot rules via +`(data)/meta/default-config.yaml` when {option}`services.anubis.instances..policy.useDefaultBotRules` is enabled, +but uses Anubis's simpler legacy threshold instead of the 5-tier thresholds from `botPolicies.yaml`. If you need custom +thresholds, specify them in {option}`services.anubis.instances..policy.settings`. +::: + +See [the upstream documentation](https://anubis.techaro.lol/docs/admin/policies) for all available policy options. diff --git a/nixos/modules/services/networking/anubis.nix b/nixos/modules/services/networking/anubis.nix index 37ab52b51b36..5c207a27ec25 100644 --- a/nixos/modules/services/networking/anubis.nix +++ b/nixos/modules/services/networking/anubis.nix @@ -12,6 +12,32 @@ let enabledInstances = lib.filterAttrs (_: conf: conf.enable) cfg.instances; instanceName = name: if name == "" then "anubis" else "anubis-${name}"; + # Only generates a custom policy file when the user has explicitly customized + # something (extraBots, settings, or disabled default bot rules). When nothing + # is customized, returns null so Anubis uses its built-in botPolicies.yaml + # which includes sensible defaults for thresholds, status_codes, store, etc. + mkPolicyFile = + name: instance: + let + hasCustomization = + !instance.policy.useDefaultBotRules + || instance.policy.extraBots != [ ] + || instance.policy.settings != { }; + bots = + (lib.optional instance.policy.useDefaultBotRules { + import = "(data)/meta/default-config.yaml"; + }) + ++ instance.policy.extraBots; + policyContent = { + inherit bots; + } + // instance.policy.settings; + in + if hasCustomization then + jsonFormat.generate "${instanceName name}-policy.json" policyContent + else + null; + unixAddr = network: addr: lib.strings.optionalString (network == "unix") addr; unixSocketAddrs = settings: @@ -40,6 +66,10 @@ let in { name, ... }: { + imports = [ + (lib.mkRenamedOptionModule [ "botPolicy" ] [ "policy" "settings" ]) + ]; + options = { enable = lib.mkEnableOption "this instance of Anubis" // { default = true; @@ -65,18 +95,71 @@ let type = types.str; }; - botPolicy = mkDefaultOption "botPolicy" { - default = null; + policy = lib.mkOption { + default = { }; description = '' - Anubis policy configuration in Nix syntax. Set to `null` to use the baked-in policy which should be - sufficient for most use-cases. - - This option has no effect if `settings.POLICY_FNAME` is set to a different value, which is useful for - importing an existing configuration. + Anubis policy configuration. See [the documentation](https://anubis.techaro.lol/docs/admin/policies) for details. ''; - type = types.nullOr jsonFormat.type; + type = types.submodule { + options = { + useDefaultBotRules = mkDefaultOption "policy.useDefaultBotRules" { + type = types.bool; + default = true; + description = '' + Whether to include Anubis's default bot detection rules via the + `(data)/meta/default-config.yaml` import. + + Set to `false` to define your own bot rules from scratch using + {option}`extraBots`. + ''; + }; + + extraBots = mkDefaultOption "policy.extraBots" { + type = types.listOf jsonFormat.type; + default = [ ]; + example = lib.literalExpression '' + [ + { + name = "my-bot"; + user_agent_regex = "MyBot/.*"; + action = "ALLOW"; + } + ] + ''; + description = '' + Additional bot rules appended to the policy. + + When {option}`useDefaultBotRules` is `true`, these rules are added after + Anubis's default rules. When `false`, only these rules are used. + ''; + }; + + settings = mkDefaultOption "policy.settings" { + type = jsonFormat.type; + default = { }; + example = lib.literalExpression '' + { + dnsbl = false; + store = { + backend = "bbolt"; + parameters.path = "/var/lib/anubis/data.bdb"; + }; + } + ''; + description = '' + Additional policy settings merged into the policy file. + + Common settings include `dnsbl`, `store`, `logging`, `thresholds`, + `impressum`, `openGraph`, and `statusCodes`. + + See [the documentation](https://anubis.techaro.lol/docs/admin/policies) for + available options. + ''; + }; + }; + }; }; extraFlags = mkDefaultOption "extraFlags" { @@ -175,8 +258,8 @@ let POLICY_FNAME = mkDefaultOption "settings.POLICY_FNAME" { default = null; description = '' - The bot policy file to use. Leave this as `null` to respect the value set in - {option}`services.anubis.instances..botPolicy`. + The policy file to use. Leave this as `null` to use the policy generated from + {option}`services.anubis.instances..policy`. ''; type = types.nullOr types.path; }; @@ -306,10 +389,8 @@ in POLICY_FNAME = if instance.settings.POLICY_FNAME != null then instance.settings.POLICY_FNAME - else if instance.botPolicy != null then - jsonFormat.generate "${instanceName name}-botPolicy.json" instance.botPolicy else - null; + mkPolicyFile name instance; } ) ); diff --git a/nixos/tests/anubis.nix b/nixos/tests/anubis.nix index 67f21d2e55be..8dcc3ef496c7 100644 --- a/nixos/tests/anubis.nix +++ b/nixos/tests/anubis.nix @@ -1,36 +1,4 @@ { lib, ... }: -let - legacyBotPolicyJSON = '' - { - "bots": [ - { - "import": "(data)/bots/_deny-pathological.yaml" - }, - { - "import": "(data)/meta/ai-block-aggressive.yaml" - }, - { - "import": "(data)/crawlers/_allow-good.yaml" - }, - { - "import": "(data)/bots/aggressive-brazilian-scrapers.yaml" - }, - { - "import": "(data)/common/keep-internet-working.yaml" - }, - { - "name": "generic-browser", - "user_agent_regex": "Mozilla|Opera", - "action": "CHALLENGE" - } - ], - "dnsbl": false, - "status_codes": { - "CHALLENGE": 200, - "DENY": 200 - } - }''; -in { name = "anubis"; meta.maintainers = with lib.maintainers; [ @@ -54,7 +22,6 @@ in services.anubis = { defaultOptions = { - botPolicy = builtins.fromJSON legacyBotPolicyJSON; settings = { DIFFICULTY = 3; USER_DEFINED_DEFAULT = true; @@ -92,14 +59,29 @@ in }; }; - instances."botPolicy-default" = { - botPolicy = null; + instances."policy-default" = { settings = { TARGET = "http://localhost:8080"; }; }; - instances."botPolicy-file" = { + instances."policy-custom" = { + policy = { + extraBots = [ + { + name = "custom-allow"; + user_agent_regex = "CustomBot/.*"; + action = "ALLOW"; + } + ]; + settings.dnsbl = false; + }; + settings = { + TARGET = "http://localhost:8080"; + }; + }; + + instances."policy-file" = { settings = { TARGET = "http://localhost:8080"; POLICY_FNAME = "/etc/anubis-botPolicy.json"; @@ -191,9 +173,10 @@ in machine.succeed('cat /run/current-system/etc/systemd/system/anubis.service | grep "DIFFICULTY=5"') machine.succeed('cat /run/current-system/etc/systemd/system/anubis-tcp.service | grep "DIFFICULTY=3"') - # Check correct BotPolicy settings are applied - machine.succeed('cat /run/current-system/etc/systemd/system/anubis.service | grep "POLICY_FNAME=/nix/store"') - machine.fail('cat /run/current-system/etc/systemd/system/anubis-botPolicy-default.service | grep "POLICY_FNAME="') - machine.succeed('cat /run/current-system/etc/systemd/system/anubis-botPolicy-file.service | grep "POLICY_FNAME=/etc/anubis-botPolicy.json"') + # Check correct policy settings are applied. + machine.fail('cat /run/current-system/etc/systemd/system/anubis.service | grep "POLICY_FNAME="') + machine.fail('cat /run/current-system/etc/systemd/system/anubis-policy-default.service | grep "POLICY_FNAME="') + machine.succeed('cat /run/current-system/etc/systemd/system/anubis-policy-custom.service | grep "POLICY_FNAME=/nix/store"') + machine.succeed('cat /run/current-system/etc/systemd/system/anubis-policy-file.service | grep "POLICY_FNAME=/etc/anubis-botPolicy.json"') ''; }