From ec97c06d07952dfe596f68a96bdd8beffa350c36 Mon Sep 17 00:00:00 2001 From: gitea Date: Fri, 20 Feb 2026 16:07:56 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20full=20audit=20pass=20=E2=80=94=20placeh?= =?UTF-8?q?older=20logs,=20jq,=20allowipv6,=20stale=20refs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - entrypoint: create proxy-host-placeholder_access.log if no NPM logs exist so fail2ban can start before any proxy hosts are configured - docker-compose: remove :ro from nginx-logs mount (needed for placeholder) - Dockerfile: add jq (required by cloudflare.conf actionunban) - cloudflare.conf: replace python3 JSON parsing with jq; clean stale comments - jail.local + jail.cloudflare.local: add allowipv6 = auto to suppress warning - jail.cloudflare.local: remove stale .env and docker-compose.cloudflare.yml refs Co-Authored-By: Claude Sonnet 4.6 --- Dockerfile | 1 + docker-compose.yml | 2 +- entrypoint.sh | 13 +++++++------ fail2ban/action.d/cloudflare.conf | 23 ++++++----------------- fail2ban/jail.cloudflare.local | 20 ++++++++------------ fail2ban/jail.local | 7 ++++--- 6 files changed, 27 insertions(+), 39 deletions(-) diff --git a/Dockerfile b/Dockerfile index 66e549d..dee053b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ iptables \ ipset \ curl \ + jq \ && rm -rf /var/lib/apt/lists/* \ # Remove debian default jail (enables sshd which has no log file in container) && rm -f /etc/fail2ban/jail.d/defaults-debian.conf diff --git a/docker-compose.yml b/docker-compose.yml index e58647e..d4795dc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,7 +33,7 @@ services: MANUAL_JAIL: "manual-bans" BAN_HIST_FILE: "/data/ban-history.json" volumes: - - ./data/npm/logs:/nginx-logs:ro + - ./data/npm/logs:/nginx-logs - f2b-data:/data - f2b-config:/etc/fail2ban diff --git a/entrypoint.sh b/entrypoint.sh index 1579d12..c66507e 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -52,12 +52,13 @@ mkdir -p /data /var/log /var/run/fail2ban # Create fail2ban log file if it doesn't exist (prevents startup errors) touch /var/log/fail2ban.log -# Ensure nginx-logs directory exists (warn if empty/unmounted) -if [ ! -d /nginx-logs ] || [ -z "$(ls -A /nginx-logs 2>/dev/null)" ]; then - echo "[f2b-cc] WARNING: /nginx-logs appears empty or unmounted." - echo "[f2b-cc] Set DATA_DIR in .env so NPM logs are bind-mounted here." - echo "[f2b-cc] Log scanning will not return results until logs are available." - mkdir -p /nginx-logs +# Ensure nginx-logs directory exists and has at least one file matching the glob. +# fail2ban requires a matching file to exist at startup — create a placeholder +# if NPM hasn't generated any proxy host logs yet. +mkdir -p /nginx-logs +if ! ls /nginx-logs/proxy-host-*_access.log > /dev/null 2>&1; then + echo "[f2b-cc] No NPM logs found — creating placeholder so fail2ban can start." + touch /nginx-logs/proxy-host-placeholder_access.log fi # ── Start supervisord (manages fail2ban + dashboard) ───────────────────────── diff --git a/fail2ban/action.d/cloudflare.conf b/fail2ban/action.d/cloudflare.conf index 3da8ce5..d4eccac 100644 --- a/fail2ban/action.d/cloudflare.conf +++ b/fail2ban/action.d/cloudflare.conf @@ -1,22 +1,12 @@ [Definition] -# ── Cloudflare IP Access Rules action ──────────────────────────────────────── -# # Blocks/unblocks IPs at the Cloudflare account level via the Access Rules API. -# When enabled, a ban will be enforced by Cloudflare before traffic even -# reaches your server — the most effective layer for high-volume attackers. +# Bans are enforced by Cloudflare before traffic reaches your server. +# Enable by setting CF_EMAIL + CF_APIKEY in docker-compose.yml. # -# SETUP: -# 1. Get your Global API Key from: -# https://dash.cloudflare.com/profile/api-tokens -# 2. Set CF_EMAIL and CF_APIKEY in your .env file -# 3. Use docker-compose.cloudflare.yml instead of docker-compose.yml -# -# NOTE: This uses the user-level Access Rules API, which applies the block -# across all zones on your Cloudflare account. For zone-scoped rules, -# replace the URL with: -# https://api.cloudflare.com/client/v4/zones//firewall/access_rules/rules -# ───────────────────────────────────────────────────────────────────────────── +# NOTE: Uses the user-level API — applies across all zones on your account. +# For zone-scoped rules replace the URL with: +# https://api.cloudflare.com/client/v4/zones//firewall/access_rules/rules actionban = curl -s -X POST \ -H "X-Auth-Email: %(cf_email)s" \ @@ -30,7 +20,7 @@ actionunban = RULE_ID=$(curl -s \ -H "X-Auth-Email: %(cf_email)s" \ -H "X-Auth-Key: %(cf_apikey)s" \ "https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules?configuration_target=ip&configuration_value=&mode=block&page=1&per_page=1" | \ - python3 -c "import sys,json; r=json.load(sys.stdin).get('result',[]); print(r[0]['id'] if r else '')" 2>/dev/null) ; \ + jq -r '.result[0].id // empty' 2>/dev/null) ; \ [ -n "$RULE_ID" ] && \ curl -s -X DELETE \ -H "X-Auth-Email: %(cf_email)s" \ @@ -39,6 +29,5 @@ actionunban = RULE_ID=$(curl -s \ > /dev/null 2>&1 || true [Init] -# Populated from environment via jail.local — do not set here cf_email = cf_apikey = diff --git a/fail2ban/jail.cloudflare.local b/fail2ban/jail.cloudflare.local index 596bb6c..338f847 100644 --- a/fail2ban/jail.cloudflare.local +++ b/fail2ban/jail.cloudflare.local @@ -1,24 +1,20 @@ # ── F2B Control Center — jail configuration (Cloudflare) ───────────────────── -# -# Used when CF_EMAIL and CF_APIKEY are set (docker-compose.cloudflare.yml). -# Identical to jail.local but adds the cloudflare action to every jail so -# bans are enforced at both the iptables and Cloudflare WAF levels. -# -# CF credentials are read from environment variables — no credentials are -# stored in this file. +# Installed when CF_EMAIL + CF_APIKEY are set in docker-compose.yml. +# Adds the Cloudflare WAF action to every jail alongside iptables. +# Credentials are injected from environment — not stored here. # ───────────────────────────────────────────────────────────────────────────── [DEFAULT] -bantime = 1h -findtime = 10m -maxretry = 5 +bantime = 1h +findtime = 10m +maxretry = 5 +allowipv6 = auto # Populated by entrypoint from SUBNETS_TO_IGNORE env var on first run. # Updated live by the dashboard — do not edit by hand. ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 -# Cloudflare credentials — injected from environment at runtime. -# Set CF_EMAIL and CF_APIKEY in your .env file. +# Cloudflare credentials — set CF_EMAIL and CF_APIKEY in docker-compose.yml. cf_email = %(ENV[CF_EMAIL])s cf_apikey = %(ENV[CF_APIKEY])s diff --git a/fail2ban/jail.local b/fail2ban/jail.local index f0bd096..6b92ce0 100644 --- a/fail2ban/jail.local +++ b/fail2ban/jail.local @@ -6,9 +6,10 @@ # ───────────────────────────────────────────────────────────────────────────── [DEFAULT] -bantime = 1h -findtime = 10m -maxretry = 5 +bantime = 1h +findtime = 10m +maxretry = 5 +allowipv6 = auto # Populated by entrypoint from SUBNETS_TO_IGNORE env var on first run. # Updated live by the dashboard — do not edit by hand.