Some people call people who block whole ASNs outright 'lazy sysadmins' and I don't want to be called that! So I'm not blocking them! They can still get in! If they're authorized, of course.
This Caddy plugin shunts IPs that I don't like straight into my forward_auth, so that one Singaporean datacenter server stops trying to crawl literally every PDF on my personal archive.
Derived from zhangjiayin/caddy-geoip2, licensed under the Apache License, Version 2.0. This repository tosses EVERYTHING out but the ASN and country lookup and makes it easier to pull in an ASN blocklist like brianhama/bad-asn-list.
Below is an example configuration for a domain protected(-ish) by this plugin. Here, no one but IPs originating from the United States can access the site, and whatever ASNs were blocked by the disliked_asns list, which you can grab from the blocklist I linked above.
You can find the blocklist format at disliked_asns.txt.example and the below Caddyfile sample at Caddyfile.example.
example.com {
route {
susip {
country_db /var/lib/GeoIP/GeoLite2-Country.mmdb
asn_db /var/lib/GeoIP/GeoLite2-ASN.mmdb
# optional paid Maxmind dataset for VPN, proxy, Tor, hosting-provider flags
anonymous_db /var/lib/GeoIP/GeoIP2-Anonymous-IP.mmdb
# example country policy: block all IPs but those from the United States
# (needs country_db)
allow_countries US
unknown_country_disliked true
# setup for importing list of ASN you don't like (e.g. Azure or AWS)
# (needs asn_db)
disliked_asns {
import /etc/caddy/disliked_asns.txt
}
}
@needs_auth expression `{susip.requires_auth} == true`
forward_auth @needs_auth authelia:9091 {
uri /api/authz/forward-auth
header_up X-Susip-IP {susip.client_ip}
header_up X-Susip-Country {susip.country_code}
header_up X-Susip-ASN {susip.asn}
header_up X-Susip-ASN-Org {susip.asn_org}
header_up X-Susip-ASN-Network {susip.asn_network}
header_up X-Susip-Reason {susip.reason}
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
}
reverse_proxy service:8080
}
}You can grab a binary compiled with the caddy-dns/cloudflare and mholt/caddy-ratelimit from the GitHub Releases page. (why these plugins, you ask? because I use them.)
You can compile it yourself by pulling down the repository and building it. Below creates a binary that you can run on a Raspberry Pi 4B running Raspbian Trixie 64-bit.
git clone https://github.com/ganyuke/caddy-susip && cd caddy-susip
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
CADDY_VERSION=v2.11.4 CGO_ENABLED=0 GOOS=linux GOARCH=arm64 "$(go env GOPATH)/bin/xcaddy" build "$CADDY_VERSION" \
--with github.com/ganyuke/caddy-susip=. \
--with github.com/caddy-dns/cloudflare@latest \
--with github.com/mholt/caddy-ratelimit@latest \
--output "caddy_${GOOS}_${GOARCH}"Make sure you include something like the following in your global Caddyfile before you use this so you don't end up gating everything because Caddy doesn't trust localhost:
{
servers {
trusted_proxies static 127.0.0.0/8 ::1/128
}
}And if you are behind Cloudflare, you might want to pull down the list of Cloudflare IPs to trust as proxies. So you aren't blocking your real orange cloud traffic.
See the cloudflare.sh file for a script to create the trusted_proxies list (with localhost) automagically. You can then import the file into your Caddyfile global configuration like so:
{
servers {
import /opt/caddy/blocklists/cloudflare_ips.txt
trusted_proxies_strict
client_ip_headers Cf-Connecting-Ip
}
}And if you don't want to use the route directive later, you just need to order this module before forward_auth:
{
order susip before forward_auth
}susip {
country_db /var/lib/GeoIP/GeoLite2-Country.mmdb
asn_db /var/lib/GeoIP/GeoLite2-ASN.mmdb
anonymous_db /var/lib/GeoIP/GeoIP2-Anonymous-IP.mmdb
allow_countries US
unknown_country_disliked true
disliked_asns {
16276
AS24940
}
}You MUST supply a path to at least one of the three Maxmind datasets:
country_dbasn_dbanonymous_db
Additionally, the following directives require the following datasets to be provided:
allow_countries:country_dbdisliked_asns:asn_db
This project does not provide any download or updater for the Maxmind datasets.
| Directive | Default | Description |
|---|---|---|
country_db |
unset | MMDB path. Accepts GeoLite2/GeoIP2 Country or City databases. |
asn_db |
unset | MMDB path. Accepts GeoLite2-ASN or GeoIP2-ASN. |
anonymous_db |
unset | MMDB path. Must be GeoIP2-Anonymous-IP. Enables VPN/proxy/Tor/hosting detection. |
allow_countries |
unset | One or more ISO 3166-1 alpha-2 codes, separated by spaces. |
unknown_country_disliked |
true |
When allow_countries is set, unknown-country IPs require auth unless this is false. |
disliked_asns |
unset | Block of ASNs, one per line, with optional AS prefix. |
disliked_asns takes a block (not inline arguments), so you can import your newline text list like so with Caddy's import directive:
disliked_asns {
import /etc/caddy/disliked_asns.txt
}| Placeholder | Value |
|---|---|
{susip.client_ip} |
Client IP that was analyzed |
{susip.country_code} |
Upper-case ISO country code via country, falls back to registered_country (empty if unknown) |
{susip.country_found} |
Whether a country was found |
{susip.country_allowed} |
Whether the country passes allow_countries (true when unset) |
{susip.is_foreign} |
Country found, not allowed, and allow_countries configured |
{susip.asn} |
Autonomous system number |
{susip.asn_org} |
ASN organization |
{susip.asn_network} |
CIDR network of the ASN record |
{susip.asn_found} |
Whether an ASN was found |
{susip.is_disliked_asn} |
ASN is in disliked_asns |
{susip.is_anonymous} |
Raw MaxMind is_anonymous flag |
{susip.is_anonymous_vpn} |
Raw MaxMind is_anonymous_vpn flag |
{susip.is_hosting_provider} |
Raw MaxMind is_hosting_provider flag |
{susip.is_public_proxy} |
Raw MaxMind is_public_proxy flag |
{susip.is_residential_proxy} |
Raw MaxMind is_residential_proxy flag |
{susip.is_tor_exit_node} |
Raw MaxMind is_tor_exit_node flag |
{susip.is_proxy} |
Any of the anonymous/VPN/public-proxy/residential-proxy/Tor flags |
{susip.is_datacenter} |
Same as {susip.is_hosting_provider} |
{susip.is_ip_sus} |
Disliked ASN or any anonymous-IP flag |
{susip.sus_reason} |
Comma-separated sus reasons |
{susip.requires_auth} |
Country disliked or IP sus (use as forward_auth condition) |
{susip.reason} |
Comma-separated country and sus reasons |
Possible reasons: foreign_country, unknown_country, disliked_asn:<asn>, hosting_provider, tor_exit, anonymous_vpn, public_proxy, residential_proxy, anonymous.