From ee19469633c8f02ea8f9189655391c900b30ec06 Mon Sep 17 00:00:00 2001 From: MrAlders0n Date: Wed, 2 Sep 2026 19:52:13 -0400 Subject: [PATCH] Edge access logs, IP header hygiene, block lists, fail2ban jails --- app_config/apache/analyzer-vhost.snippet.conf | 44 +++++++ app_config/caddy/Caddyfile.type1 | 59 +++++++-- app_config/fail2ban/README.md | 123 ++++++++++++++++++ .../apache/filter.d/beacon-api-429.conf | 5 + .../apache/filter.d/beacon-api-flood.conf | 6 + .../fail2ban/apache/jail.d/beacon-http.conf | 31 +++++ .../fail2ban.service.d/after-docker.conf | 4 + .../caddy/filter.d/beacon-api-429.conf | 7 + .../caddy/filter.d/beacon-api-flood.conf | 7 + .../fail2ban/caddy/jail.d/beacon-http.conf | 36 +++++ .../data/Caddy/CaddyFile/Caddyfile.proxy | 59 +++++++-- .../data/Caddy/conf.d/blocklist.caddy | 13 ++ docker-deployment-type1/docker-compose.yml | 2 + 13 files changed, 378 insertions(+), 18 deletions(-) create mode 100644 app_config/apache/analyzer-vhost.snippet.conf create mode 100644 app_config/fail2ban/README.md create mode 100644 app_config/fail2ban/apache/filter.d/beacon-api-429.conf create mode 100644 app_config/fail2ban/apache/filter.d/beacon-api-flood.conf create mode 100644 app_config/fail2ban/apache/jail.d/beacon-http.conf create mode 100644 app_config/fail2ban/caddy/fail2ban.service.d/after-docker.conf create mode 100644 app_config/fail2ban/caddy/filter.d/beacon-api-429.conf create mode 100644 app_config/fail2ban/caddy/filter.d/beacon-api-flood.conf create mode 100644 app_config/fail2ban/caddy/jail.d/beacon-http.conf create mode 100644 docker-deployment-type1/data/Caddy/conf.d/blocklist.caddy diff --git a/app_config/apache/analyzer-vhost.snippet.conf b/app_config/apache/analyzer-vhost.snippet.conf new file mode 100644 index 0000000..3d44faf --- /dev/null +++ b/app_config/apache/analyzer-vhost.snippet.conf @@ -0,0 +1,44 @@ +# Beacon behind a host Apache (the analyzer.meshmapper.net shape). Drop these lines into the +# *:443 VirtualHost next to the existing ProxyPass rules. Needs mod_headers, mod_proxy, +# mod_proxy_http, mod_proxy_wstunnel, mod_authz_core (all already loaded on that box). + + # dedicated log so fail2ban and the runbook can parse it (other_vhosts uses vhost_combined) + ErrorLog ${APACHE_LOG_DIR}/analyzer-ssl-error.log + CustomLog ${APACHE_LOG_DIR}/analyzer-ssl-access.log combined + + # client-IP hygiene: the app trusts these headers, so only Apache may set them. + # mod_proxy re-adds a clean X-Forwarded-For after the unset. + RequestHeader unset X-Forwarded-For early + RequestHeader unset X-Real-IP early + RequestHeader unset True-Client-IP early + RequestHeader set X-Real-IP "%{REMOTE_ADDR}s" + + # scraper User-Agent signatures live in /etc/apache2/beacon-blocklist-ua.conf; a match sets + # beacon_block, which the /api and /ws blocks below reject + Include /etc/apache2/beacon-blocklist-ua.conf + + # manual block list for /api and /ws: edit /etc/apache2/beacon-blocklist.conf, then + # `apachectl configtest && systemctl reload apache2` + + + Require all granted + Require not env beacon_block + Include /etc/apache2/beacon-blocklist.conf + + + + + Require all granted + Require not env beacon_block + Include /etc/apache2/beacon-blocklist.conf + + + +# /etc/apache2/beacon-blocklist.conf — one `Require not ip ` per line. +# Keep the TEST-NET placeholder so the file is never empty: +# +# Require not ip 192.0.2.0/24 +# +# /etc/apache2/beacon-blocklist-ua.conf — one SetEnvIfNoCase per scraper User-Agent signature: +# +# SetEnvIfNoCase User-Agent "mesh\.hansimgamr\.net" beacon_block diff --git a/app_config/caddy/Caddyfile.type1 b/app_config/caddy/Caddyfile.type1 index a7c9eef..45efd60 100644 --- a/app_config/caddy/Caddyfile.type1 +++ b/app_config/caddy/Caddyfile.type1 @@ -1,14 +1,55 @@ # Type 1 (All-in-One): API + Web on same server {$DOMAIN} { - handle /api/* { - reverse_proxy tower-app:8080 - } + # defines @blocked_ip and @blocked_ua; edit conf.d/blocklist.caddy then `caddy reload` + import conf.d/blocklist.caddy - handle /ws { - reverse_proxy tower-app:8080 - } + # JSON access log for the whole site (API, SPA and assets together, so per-IP joins are one file) + log { + output file /var/log/caddy/access.json { + mode 0644 + roll_size 100MiB + roll_keep 20 + roll_keep_for 720h + } + format json + } - handle { - reverse_proxy tower-web:80 - } + # opt-in: 403 API calls whose Referer names another site. Spoofable; enable only if the + # logs show a specific foreign host with sustained volume. + # @foreign_referer { + # header Referer * + # not header Referer https://{$DOMAIN}/* + # } + + handle /api/* { + respond @blocked_ip "Forbidden" 403 { + close + } + respond @blocked_ua "Forbidden" 403 { + close + } + # respond @foreign_referer "Forbidden" 403 + reverse_proxy app:8080 { + # the app trusts these headers for the client IP, so only Caddy may set them + header_up -True-Client-IP + header_up X-Real-IP {remote_host} + } + } + + handle /ws { + respond @blocked_ip "Forbidden" 403 { + close + } + respond @blocked_ua "Forbidden" 403 { + close + } + reverse_proxy app:8080 { + header_up -True-Client-IP + header_up X-Real-IP {remote_host} + } + } + + handle { + reverse_proxy web:80 + } } diff --git a/app_config/fail2ban/README.md b/app_config/fail2ban/README.md new file mode 100644 index 0000000..9d7b07b --- /dev/null +++ b/app_config/fail2ban/README.md @@ -0,0 +1,123 @@ +# Beacon API abuse controls: edge log, block list, fail2ban + +Staged posture: **log → throttle (beacon-server 429s) → firewall-ban repeat offenders.** Real +users include mobile users behind carrier CGNAT, so thresholds are generous and bans are short. + +Two deployment shapes, two file sets: + +| Shape | Edge | Files | +|---|---|---| +| Caddy in Docker (type 1, e.g. dev.meshcore.ca) | `docker-deployment-type1/data/Caddy/…` | `fail2ban/caddy/` | +| Host Apache in front of the containers (analyzer.meshmapper.net) | `app_config/apache/analyzer-vhost.snippet.conf` | `fail2ban/apache/` | + +## 1. Edge: log + client-IP hygiene + manual block list + +**Caddy.** `Caddyfile.proxy` now writes a JSON access log to `data/Caddy/logs/access.json`, strips +`True-Client-IP` and overwrites `X-Real-IP` on the way to the app (beacon-server trusts those headers +for the client IP), and 403s anything listed in `data/Caddy/conf.d/blocklist.caddy`. The compose +file mounts `conf.d` and `logs`; new mounts need one `docker compose up -d caddy`, after that: + +```bash +docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile +docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile +``` + +**Apache.** Apply the snippet to the `*:443` vhost and create `/etc/apache2/beacon-blocklist.conf` +and `/etc/apache2/beacon-blocklist-ua.conf`, then `apachectl configtest && systemctl reload apache2`. + +**Two ways to block.** By source IP (`remote_ip` / `Require not ip`) or by User-Agent signature +(`@blocked_ua` / `SetEnvIfNoCase`). A scraper that identifies itself, like the +`mesh.hansimgamr.net am-i-connected` client found on day 0, is better blocked by UA: it survives an +IP change and leaves the operator's own browser alone. Both apply to `/api/*` and `/ws` only. + +Smoke test on either box (chi's log line must show the real client IP, not 1.2.3.4): + +```bash +curl -s -o /dev/null -w '%{http_code}\n' -H 'True-Client-IP: 1.2.3.4' -H 'X-Real-IP: 1.2.3.4' https://$DOMAIN/api/v1/scopes +docker compose logs --tail 5 app +``` + +## 2. fail2ban + +Two jails per box, same thresholds: + +| Jail | Counts | Trips at | Why | +|---|---|---|---| +| `beacon-api-flood` | every `/api/` request | 900 in 60 s | a browser session peaks ~150 in its first 30 s (auto-chained pages), then ~2/min; 15 req/s for a full minute is no browser | +| `beacon-api-429` | `/api/` responses with status 429 | 300 in 10 min | only fires on clients that keep hammering through the server's throttle | + +Both use `backend = polling` (the Debian default `systemd` cannot tail files) and a flat 10 min ban. +Install: + +```bash +sudo cp filter.d/*.conf /etc/fail2ban/filter.d/ +sudo cp jail.d/beacon-http.conf /etc/fail2ban/jail.d/ +# Caddy box only: the ban chain must come up after dockerd +sudo install -D fail2ban.service.d/after-docker.conf /etc/systemd/system/fail2ban.service.d/after-docker.conf +sudo systemctl daemon-reload && sudo systemctl restart fail2ban +sudo fail2ban-regex /etc/fail2ban/filter.d/beacon-api-flood.conf # date hits > 0, matches == /api/ lines +sudo fail2ban-client status beacon-api-flood +``` + +**Observe first.** The Caddy jail file ships with `banaction = dummy`; on the Apache box add the same +line per jail. Would-be bans land in `/var/run/fail2ban/fail2ban.dummy`. Run 3–7 days; every IP +there must be an obvious scraper, otherwise raise `maxretry`. + +**Enforce.** Caddy box: traffic to Docker-published ports is DNAT'ed through the FORWARD path, which +the default nftables action (input hook) never sees, so switch to +`banaction = nftables[type=allports, chain=f2b-forward, chain_hook=forward]` (a second chain in the +existing `f2b-table`; `iptables-allports[chain=DOCKER-USER]` is equivalent). Apache box: remove the +`dummy` line, the inherited nftables input-hook action works because Apache listens on the host. +Then: + +```bash +sudo fail2ban-client reload +sudo nft list table inet f2b-table # addr-set-beacon-api-flood present +sudo fail2ban-client set beacon-api-flood banip # site unreachable from it +sudo fail2ban-client set beacon-api-flood unbanip +``` + +Once beacon-server's per-IP limit is live, re-derive the flood `maxretry` as ~3× the per-minute +allowance. Consider `bantime.increment` only after a clean week. + +## 3. Finding scrapers by hand + +Caddy JSON (`LOG=/opt/docker/beacon/data/Caddy/logs/access.json`): + +```bash +# top IPs, last hour +jq -r 'select(.ts > (now-3600)) | .request.remote_ip' "$LOG" | sort | uniq -c | sort -rn | head -20 +# IPs with >200 /api/ hits and zero /ws upgrades, last 24h (the SPA always opens a socket) +jq -r 'select(.ts > (now-86400)) | [.request.remote_ip, (.request.uri|startswith("/api/")|tostring), (.request.uri=="/ws"|tostring)] | @tsv' "$LOG" \ + | awk -F'\t' '{a[$1]+=($2=="true"); w[$1]+=($3=="true")} END {for (ip in a) if (a[ip]>200 && w[ip]==0) print a[ip], ip}' | sort -rn | head +# IPs asking limit= above 200 (the SPA never does) +jq -r '(.request.uri|capture("[?&]limit=(?[0-9]+)")?) as $c | select($c != null and ($c.n|tonumber) > 200) | .request.remote_ip' "$LOG" | sort | uniq -c | sort -rn | head +# Referer / Origin hosts hitting /api/ (a reverse-proxying site usually forwards its visitors' Referer) +jq -r 'select(.request.uri|startswith("/api/")) | [(.request.headers.Referer[0] // "-"), (.request.headers.Origin[0] // "-")] | @tsv' "$LOG" \ + | sed -E 's#(https?://[^/[:space:]]+)[^[:space:]]*#\1#g' | sort | uniq -c | sort -rn | head -30 +# IPs with >50 /api/ hits and no SPA/asset fetch, last 24h (a native app looks like this too) +jq -r 'select(.ts > (now-86400)) | [.request.remote_ip, (.request.uri|startswith("/api/")|tostring), ((.request.uri|startswith("/api/")|not) and .request.uri!="/ws"|tostring)] | @tsv' "$LOG" \ + | awk -F'\t' '{a[$1]+=($2=="true"); s[$1]+=($3=="true")} END {for (ip in a) if (a[ip]>50 && s[ip]==0) print a[ip], ip}' | sort -rn | head +# peak /api/ requests per IP per minute: calibrate maxretry / the server limit at ~3x the largest legitimate bucket +jq -r 'select(.request.uri|startswith("/api/")) | "\(.request.remote_ip) \((.ts/60)|floor)"' "$LOG" | sort | uniq -c | sort -rn | head -20 +# spoof attempts (client-sent IP headers are logged as received) +jq -r 'select(.request.headers["True-Client-IP"] or .request.headers["X-Real-IP"]) | [.request.remote_ip, (.request.headers["True-Client-IP"][0]//"-")] | @tsv' "$LOG" | sort | uniq -c | sort -rn | head +``` + +Apache combined (`LOG=/var/log/apache2/analyzer-ssl-access.log`, run with sudo; `$7` is the request +path, `$11` the quoted Referer): + +```bash +awk '{print $1}' "$LOG" | sort | uniq -c | sort -rn | head -20 +awk '$7 ~ /^\/api\// {a[$1]++} $7=="/ws" {w[$1]++} END {for (ip in a) if (a[ip]>200 && !(ip in w)) print a[ip], ip}' "$LOG" | sort -rn | head +awk '$7 ~ /^\/api\/.*[?&]limit=/ {match($7,/[?&]limit=[0-9]+/); n=substr($7,RSTART+7,RLENGTH-7); if (n+0>200) print $1}' "$LOG" | sort | uniq -c | sort -rn | head +awk '$7 ~ /^\/api\// {print $11}' "$LOG" | sed -E 's#"(https?://[^/"]+).*#\1#' | sort | uniq -c | sort -rn | head -30 +awk '$7 ~ /^\/api\// {a[$1]++} $7 !~ /^\/api\// && $7!="/ws" {s[$1]++} END {for (ip in a) if (a[ip]>50 && !(ip in s)) print a[ip], ip}' "$LOG" | sort -rn | head +awk '$7 ~ /^\/api\// {print $1, substr($4,2,17)}' "$LOG" | sort | uniq -c | sort -rn | head -20 +``` + +A site that reverse-proxies the API shows up as one high-volume, API-only IP with no `/ws` and +usually a foreign Referer. Put it in the block list and reload. + +Operations: `fail2ban-client status `, `fail2ban-client get banip --with-time`, +`fail2ban-client set unbanip `, `fail2ban-client reload ` after editing a filter. diff --git a/app_config/fail2ban/apache/filter.d/beacon-api-429.conf b/app_config/fail2ban/apache/filter.d/beacon-api-429.conf new file mode 100644 index 0000000..5a0d409 --- /dev/null +++ b/app_config/fail2ban/apache/filter.d/beacon-api-429.conf @@ -0,0 +1,5 @@ +# fail2ban filter: beacon-api-429 +# Requests to /api/ that beacon-server already throttled (status 429). Only persistence is punished. +[Definition] +failregex = ^ \S+ \S+ \[?\]? ?"[A-Z]+ /api/[^"]*" 429 +ignoreregex = diff --git a/app_config/fail2ban/apache/filter.d/beacon-api-flood.conf b/app_config/fail2ban/apache/filter.d/beacon-api-flood.conf new file mode 100644 index 0000000..89f5b5f --- /dev/null +++ b/app_config/fail2ban/apache/filter.d/beacon-api-flood.conf @@ -0,0 +1,6 @@ +# fail2ban filter: beacon-api-flood +# Apache `combined` log (Ubuntu variant). Every request to /api/ from an IP counts as one hit; +# the jail's maxretry/findtime decide what "too many" is. fail2ban has already cut the date out. +[Definition] +failregex = ^ \S+ \S+ \[?\]? ?"[A-Z]+ /api/ +ignoreregex = diff --git a/app_config/fail2ban/apache/jail.d/beacon-http.conf b/app_config/fail2ban/apache/jail.d/beacon-http.conf new file mode 100644 index 0000000..1752db2 --- /dev/null +++ b/app_config/fail2ban/apache/jail.d/beacon-http.conf @@ -0,0 +1,31 @@ +# Beacon API jails for a host-Apache deployment (analyzer.meshmapper.net shape). +# Same conventions as meshmapper-http.conf on that box: no [DEFAULT], backend = polling per jail, +# ignoreip = loopback, Tailscale, the Subscriber. +# banaction = dummy is observe mode (would-be bans land in /var/run/fail2ban/fail2ban.dummy). +# To enforce, delete those lines: the inherited default (nftables, input hook) works as-is because +# Apache listens on the host. bantime is a flat 10m to start: a banned CGNAT address hurts every +# user behind it. + +[beacon-api-flood] +enabled = true +backend = polling +filter = beacon-api-flood +port = http,https +banaction = dummy +logpath = /var/log/apache2/analyzer-ssl-access.log +maxretry = 900 +findtime = 60 +bantime = 10m +ignoreip = 127.0.0.1/8 ::1 100.64.0.0/10 192.99.151.15 + +[beacon-api-429] +enabled = true +backend = polling +filter = beacon-api-429 +port = http,https +banaction = dummy +logpath = /var/log/apache2/analyzer-ssl-access.log +maxretry = 300 +findtime = 600 +bantime = 10m +ignoreip = 127.0.0.1/8 ::1 100.64.0.0/10 192.99.151.15 diff --git a/app_config/fail2ban/caddy/fail2ban.service.d/after-docker.conf b/app_config/fail2ban/caddy/fail2ban.service.d/after-docker.conf new file mode 100644 index 0000000..cd64841 --- /dev/null +++ b/app_config/fail2ban/caddy/fail2ban.service.d/after-docker.conf @@ -0,0 +1,4 @@ +# /etc/systemd/system/fail2ban.service.d/after-docker.conf +# The forward-hook / DOCKER-USER ban chain only makes sense once dockerd has set up its rules. +[Unit] +After=docker.service diff --git a/app_config/fail2ban/caddy/filter.d/beacon-api-429.conf b/app_config/fail2ban/caddy/filter.d/beacon-api-429.conf new file mode 100644 index 0000000..5601052 --- /dev/null +++ b/app_config/fail2ban/caddy/filter.d/beacon-api-429.conf @@ -0,0 +1,7 @@ +# fail2ban filter: beacon-api-429 +# Requests to /api/ that beacon-server already throttled (status 429). A client that keeps hammering +# through 429s is ignoring the app's own limit; this jail only enforces persistence. +[Definition] +datepattern = "ts":{EPOCH} +failregex = ^\{"level":"\w+",.*?"request":\{"remote_ip":"","remote_port":"\d+",.*?"uri":"/api/.*?"status":429, +ignoreregex = diff --git a/app_config/fail2ban/caddy/filter.d/beacon-api-flood.conf b/app_config/fail2ban/caddy/filter.d/beacon-api-flood.conf new file mode 100644 index 0000000..d022424 --- /dev/null +++ b/app_config/fail2ban/caddy/filter.d/beacon-api-flood.conf @@ -0,0 +1,7 @@ +# fail2ban filter: beacon-api-flood +# Caddy JSON access log, one object per line. Every request to /api/ from an IP counts as one hit; +# the jail's maxretry/findtime decide what "too many" is. +[Definition] +datepattern = "ts":{EPOCH} +failregex = ^\{"level":"\w+",.*?"request":\{"remote_ip":"","remote_port":"\d+",.*?"uri":"/api/ +ignoreregex = diff --git a/app_config/fail2ban/caddy/jail.d/beacon-http.conf b/app_config/fail2ban/caddy/jail.d/beacon-http.conf new file mode 100644 index 0000000..2f45414 --- /dev/null +++ b/app_config/fail2ban/caddy/jail.d/beacon-http.conf @@ -0,0 +1,36 @@ +# Beacon API jails for a Caddy-in-Docker deployment (type 1). See app_config/fail2ban/README.md. +# +# DELIBERATE choices: +# - No [DEFAULT] section: it would merge into the global default and change the sshd jail. +# - backend = polling per jail: the Debian default is systemd, which cannot tail log FILES. +# - banaction: traffic to Docker-published 80/443 is DNAT'ed through the FORWARD path, so the +# default nftables action (input hook) never sees it. Start in observe mode (dummy), then switch +# to a forward-hook chain in the existing f2b-table: +# nftables[type=allports, chain=f2b-forward, chain_hook=forward] +# (equivalent: iptables-allports[chain=DOCKER-USER]) +# - ignoreip: loopback, RFC1918 (Docker bridges), Tailscale. Add the operator's home IP if wanted. +# - bantime is a flat 10m: a banned CGNAT address hurts every user behind it. + +[beacon-api-flood] +enabled = true +backend = polling +filter = beacon-api-flood +logpath = /opt/docker/beacon/data/Caddy/logs/access.json +banaction = dummy +ignoreip = 127.0.0.0/8 ::1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 100.64.0.0/10 +findtime = 60 +maxretry = 900 +bantime = 10m +maxmatches = 5 + +[beacon-api-429] +enabled = true +backend = polling +filter = beacon-api-429 +logpath = /opt/docker/beacon/data/Caddy/logs/access.json +banaction = dummy +ignoreip = 127.0.0.0/8 ::1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 100.64.0.0/10 +findtime = 10m +maxretry = 300 +bantime = 10m +maxmatches = 5 diff --git a/docker-deployment-type1/data/Caddy/CaddyFile/Caddyfile.proxy b/docker-deployment-type1/data/Caddy/CaddyFile/Caddyfile.proxy index 10142c6..88835dd 100644 --- a/docker-deployment-type1/data/Caddy/CaddyFile/Caddyfile.proxy +++ b/docker-deployment-type1/data/Caddy/CaddyFile/Caddyfile.proxy @@ -1,13 +1,54 @@ {$DOMAIN} { - handle /api/* { - reverse_proxy tower-app:8080 - } + # defines @blocked_ip and @blocked_ua; edit conf.d/blocklist.caddy then `caddy reload` + import conf.d/blocklist.caddy - handle /ws { - reverse_proxy tower-app:8080 - } + # JSON access log for the whole site (API, SPA and assets together, so per-IP joins are one file) + log { + output file /var/log/caddy/access.json { + mode 0644 + roll_size 100MiB + roll_keep 20 + roll_keep_for 720h + } + format json + } - handle { - reverse_proxy tower-web:80 - } + # opt-in: 403 API calls whose Referer names another site. Spoofable; enable only if the + # logs show a specific foreign host with sustained volume. + # @foreign_referer { + # header Referer * + # not header Referer https://{$DOMAIN}/* + # } + + handle /api/* { + respond @blocked_ip "Forbidden" 403 { + close + } + respond @blocked_ua "Forbidden" 403 { + close + } + # respond @foreign_referer "Forbidden" 403 + reverse_proxy app:8080 { + # the app trusts these headers for the client IP, so only Caddy may set them + header_up -True-Client-IP + header_up X-Real-IP {remote_host} + } + } + + handle /ws { + respond @blocked_ip "Forbidden" 403 { + close + } + respond @blocked_ua "Forbidden" 403 { + close + } + reverse_proxy app:8080 { + header_up -True-Client-IP + header_up X-Real-IP {remote_host} + } + } + + handle { + reverse_proxy web:80 + } } diff --git a/docker-deployment-type1/data/Caddy/conf.d/blocklist.caddy b/docker-deployment-type1/data/Caddy/conf.d/blocklist.caddy new file mode 100644 index 0000000..6de87d4 --- /dev/null +++ b/docker-deployment-type1/data/Caddy/conf.d/blocklist.caddy @@ -0,0 +1,13 @@ +# Manual block list for /api/* and /ws. +# @blocked_ip: one IP or CIDR per remote_ip line. Keep at least one line: 192.0.2.0/24 is TEST-NET +# (never routable) and is a safe placeholder. +# @blocked_ua: one header_regexp line per scraper User-Agent signature (Go regexp; (?i) = case-insensitive). +# Apply changes with: +# docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile +# docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile +@blocked_ip { + remote_ip 192.0.2.0/24 +} +@blocked_ua { + header_regexp User-Agent "(?i)mesh\.hansimgamr\.net" +} diff --git a/docker-deployment-type1/docker-compose.yml b/docker-deployment-type1/docker-compose.yml index a7da144..5e728e8 100644 --- a/docker-deployment-type1/docker-compose.yml +++ b/docker-deployment-type1/docker-compose.yml @@ -53,6 +53,8 @@ services: env_file: .env volumes: - ./data/Caddy/CaddyFile/Caddyfile.proxy:/etc/caddy/Caddyfile + - ./data/Caddy/conf.d:/etc/caddy/conf.d:ro + - ./data/Caddy/logs:/var/log/caddy - ./data/Caddy/data:/data/caddy/ - ./data/Caddy/config:/config/caddy/ depends_on: