feat: plug-and-play refactor — docker-npm action, CF support, whitelist live-update
- Replace iptables-allports with docker-npm action (DOCKER-USER + xt_string X-Forwarded-For matching + INPUT chain) matching user's working setup - Add telegram_notif.sh (deployed to /data/action.d/ at first run, user-editable) - Add cloudflare.conf action; jail.cloudflare.local enabled via CF compose file - Two compose files: docker-compose.yml (standard) and docker-compose.cloudflare.yml - entrypoint: modprobe xt_string, DOCKER-USER chain check, CF jail auto-selection, telegram_notif.sh deployment to persistent volume on first run - Fix whitelist live-update: addignoreip/delignoreip called alongside jail.local write - Hardcode AUTOBAN_THR=75 and DEFAULT_DAYS=3 (remove env vars) - Include Nginx Proxy Manager in both compose files with shared log bind mount - Rewrite filters for actual NPM log format ([Client <HOST>] real IP extraction) - Add DATA_DIR, Telegram, CF API key fields to .env.example Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
67
.env.example
67
.env.example
@@ -1,47 +1,46 @@
|
||||
# ── F2B Control Center — environment configuration ───────────────────────────
|
||||
# Copy this file to .env and fill in your values.
|
||||
# Only NPM_LOG_DIR is strictly required to get started.
|
||||
# cp .env.example .env then fill in your values.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# ── Required ──────────────────────────────────────────────────────────────────
|
||||
|
||||
# Path to your Nginx Proxy Manager log directory on the host.
|
||||
# This directory will be mounted read-only inside the container.
|
||||
# Common paths:
|
||||
# /opt/npm/data/logs
|
||||
# /home/docker/NGINX/data/logs
|
||||
# /docker/nginx-proxy-manager/data/logs
|
||||
NPM_LOG_DIR=/opt/npm/data/logs
|
||||
# ── Data directory ────────────────────────────────────────────────────────────
|
||||
# Host path where NPM data, logs, and certs are stored.
|
||||
# NPM logs will be at: ${DATA_DIR}/npm/logs/proxy-host-*_access.log
|
||||
DATA_DIR=./data
|
||||
|
||||
# ── Dashboard ─────────────────────────────────────────────────────────────────
|
||||
|
||||
# Port the dashboard listens on (host port when using network_mode: host)
|
||||
# Port the dashboard listens on (direct host port — network_mode: host)
|
||||
DASHBOARD_PORT=4000
|
||||
|
||||
# ── AbuseIPDB integration (optional but recommended) ─────────────────────────
|
||||
# Enables IP reputation lookups and auto-ban by abuse score.
|
||||
# Free API keys available at https://www.abuseipdb.com/
|
||||
ABUSEIPDB_API_KEY=
|
||||
|
||||
# Minimum AbuseIPDB confidence score (0–100) to trigger auto-ban
|
||||
AUTOBAN_THRESHOLD=75
|
||||
|
||||
# ── Log scanning ──────────────────────────────────────────────────────────────
|
||||
|
||||
# Default lookback window when scanning nginx logs (days)
|
||||
DEFAULT_LOOKBACK_DAYS=3
|
||||
|
||||
# Comma-separated CIDR subnets to skip during log scanning and banning.
|
||||
# Include your LAN, Docker bridge, and any trusted networks.
|
||||
# ── Network ───────────────────────────────────────────────────────────────────
|
||||
# Comma-separated CIDRs to skip during log scanning and banning.
|
||||
# Include your LAN, Docker bridge, and any other trusted networks.
|
||||
SUBNETS_TO_IGNORE=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
|
||||
# ── Optional integrations ─────────────────────────────────────────────────────
|
||||
# ── AbuseIPDB (optional but recommended) ─────────────────────────────────────
|
||||
# Enables IP reputation lookups and the AUTO-BAN feature.
|
||||
# Free API keys: https://www.abuseipdb.com/
|
||||
ABUSEIPDB_API_KEY=
|
||||
|
||||
# Webhook URL: receives a POST request on every manual ban action.
|
||||
# Payload: { "action": "ban", "ip": "1.2.3.4", "jail": "manual-bans", "ts": "..." }
|
||||
# Examples: Discord webhook, n8n, Slack, custom endpoint
|
||||
# ── Telegram notifications (optional) ────────────────────────────────────────
|
||||
# Sends a message on ban/unban/start/stop events.
|
||||
# 1. Create a bot via @BotFather → copy the token
|
||||
# 2. Get your chat ID (send a message to the bot, then:
|
||||
# curl https://api.telegram.org/bot<TOKEN>/getUpdates)
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
TELEGRAM_CHAT_ID=
|
||||
|
||||
# ── Cloudflare (docker-compose.cloudflare.yml only) ───────────────────────────
|
||||
# Required when using docker-compose.cloudflare.yml.
|
||||
# Global API Key from: https://dash.cloudflare.com/profile/api-tokens
|
||||
CF_EMAIL=
|
||||
CF_APIKEY=
|
||||
|
||||
# ── Webhook (optional) ────────────────────────────────────────────────────────
|
||||
# POST to this URL on every manual ban from the dashboard.
|
||||
# Payload: { "action": "ban", "ip": "...", "jail": "manual-bans", "ts": "..." }
|
||||
WEBHOOK_URL=
|
||||
|
||||
# Path to a custom script to run after whitelist changes (e.g. Cloudflare sync).
|
||||
# The script is executed as a background fire-and-forget process.
|
||||
# ── Cloudflare whitelist sync (optional) ──────────────────────────────────────
|
||||
# Path (inside the container) to a script run after any whitelist change.
|
||||
# Mount your script into the container and set this path.
|
||||
# CF_SYNC=/usr/local/bin/cloudflare-whitelist-sync.sh
|
||||
|
||||
@@ -43,7 +43,8 @@ COPY supervisor.conf /etc/supervisor/conf.d/f2b-control-center.conf
|
||||
# ── Startup and health ────────────────────────────────────────────────────────
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
COPY healthcheck.sh /healthcheck.sh
|
||||
RUN chmod +x /entrypoint.sh /healthcheck.sh
|
||||
RUN chmod +x /entrypoint.sh /healthcheck.sh \
|
||||
/etc/f2b-defaults/action.d/telegram_notif.sh
|
||||
|
||||
# ── Runtime directories ───────────────────────────────────────────────────────
|
||||
RUN mkdir -p /data /nginx-logs /var/log /var/run/fail2ban
|
||||
|
||||
@@ -19,9 +19,9 @@ const CF_SYNC = process.env.CF_SYNC || '/usr/local/bin/cloudflare-wh
|
||||
const MANUAL_JAIL = process.env.MANUAL_JAIL || 'manual-bans';
|
||||
const BAN_HIST_FILE = process.env.BAN_HIST_FILE || '/data/ban-history.json';
|
||||
const SUBNETS = (process.env.SUBNETS_TO_IGNORE || '10.0.0.0/8,172.16.0.0/12').split(',').map(s => s.trim());
|
||||
const DEFAULT_DAYS = parseInt(process.env.DEFAULT_LOOKBACK_DAYS || '3');
|
||||
const DEFAULT_DAYS = 3;
|
||||
const ABUSE_KEY = process.env.ABUSEIPDB_API_KEY;
|
||||
const AUTOBAN_THR = parseInt(process.env.AUTOBAN_THRESHOLD || '75');
|
||||
const AUTOBAN_THR = 75;
|
||||
// Optional: POST to this URL on every manual ban (Discord, Slack, n8n, etc.)
|
||||
const WEBHOOK_URL = process.env.WEBHOOK_URL || '';
|
||||
|
||||
@@ -73,11 +73,12 @@ async function addWhitelist(ip, note) {
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(JAIL_LOCAL, lines.join('\n'));
|
||||
// unban from all jails
|
||||
// Live-update running fail2ban (no reload needed)
|
||||
const jails = await getJails();
|
||||
await Promise.all(jails.map(j =>
|
||||
run(`fail2ban-client set ${j} unbanip ${ip}`).catch(() => {})
|
||||
));
|
||||
await Promise.all(jails.map(j => Promise.all([
|
||||
run(`fail2ban-client set ${j} addignoreip ${ip}`).catch(() => {}),
|
||||
run(`fail2ban-client set ${j} unbanip ${ip}`).catch(() => {}),
|
||||
])));
|
||||
if (fs.existsSync(CF_SYNC)) exec(`${CF_SYNC} &`);
|
||||
}
|
||||
|
||||
@@ -87,6 +88,11 @@ async function removeWhitelist(ip) {
|
||||
new RegExp(`\\s*${ip.replace(/\./g, '\\.')}(?:\\s*#[^\\n]*)?`, 'g'), ''
|
||||
);
|
||||
fs.writeFileSync(JAIL_LOCAL, updated);
|
||||
// Live-update running fail2ban (no reload needed)
|
||||
const jails = await getJails();
|
||||
await Promise.all(jails.map(j =>
|
||||
run(`fail2ban-client set ${j} delignoreip ${ip}`).catch(() => {})
|
||||
));
|
||||
if (fs.existsSync(CF_SYNC)) exec(`${CF_SYNC} &`);
|
||||
}
|
||||
|
||||
|
||||
78
docker-compose.cloudflare.yml
Normal file
78
docker-compose.cloudflare.yml
Normal file
@@ -0,0 +1,78 @@
|
||||
# ── F2B Control Center — Cloudflare stack ────────────────────────────────────
|
||||
#
|
||||
# Identical to docker-compose.yml with CF credentials added.
|
||||
# Bans are enforced at BOTH the iptables level AND the Cloudflare WAF level.
|
||||
#
|
||||
# SETUP:
|
||||
# 1. cp .env.example .env
|
||||
# 2. Fill in CF_EMAIL and CF_APIKEY in .env
|
||||
# 3. docker-compose -f docker-compose.cloudflare.yml up -d
|
||||
#
|
||||
# On first start the entrypoint detects CF_EMAIL/CF_APIKEY and installs
|
||||
# jail.cloudflare.local instead of jail.local, enabling the cloudflare
|
||||
# action for all jails automatically.
|
||||
#
|
||||
# IMPORTANT: If you have already started the standard compose and have an
|
||||
# existing f2b-config volume, delete it first so the CF jail config is
|
||||
# installed fresh:
|
||||
# docker-compose down
|
||||
# docker volume rm f2b-control-center_f2b-config
|
||||
# docker-compose -f docker-compose.cloudflare.yml up -d
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
|
||||
# ── Nginx Proxy Manager ─────────────────────────────────────────────────────
|
||||
npm:
|
||||
image: jc21/nginx-proxy-manager:latest
|
||||
container_name: nginx-proxy-manager
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "81:81"
|
||||
volumes:
|
||||
- ${DATA_DIR:-./data}/npm:/data
|
||||
- ${DATA_DIR:-./data}/npm/logs:/data/logs
|
||||
- ${DATA_DIR:-./data}/letsencrypt:/etc/letsencrypt
|
||||
|
||||
# ── F2B Control Center ──────────────────────────────────────────────────────
|
||||
f2b-control-center:
|
||||
build: .
|
||||
image: f2b-control-center:latest
|
||||
container_name: f2b-control-center
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- npm
|
||||
network_mode: host
|
||||
|
||||
environment:
|
||||
PORT: "${DASHBOARD_PORT:-4000}"
|
||||
ABUSEIPDB_API_KEY: "${ABUSEIPDB_API_KEY:-}"
|
||||
SUBNETS_TO_IGNORE: "${SUBNETS_TO_IGNORE:-10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}"
|
||||
WEBHOOK_URL: "${WEBHOOK_URL:-}"
|
||||
TELEGRAM_BOT_TOKEN: "${TELEGRAM_BOT_TOKEN:-}"
|
||||
TELEGRAM_CHAT_ID: "${TELEGRAM_CHAT_ID:-}"
|
||||
# ── Cloudflare credentials ──────────────────────────────────────────────
|
||||
# Required: your Cloudflare account email
|
||||
CF_EMAIL: "${CF_EMAIL}"
|
||||
# Required: your Cloudflare Global API Key
|
||||
# https://dash.cloudflare.com/profile/api-tokens → "Global API Key"
|
||||
CF_APIKEY: "${CF_APIKEY}"
|
||||
# Internal paths
|
||||
LOG_DIR: "/nginx-logs"
|
||||
FAIL2BAN_LOG: "/var/log/fail2ban.log"
|
||||
JAIL_LOCAL: "/etc/fail2ban/jail.local"
|
||||
MANUAL_JAIL: "manual-bans"
|
||||
BAN_HIST_FILE: "/data/ban-history.json"
|
||||
|
||||
volumes:
|
||||
- ${DATA_DIR:-./data}/npm/logs:/nginx-logs:ro
|
||||
- f2b-data:/data
|
||||
- f2b-config:/etc/fail2ban
|
||||
|
||||
volumes:
|
||||
f2b-data:
|
||||
f2b-config:
|
||||
@@ -1,92 +1,69 @@
|
||||
# ── F2B Control Center — docker-compose ──────────────────────────────────────
|
||||
# ── F2B Control Center — standard stack ──────────────────────────────────────
|
||||
#
|
||||
# Includes: Nginx Proxy Manager + Fail2Ban + dashboard
|
||||
#
|
||||
# QUICK START:
|
||||
# 1. cp .env.example .env
|
||||
# 2. Set NPM_LOG_DIR to your Nginx Proxy Manager log path
|
||||
# 3. docker-compose up -d
|
||||
# cp .env.example .env
|
||||
# # edit .env — at minimum review DATA_DIR and SUBNETS_TO_IGNORE
|
||||
# docker-compose up -d
|
||||
#
|
||||
# NETWORK MODE:
|
||||
# network_mode: host is the recommended default.
|
||||
# This allows fail2ban's iptables rules to block traffic at the host level,
|
||||
# which is required when Nginx Proxy Manager receives traffic from the host
|
||||
# network stack (the typical Docker-based NPM setup).
|
||||
#
|
||||
# If you only want the dashboard (no active iptables blocking), you can
|
||||
# switch to bridge networking by commenting out network_mode and
|
||||
# uncommenting the ports section instead.
|
||||
# CLOUDFLARE:
|
||||
# To also ban at the CF WAF level, use docker-compose.cloudflare.yml instead.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
|
||||
# ── Nginx Proxy Manager ─────────────────────────────────────────────────────
|
||||
npm:
|
||||
image: jc21/nginx-proxy-manager:latest
|
||||
container_name: nginx-proxy-manager
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "81:81" # NPM admin UI (change or restrict in production)
|
||||
volumes:
|
||||
- ${DATA_DIR:-./data}/npm:/data
|
||||
- ${DATA_DIR:-./data}/npm/logs:/data/logs # shared with f2b (see below)
|
||||
- ${DATA_DIR:-./data}/letsencrypt:/etc/letsencrypt
|
||||
|
||||
# ── F2B Control Center ──────────────────────────────────────────────────────
|
||||
f2b-control-center:
|
||||
build: .
|
||||
image: f2b-control-center:latest
|
||||
container_name: f2b-control-center
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- npm
|
||||
|
||||
# Required for iptables rules to manipulate the host network stack.
|
||||
# network_mode: host makes the container share the host's network namespace,
|
||||
# so fail2ban bans affect traffic arriving at the host.
|
||||
# Host network mode is required so fail2ban's iptables rules affect the
|
||||
# host network stack — blocking traffic before it reaches NPM containers.
|
||||
network_mode: host
|
||||
|
||||
# Alternative (bridge mode — dashboard only, no host-level blocking):
|
||||
# Comment out network_mode above and uncomment these:
|
||||
# ports:
|
||||
# - "${DASHBOARD_PORT:-4000}:4000"
|
||||
# cap_add:
|
||||
# - NET_ADMIN
|
||||
# - NET_RAW
|
||||
|
||||
environment:
|
||||
# ── Dashboard ────────────────────────────────────────────────────────
|
||||
PORT: "${DASHBOARD_PORT:-4000}"
|
||||
|
||||
# AbuseIPDB integration (optional but recommended)
|
||||
# Get a free API key at https://www.abuseipdb.com/
|
||||
ABUSEIPDB_API_KEY: "${ABUSEIPDB_API_KEY:-}"
|
||||
|
||||
# Auto-ban: AbuseIPDB confidence score threshold (0-100)
|
||||
AUTOBAN_THRESHOLD: "${AUTOBAN_THRESHOLD:-75}"
|
||||
|
||||
# Default lookback window for nginx log scanning (days)
|
||||
DEFAULT_LOOKBACK_DAYS: "${DEFAULT_LOOKBACK_DAYS:-3}"
|
||||
|
||||
# Comma-separated CIDR subnets to ignore in scans and bans
|
||||
SUBNETS_TO_IGNORE: "${SUBNETS_TO_IGNORE:-10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}"
|
||||
|
||||
# Optional: POST ban events to this URL (e.g. Discord webhook, n8n, etc.)
|
||||
WEBHOOK_URL: "${WEBHOOK_URL:-}"
|
||||
|
||||
# ── Internal paths — change only if you remap volumes ────────────────
|
||||
LOG_DIR: "/nginx-logs"
|
||||
FAIL2BAN_LOG: "/var/log/fail2ban.log"
|
||||
JAIL_LOCAL: "/etc/fail2ban/jail.local"
|
||||
MANUAL_JAIL: "manual-bans"
|
||||
BAN_HIST_FILE: "/data/ban-history.json"
|
||||
PORT: "${DASHBOARD_PORT:-4000}"
|
||||
ABUSEIPDB_API_KEY: "${ABUSEIPDB_API_KEY:-}"
|
||||
SUBNETS_TO_IGNORE: "${SUBNETS_TO_IGNORE:-10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}"
|
||||
WEBHOOK_URL: "${WEBHOOK_URL:-}"
|
||||
TELEGRAM_BOT_TOKEN: "${TELEGRAM_BOT_TOKEN:-}"
|
||||
TELEGRAM_CHAT_ID: "${TELEGRAM_CHAT_ID:-}"
|
||||
# Internal paths — only change if you remap volumes
|
||||
LOG_DIR: "/nginx-logs"
|
||||
FAIL2BAN_LOG: "/var/log/fail2ban.log"
|
||||
JAIL_LOCAL: "/etc/fail2ban/jail.local"
|
||||
MANUAL_JAIL: "manual-bans"
|
||||
BAN_HIST_FILE: "/data/ban-history.json"
|
||||
|
||||
volumes:
|
||||
# ── REQUIRED: your Nginx Proxy Manager access log directory ──────────
|
||||
# Change NPM_LOG_DIR in .env to match your setup.
|
||||
# Default paths for common NPM Docker setups:
|
||||
# /opt/npm/data/logs (appdata-style)
|
||||
# /home/docker/NGINX/data/logs
|
||||
# /docker/nginx-proxy-manager/data/logs
|
||||
- "${NPM_LOG_DIR:-/opt/npm/data/logs}:/nginx-logs:ro"
|
||||
|
||||
# ── Persistent application data (ban history) ─────────────────────
|
||||
# NPM logs — read-only. Shared with NPM via bind mount above.
|
||||
- ${DATA_DIR:-./data}/npm/logs:/nginx-logs:ro
|
||||
# Persistent app state (ban history)
|
||||
- f2b-data:/data
|
||||
|
||||
# ── Fail2ban configuration (survives container updates) ───────────
|
||||
# Edit /etc/fail2ban/jail.local inside the container or mount a local
|
||||
# directory here to manage config files outside the container.
|
||||
# Fail2ban config — persists across image updates
|
||||
- f2b-config:/etc/fail2ban
|
||||
|
||||
labels:
|
||||
com.f2b-control-center.description: "Fail2Ban Control Center for Nginx Proxy Manager"
|
||||
|
||||
volumes:
|
||||
f2b-data:
|
||||
driver: local
|
||||
f2b-config:
|
||||
driver: local
|
||||
|
||||
@@ -6,11 +6,33 @@ set -e
|
||||
|
||||
echo "[f2b-cc] Starting F2B Control Center..."
|
||||
|
||||
# ── Kernel module: xt_string (required for X-Forwarded-For matching) ──────────
|
||||
if modprobe xt_string 2>/dev/null; then
|
||||
echo "[f2b-cc] xt_string kernel module loaded OK"
|
||||
else
|
||||
echo "[f2b-cc] WARNING: xt_string module unavailable — X-Forwarded-For iptables rules will NOT work"
|
||||
echo "[f2b-cc] Run 'modprobe xt_string' on the Docker host to fix this."
|
||||
fi
|
||||
|
||||
# ── DOCKER-USER chain (must exist for the ban action to insert rules) ─────────
|
||||
if iptables -L DOCKER-USER -n > /dev/null 2>&1; then
|
||||
echo "[f2b-cc] DOCKER-USER iptables chain found OK"
|
||||
else
|
||||
echo "[f2b-cc] DOCKER-USER chain missing — creating it"
|
||||
iptables -N DOCKER-USER 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ── First-run: install default fail2ban config if none exists ─────────────────
|
||||
if [ ! -f /etc/fail2ban/jail.local ]; then
|
||||
echo "[f2b-cc] First run — installing default fail2ban configuration..."
|
||||
cp -r /etc/f2b-defaults/. /etc/fail2ban/
|
||||
|
||||
# Cloudflare credentials present → use the CF-enabled jail config
|
||||
if [ -n "${CF_EMAIL}" ] && [ -n "${CF_APIKEY}" ]; then
|
||||
echo "[f2b-cc] CF_EMAIL + CF_APIKEY detected — enabling Cloudflare jail config"
|
||||
cp /etc/f2b-defaults/jail.cloudflare.local /etc/fail2ban/jail.local
|
||||
fi
|
||||
|
||||
# Apply SUBNETS_TO_IGNORE from environment into jail.local's ignoreip line
|
||||
if [ -n "${SUBNETS_TO_IGNORE}" ]; then
|
||||
IGNORE_LINE="ignoreip = 127.0.0.1/8 ::1 ${SUBNETS_TO_IGNORE}"
|
||||
@@ -24,6 +46,16 @@ else
|
||||
echo "[f2b-cc] Using existing fail2ban configuration."
|
||||
fi
|
||||
|
||||
# ── Deploy telegram_notif.sh to persistent volume (user-editable) ─────────────
|
||||
mkdir -p /data/action.d
|
||||
if [ ! -f /data/action.d/telegram_notif.sh ]; then
|
||||
echo "[f2b-cc] Deploying telegram_notif.sh to /data/action.d/"
|
||||
cp /etc/f2b-defaults/action.d/telegram_notif.sh /data/action.d/telegram_notif.sh
|
||||
chmod +x /data/action.d/telegram_notif.sh
|
||||
else
|
||||
echo "[f2b-cc] /data/action.d/telegram_notif.sh already present — skipping copy"
|
||||
fi
|
||||
|
||||
# ── Ensure required directories and files exist ───────────────────────────────
|
||||
mkdir -p /data /var/log /var/run/fail2ban
|
||||
|
||||
@@ -33,7 +65,7 @@ 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 NPM_LOG_DIR in .env and mount your NPM log directory."
|
||||
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
|
||||
fi
|
||||
|
||||
44
fail2ban/action.d/cloudflare.conf
Normal file
44
fail2ban/action.d/cloudflare.conf
Normal file
@@ -0,0 +1,44 @@
|
||||
[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.
|
||||
#
|
||||
# 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/<ZONE_ID>/firewall/access_rules/rules
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
actionban = curl -s -X POST \
|
||||
-H "X-Auth-Email: %(cf_email)s" \
|
||||
-H "X-Auth-Key: %(cf_apikey)s" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"mode\":\"block\",\"configuration\":{\"target\":\"ip\",\"value\":\"<ip>\"},\"notes\":\"f2b-cc: <name>\"}" \
|
||||
"https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules" \
|
||||
> /dev/null 2>&1 || true
|
||||
|
||||
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=<ip>&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) ; \
|
||||
[ -n "$RULE_ID" ] && \
|
||||
curl -s -X DELETE \
|
||||
-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/$RULE_ID" \
|
||||
> /dev/null 2>&1 || true
|
||||
|
||||
[Init]
|
||||
# Populated from environment via jail.local — do not set here
|
||||
cf_email =
|
||||
cf_apikey =
|
||||
21
fail2ban/action.d/docker-npm.conf
Normal file
21
fail2ban/action.d/docker-npm.conf
Normal file
@@ -0,0 +1,21 @@
|
||||
[Definition]
|
||||
|
||||
# ── Lifecycle notifications ───────────────────────────────────────────────────
|
||||
actionstart = bash /data/action.d/telegram_notif.sh -a start
|
||||
actionstop = bash /data/action.d/telegram_notif.sh -a stop
|
||||
|
||||
# ── Ban ───────────────────────────────────────────────────────────────────────
|
||||
# 1. DOCKER-USER: drops forwarded packets containing the banned IP in the
|
||||
# X-Forwarded-For header — catches traffic coming through Cloudflare/CDN
|
||||
# where the real client IP is forwarded as a header to NPM.
|
||||
# 2. INPUT: drops direct connections from the banned IP at the host level.
|
||||
# 3. Telegram notification (silent if TELEGRAM_BOT_TOKEN is unset).
|
||||
actionban = iptables -I DOCKER-USER -m string --algo bm --string 'X-Forwarded-For: <ip>' -j DROP
|
||||
iptables -A INPUT -s <ip> -j DROP
|
||||
bash /data/action.d/telegram_notif.sh -b <ip> -r "<name>"
|
||||
|
||||
# ── Unban ─────────────────────────────────────────────────────────────────────
|
||||
# || true prevents failure if the rule was already removed (e.g. on restart).
|
||||
actionunban = iptables -D DOCKER-USER -m string --algo bm --string 'X-Forwarded-For: <ip>' -j DROP || true
|
||||
iptables -D INPUT -s <ip> -j DROP || true
|
||||
bash /data/action.d/telegram_notif.sh -u <ip>
|
||||
54
fail2ban/action.d/telegram_notif.sh
Normal file
54
fail2ban/action.d/telegram_notif.sh
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
# ── Telegram notification hook for fail2ban ───────────────────────────────────
|
||||
# Called by docker-npm.conf on ban/unban/start/stop events.
|
||||
# Silently exits if TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID is not set.
|
||||
#
|
||||
# Usage:
|
||||
# telegram_notif.sh -a start|stop
|
||||
# telegram_notif.sh -b <ip> -r "<reason>"
|
||||
# telegram_notif.sh -u <ip>
|
||||
#
|
||||
# Configure by setting in .env (passed into the container via docker-compose):
|
||||
# TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
|
||||
# TELEGRAM_CHAT_ID=-100123456789
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
|
||||
CHAT_ID="${TELEGRAM_CHAT_ID:-}"
|
||||
|
||||
# Exit silently if not configured — allows docker-npm action to work without Telegram
|
||||
[[ -z "$BOT_TOKEN" || -z "$CHAT_ID" ]] && exit 0
|
||||
|
||||
send() {
|
||||
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
|
||||
--data-urlencode "chat_id=${CHAT_ID}" \
|
||||
--data-urlencode "text=$1" \
|
||||
--data-urlencode "parse_mode=HTML" \
|
||||
> /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# Parse flags
|
||||
ACTION="" BAN_IP="" REASON="" UNBAN_IP=""
|
||||
while getopts ":a:b:r:u:" opt; do
|
||||
case $opt in
|
||||
a) ACTION="$OPTARG" ;;
|
||||
b) BAN_IP="$OPTARG" ;;
|
||||
r) REASON="$OPTARG" ;;
|
||||
u) UNBAN_IP="$OPTARG" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$ACTION" in
|
||||
start) send "🟢 <b>F2B Control Center started</b>" ;;
|
||||
stop) send "🔴 <b>F2B Control Center stopped</b>" ;;
|
||||
esac
|
||||
|
||||
if [[ -n "$BAN_IP" ]]; then
|
||||
MSG="🚫 <b>BANNED</b>: <code>${BAN_IP}</code>"
|
||||
[[ -n "$REASON" ]] && MSG="${MSG} [${REASON}]"
|
||||
send "$MSG"
|
||||
fi
|
||||
|
||||
if [[ -n "$UNBAN_IP" ]]; then
|
||||
send "✅ <b>UNBANNED</b>: <code>${UNBAN_IP}</code>"
|
||||
fi
|
||||
@@ -1,28 +1,15 @@
|
||||
# ── F2B Control Center — badbot filter ───────────────────────────────────────
|
||||
#
|
||||
# Blocks known malicious scanners, vulnerability testers, and exploit frameworks
|
||||
# by their HTTP User-Agent string.
|
||||
#
|
||||
# LOG FORMAT:
|
||||
# Primary pattern matches NPM logs with [Client IP] real-IP field:
|
||||
# PROXY_IP - - [date] "GET /" 200 146 "-" "UserAgent" [Client REAL_IP]
|
||||
#
|
||||
# If your NPM logs have the client IP at line start (standard nginx combined),
|
||||
# uncomment the alternative failregex lines instead.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
[Definition]
|
||||
|
||||
# ── Primary: NPM [Client IP] format ──────────────────────────────────────────
|
||||
failregex = ^[^ ]+ - -[^\[]*\[[^\]]+\] "[A-Z]+ [^"]*" \d{3} \d+ "[^"]*" "(?:masscan|zgrab|python-requests|Go-http-client/1\.1|[Nn]uclei|sqlmap|dirbuster|gobuster|nikto|nmap|wfuzz|Metasploit|libwww-perl|WPScan|ZmEu|jorgee|NetcraftSurveyAgent|Expanse|Shodan|censys|BinaryEdge|internet-measurement|SemrushBot|AhrefsBot)[^"]*" \[Client <HOST>\]
|
||||
# ── NPM access log format ─────────────────────────────────────────────────────
|
||||
# PROXY_IP - - [DD/Mon/YYYY:HH:MM:SS +0000] "METHOD PATH HTTP/VER" STATUS BYTES "REFERER" "UA" [Client REAL_IP]
|
||||
#
|
||||
# <HOST> is placed at the [Client REAL_IP] position — this is the IP that gets
|
||||
# banned, which is the real client IP forwarded by Cloudflare/CDN via X-Forwarded-For.
|
||||
#
|
||||
# Test against your logs:
|
||||
# fail2ban-regex /nginx-logs/proxy-host-1_access.log /etc/fail2ban/filter.d/badbot.conf
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# ── Alternative: standard nginx combined format (IP at line start) ────────────
|
||||
# Uncomment and comment out the Primary line above to use this instead.
|
||||
# failregex = ^<HOST> - -[^\[]*\[[^\]]+\] "[A-Z]+ [^"]*" \d{3} \d+ "[^"]*" "(?:masscan|zgrab|python-requests|Go-http-client/1\.1|[Nn]uclei|sqlmap|dirbuster|gobuster|nikto|nmap|wfuzz|Metasploit|libwww-perl|WPScan|ZmEu|jorgee|NetcraftSurveyAgent|Expanse|Shodan|censys|BinaryEdge|internet-measurement)[^"]*"
|
||||
failregex = ^\S+ - - \[[^\]]+\] "\S+ [^"]*" \d{3} \d+ "[^"]*" "(?i:masscan|zgrab|python-requests|go-http-client/1\.1|nuclei|sqlmap|dirbuster|gobuster|nikto|wfuzz|metasploit|libwww-perl|wpscan|nmap|zmeu|jorgee|shodan\.com|censys|binaryedge|internet-measurement|netcraft|strikeready|dataforseo|semrushbot|ahrefsbot|mj12bot|dotbot)[^"]*" \[Client <HOST>\]
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# ── Notes ────────────────────────────────────────────────────────────────────
|
||||
# Add more UA patterns to the failregex alternation as needed.
|
||||
# Test your filter with:
|
||||
# fail2ban-regex /nginx-logs/proxy-host-1_access.log /etc/fail2ban/filter.d/badbot.conf
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
# ── F2B Control Center — http-errors filter ──────────────────────────────────
|
||||
#
|
||||
# Bans IPs generating a high volume of HTTP 4xx/5xx error responses.
|
||||
# Works well for catching brute-force login attempts, path traversal scans,
|
||||
# and generally misbehaving clients.
|
||||
#
|
||||
# Tune `maxretry` and `findtime` in jail.local to adjust sensitivity.
|
||||
# Default: 15 errors in 5 minutes.
|
||||
#
|
||||
# LOG FORMAT:
|
||||
# Primary pattern matches NPM logs with [Client IP] real-IP field.
|
||||
# See badbot.conf for details on switching to standard nginx format.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
[Definition]
|
||||
|
||||
# ── Primary: NPM [Client IP] format ──────────────────────────────────────────
|
||||
# Matches any 4xx or 5xx response (excluding 200 implicitly by the status code).
|
||||
failregex = ^[^ ]+ - -[^\[]*\[[^\]]+\] "[A-Z]+ [^"]*" [45]\d\d \d+ "[^"]*" "[^"]*" \[Client <HOST>\]
|
||||
# ── NPM access log format ─────────────────────────────────────────────────────
|
||||
# Bans IPs generating excessive 4xx/5xx errors.
|
||||
# Default jail: 15 errors in 5 minutes (tunable in jail.local).
|
||||
#
|
||||
# PROXY_IP - - [date] "METHOD PATH HTTP/VER" STATUS BYTES "REFERER" "UA" [Client REAL_IP]
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# ── Alternative: standard nginx combined format ───────────────────────────────
|
||||
# failregex = ^<HOST> - -[^\[]*\[[^\]]+\] "[A-Z]+ [^"]*" [45]\d\d
|
||||
failregex = ^\S+ - - \[[^\]]+\] "\S+ [^"]*" [45]\d\d \d+ "[^"]*" "[^"]*" \[Client <HOST>\]
|
||||
|
||||
# Ignore common false-positive 4xx codes:
|
||||
# 404 — very common for missing favicons, /robots.txt, etc. Remove from
|
||||
# ignoreregex below if you DO want to count 404s (recommended for
|
||||
# aggressive probe detection when combined with high maxretry).
|
||||
ignoreregex = ^[^ ]+ - -[^\[]*\[[^\]]+\] "[A-Z]+ /(?:favicon\.ico|robots\.txt|apple-touch-icon[^"]*) HTTP[^"]*" 404 .* \[Client <HOST>\]
|
||||
# Exclude very common benign 404s to reduce noise.
|
||||
# Remove these if you want to count ALL error responses.
|
||||
ignoreregex = ^\S+ - - \[[^\]]+\] "\S+ /(?:favicon\.ico|robots\.txt|sitemap\.xml|apple-touch-icon[^"]*|\.well-known/[^"]*)[^"]*" 404 \d+ "[^"]*" "[^"]*" \[Client <HOST>\]
|
||||
|
||||
@@ -1,29 +1,12 @@
|
||||
# ── F2B Control Center — npm-probe filter ────────────────────────────────────
|
||||
#
|
||||
# Bans IPs probing for well-known vulnerable paths.
|
||||
# These requests almost always indicate automated exploit scanning — a single
|
||||
# hit warrants a long ban (configured to 48h / maxretry 3 in jail.local).
|
||||
#
|
||||
# Covered categories:
|
||||
# - PHP-based CMS admin paths (WordPress, Joomla, etc.)
|
||||
# - Common config/credential file leaks (.env, .git, etc.)
|
||||
# - Java frameworks (actuator, Spring Boot, Struts)
|
||||
# - Web shells and common RCE payloads
|
||||
# - Device/router admin interfaces (HNAP, boaform)
|
||||
# - PHPMyAdmin, Adminer, database tools
|
||||
# - Path traversal attempts (../)
|
||||
#
|
||||
# LOG FORMAT:
|
||||
# Primary pattern matches NPM logs with [Client IP] real-IP field.
|
||||
# See badbot.conf for details on switching to standard nginx format.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
[Definition]
|
||||
|
||||
# ── Primary: NPM [Client IP] format ──────────────────────────────────────────
|
||||
failregex = ^[^ ]+ - -[^\[]*\[[^\]]+\] "(?:GET|POST|HEAD|OPTIONS) (?:/.env(?:\.[^"]*)?|/.git(?:/[^"]*)?|/wp-login\.php|/wp-admin(?:/[^"]*)?|/xmlrpc\.php|/phpmyadmin(?:/[^"]*)?|/pma(?:/[^"]*)?|/adminer(?:\.php)?|/admin\.php|/config\.php|/setup\.php|/install\.php|/actuator(?:/[^"]*)?|/console|/manager/html|/invoker/JMXInvokerServlet|/solr(?:/[^"]*)?|/geoserver(?:/[^"]*)?|/boaform(?:/[^"]*)?|/HNAP1(?:/[^"]*)?|/cgi-bin/[^"]*|/shell\.php|/cmd\.php|/eval-stdin\.php|/[^"]*\.\./[^"]*) HTTP[^"]*" \d{3} .* \[Client <HOST>\]
|
||||
# ── NPM access log format ─────────────────────────────────────────────────────
|
||||
# Bans IPs probing for well-known vulnerable paths.
|
||||
# Default jail: 3 hits in 30 minutes → 48h ban (very aggressive, intentionally).
|
||||
#
|
||||
# PROXY_IP - - [date] "METHOD PATH HTTP/VER" STATUS BYTES "REFERER" "UA" [Client REAL_IP]
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# ── Alternative: standard nginx combined format ───────────────────────────────
|
||||
# failregex = ^<HOST> - -[^\[]*\[[^\]]+\] "(?:GET|POST|HEAD) (?:/.env|/.git|/wp-login\.php|/wp-admin|/xmlrpc\.php|/phpmyadmin|/pma|/adminer|/admin\.php|/actuator|/console|/boaform|/HNAP1|/cgi-bin/|/shell\.php) HTTP"
|
||||
failregex = ^\S+ - - \[[^\]]+\] "[A-Z]+ /(?:\.env[^"]*|\.git[^"]*|wp-login\.php[^"]*|wp-admin[^"]*|xmlrpc\.php[^"]*|phpmyadmin[^"]*|pma/[^"]*|adminer[^"]*|admin\.php[^"]*|config\.php[^"]*|setup\.php[^"]*|install\.php[^"]*|actuator[^"]*|console[^"]*|manager/html[^"]*|invoker/[^"]*|solr/[^"]*|geoserver/[^"]*|boaform/[^"]*|HNAP1[^"]*|cgi-bin/[^"]*|shell\.php[^"]*|cmd\.php[^"]*|eval-stdin\.php[^"]*)[^"]*" \d{3} \d+ "[^"]*" "[^"]*" \[Client <HOST>\]
|
||||
|
||||
ignoreregex =
|
||||
|
||||
78
fail2ban/jail.cloudflare.local
Normal file
78
fail2ban/jail.cloudflare.local
Normal file
@@ -0,0 +1,78 @@
|
||||
# ── 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.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
[DEFAULT]
|
||||
bantime = 1h
|
||||
findtime = 10m
|
||||
maxretry = 5
|
||||
|
||||
# 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.
|
||||
cf_email = %(ENV[CF_EMAIL])s
|
||||
cf_apikey = %(ENV[CF_APIKEY])s
|
||||
|
||||
# ── NPM: Bad Bots ─────────────────────────────────────────────────────────────
|
||||
[badbot]
|
||||
enabled = true
|
||||
filter = badbot
|
||||
logpath = /nginx-logs/proxy-host-*_access.log
|
||||
bantime = 24h
|
||||
findtime = 10m
|
||||
maxretry = 3
|
||||
action = docker-npm
|
||||
cloudflare[cf_email="%(cf_email)s", cf_apikey="%(cf_apikey)s"]
|
||||
|
||||
# ── NPM: HTTP Error Spamming ──────────────────────────────────────────────────
|
||||
[http-errors]
|
||||
enabled = true
|
||||
filter = http-errors
|
||||
logpath = /nginx-logs/proxy-host-*_access.log
|
||||
bantime = 1h
|
||||
findtime = 5m
|
||||
maxretry = 15
|
||||
action = docker-npm
|
||||
cloudflare[cf_email="%(cf_email)s", cf_apikey="%(cf_apikey)s"]
|
||||
|
||||
# ── NPM: Exploit Probing ──────────────────────────────────────────────────────
|
||||
[npm-probe]
|
||||
enabled = true
|
||||
filter = npm-probe
|
||||
logpath = /nginx-logs/proxy-host-*_access.log
|
||||
bantime = 48h
|
||||
findtime = 30m
|
||||
maxretry = 3
|
||||
action = docker-npm
|
||||
cloudflare[cf_email="%(cf_email)s", cf_apikey="%(cf_apikey)s"]
|
||||
|
||||
# ── Manual Bans ───────────────────────────────────────────────────────────────
|
||||
[manual-bans]
|
||||
enabled = true
|
||||
filter = manual-bans
|
||||
logpath = /dev/null
|
||||
bantime = -1
|
||||
findtime = 1d
|
||||
maxretry = 1
|
||||
action = docker-npm
|
||||
cloudflare[cf_email="%(cf_email)s", cf_apikey="%(cf_apikey)s"]
|
||||
|
||||
# ── Recidive — repeat offenders ───────────────────────────────────────────────
|
||||
[recidive]
|
||||
enabled = false
|
||||
filter = recidive
|
||||
logpath = /var/log/fail2ban.log
|
||||
bantime = 7d
|
||||
findtime = 1d
|
||||
maxretry = 3
|
||||
action = docker-npm
|
||||
cloudflare[cf_email="%(cf_email)s", cf_apikey="%(cf_apikey)s"]
|
||||
@@ -1,46 +1,27 @@
|
||||
# ── F2B Control Center — fail2ban jail configuration ─────────────────────────
|
||||
# ── F2B Control Center — jail configuration (standard) ───────────────────────
|
||||
#
|
||||
# This file is written to /etc/fail2ban/jail.local on first container start.
|
||||
# After first run it is persisted in the f2b-config Docker volume so your
|
||||
# customisations survive image updates.
|
||||
# Installed to /etc/fail2ban/jail.local on first container start.
|
||||
# Persisted in the f2b-config Docker volume — survives image updates.
|
||||
#
|
||||
# IMPORTANT: The dashboard manages the `ignoreip` line automatically.
|
||||
# Do not edit it by hand unless you are not using the dashboard.
|
||||
# WHITELIST: managed by the dashboard. The ignoreip line below is written by
|
||||
# the entrypoint (from SUBNETS_TO_IGNORE) and updated live by the dashboard
|
||||
# without restarting fail2ban.
|
||||
#
|
||||
# LOG FORMAT NOTE:
|
||||
# The default filters expect Nginx Proxy Manager access logs that include
|
||||
# the real client IP in a [Client X.X.X.X] field — the format produced
|
||||
# when NPM forwards through Cloudflare or another proxy.
|
||||
#
|
||||
# If your NPM logs have the client IP at the start of each line (standard
|
||||
# nginx combined format), edit the failregex lines in each filter to use:
|
||||
# failregex = ^<HOST> - - \[...
|
||||
# See fail2ban/filter.d/*.conf for both patterns (commented examples included).
|
||||
# CLOUDFLARE: to also block IPs at the CF level, use docker-compose.cloudflare.yml
|
||||
# instead of docker-compose.yml. It installs jail.cloudflare.local which
|
||||
# adds the cloudflare action to every jail automatically.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
[DEFAULT]
|
||||
# Default ban duration (supports suffixes: s, m, h, d)
|
||||
bantime = 1h
|
||||
|
||||
# Time window in which maxretry violations trigger a ban
|
||||
findtime = 10m
|
||||
|
||||
# Number of violations before banning
|
||||
maxretry = 5
|
||||
|
||||
# IPs and subnets to never ban.
|
||||
# The dashboard appends whitelisted IPs here — do not remove this line.
|
||||
# The entrypoint script expands SUBNETS_TO_IGNORE from your .env on first run.
|
||||
# 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
|
||||
|
||||
# Default ban action: iptables-allports blocks all ports for banned IPs.
|
||||
# Requires NET_ADMIN capability (granted in docker-compose.yml) and
|
||||
# network_mode: host to affect host-level traffic.
|
||||
banaction = iptables-allports
|
||||
|
||||
# ── NPM: Bad Bots ─────────────────────────────────────────────────────────────
|
||||
# Blocks known malicious scanners and exploit frameworks by User-Agent.
|
||||
# Stricter settings: low maxretry, long ban time.
|
||||
[badbot]
|
||||
enabled = true
|
||||
filter = badbot
|
||||
@@ -48,11 +29,9 @@ logpath = /nginx-logs/proxy-host-*_access.log
|
||||
bantime = 24h
|
||||
findtime = 10m
|
||||
maxretry = 3
|
||||
action = %(action_)s
|
||||
action = docker-npm
|
||||
|
||||
# ── NPM: HTTP Error Spamming ──────────────────────────────────────────────────
|
||||
# Bans IPs that generate a high volume of 4xx/5xx errors.
|
||||
# Useful for catching brute-force attempts, broken crawlers, and probes.
|
||||
[http-errors]
|
||||
enabled = true
|
||||
filter = http-errors
|
||||
@@ -60,11 +39,9 @@ logpath = /nginx-logs/proxy-host-*_access.log
|
||||
bantime = 1h
|
||||
findtime = 5m
|
||||
maxretry = 15
|
||||
action = %(action_)s
|
||||
action = docker-npm
|
||||
|
||||
# ── NPM: Exploit Probing ──────────────────────────────────────────────────────
|
||||
# Bans IPs requesting well-known vulnerable paths (.env, wp-admin, etc.).
|
||||
# These are almost always malicious — short maxretry, long ban.
|
||||
[npm-probe]
|
||||
enabled = true
|
||||
filter = npm-probe
|
||||
@@ -72,11 +49,10 @@ logpath = /nginx-logs/proxy-host-*_access.log
|
||||
bantime = 48h
|
||||
findtime = 30m
|
||||
maxretry = 3
|
||||
action = %(action_)s
|
||||
action = docker-npm
|
||||
|
||||
# ── Manual Bans ───────────────────────────────────────────────────────────────
|
||||
# Permanent bans added via the dashboard or `fail2ban-client set manual-bans banip`.
|
||||
# No automatic log-based detection — only manual entries via the dashboard.
|
||||
# Populated via dashboard or: fail2ban-client set manual-bans banip <IP>
|
||||
[manual-bans]
|
||||
enabled = true
|
||||
filter = manual-bans
|
||||
@@ -84,11 +60,11 @@ logpath = /dev/null
|
||||
bantime = -1
|
||||
findtime = 1d
|
||||
maxretry = 1
|
||||
action = %(action_)s
|
||||
action = docker-npm
|
||||
|
||||
# ── Recidive ──────────────────────────────────────────────────────────────────
|
||||
# Escalated bans for repeat offenders across any jail.
|
||||
# Disabled by default — enable if you want long-term blocks for persistent attackers.
|
||||
# ── Recidive — repeat offenders ───────────────────────────────────────────────
|
||||
# Escalates bans to 7d for IPs that get banned 3+ times within a day.
|
||||
# Enable once your other jails have been running for a while.
|
||||
[recidive]
|
||||
enabled = false
|
||||
filter = recidive
|
||||
@@ -96,4 +72,4 @@ logpath = /var/log/fail2ban.log
|
||||
bantime = 7d
|
||||
findtime = 1d
|
||||
maxretry = 3
|
||||
action = %(action_)s
|
||||
action = docker-npm
|
||||
|
||||
Reference in New Issue
Block a user