cloudflare — blocking at the edge

Blocks IPs remotely via the Cloudflare v4 API using IP access rules — the equivalent of the fail2ban cloudflare/cloudflare-token actions, done with LWP::UserAgent rather than curl. The ban never touches the local host; Cloudflare stops the traffic before it reaches you.

[kur.cf]
backend = "cloudflare"

[kur.cf.options]
token = "your-scoped-api-token"
zone  = "your-zone-id"
mode  = "block"

What it creates

One IP access rule per banned IP, at the zone level when zone is set and at the user (account-wide) level otherwise:

POST https://api.cloudflare.com/client/v4/zones/<zone>/firewall/access_rules/rules
{ "mode": "block",
  "configuration": { "target": "ip", "value": "1.2.3.4" },
  "notes": "kur_cf" }

IPv6 IPs use "target": "ip6". The notes value is the rule's identity: unban looks the rule ID up by mode + notes + target + value and DELETEs it.

Cloudflare-side setup — required first

Requirements

Settings

Options

| option | default | what | |-----------|-------------------|-------------------------------------------------------------------| | token | (unset) | API token, sent as Authorization: Bearer <token> | | email | (unset) | account email for legacy auth, sent as X-Auth-Email | | key | (unset) | legacy global API key, sent as X-Auth-Key | | zone | (unset) | zone ID (hex) to manage rules under; unset = user level | | mode | block | block, challenge, js_challenge, or managed_challenge | | notes | <prefix>_<name> | note on created rules — also how they are found again | | timeout | 30 | HTTP timeout in seconds |

Either token or both email and key must be set; anything else fails at kur startup.

mode is what Cloudflare does with matching requests — block refuses outright, the challenge modes interpose a CAPTCHA / JS proof / managed challenge instead, useful when you would rather turn back than banish.

What each operation does

| operation | API traffic | |------------|------------------------------------------------------------------------------| | init | GET <endpoint>?per_page=5 — verifies credentials and endpoint | | ban | POST <endpoint> with mode/configuration/notes | | unban | GET <endpoint>?mode=..&notes=..&configuration.target=..&configuration.value=.. to find the rule ID, then DELETE <endpoint>/<id> | | list | no API call — the kur's own ban book | | check | same probe as init | | flush | the unban lookup+DELETE per banned IP | | re_init | teardown (best effort), init, re-POST every banned IP | | teardown | the unban lookup+DELETE per banned IP (ban book kept for re_init) |

<endpoint> is .../zones/<zone>/firewall/access_rules/rules or .../user/firewall/access_rules/rules.

API-level failures die with the HTTP status and Cloudflare's error codes/messages included, which is what lands in the kur's error responses and log.

self_heal and remote drift

check verifies the endpoint and credentials still answer — it does not verify individual rules still exist, so a rule someone deleted in the dashboard stays deleted until re_init. The converse is handled: unbanning an IP whose rule is already gone is treated as already-unbanned, not an error.

Gotchas