From bd1cc6b6f326bf3dad18819ea0abea95558a5280 Mon Sep 17 00:00:00 2001 From: Yuri-Lima Date: Mon, 18 Aug 2025 11:16:56 +0200 Subject: [PATCH] Update .gitignore to exclude fail2ban data directory, clean up docker-compose.yaml by removing unused volume mappings, and add new Fail2Ban filter configurations for enhanced security against various attack vectors. --- .gitignore | 2 +- docker-compose.yaml | 4 +- .../filter.d/http-get-dos-compressed.conf | 8 ++ fail2ban/filter.d/http-get-dos.conf | 8 ++ fail2ban/filter.d/nginx-429.conf | 6 ++ fail2ban/filter.d/nginx-4xx.conf | 6 ++ fail2ban/filter.d/nginx-badbots.conf | 12 +++ fail2ban/filter.d/nginx-botsearch.conf | 6 ++ fail2ban/filter.d/sshd-pubkey.conf | 7 ++ fail2ban/jail.d/00-defaults.local | 7 ++ fail2ban/jail.d/nginx-phoenix.local | 88 +++++++++++++++++++ fail2ban/jail.d/sshd.local | 17 ++++ 12 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 fail2ban/filter.d/http-get-dos-compressed.conf create mode 100644 fail2ban/filter.d/http-get-dos.conf create mode 100644 fail2ban/filter.d/nginx-429.conf create mode 100644 fail2ban/filter.d/nginx-4xx.conf create mode 100644 fail2ban/filter.d/nginx-badbots.conf create mode 100644 fail2ban/filter.d/nginx-botsearch.conf create mode 100644 fail2ban/filter.d/sshd-pubkey.conf create mode 100644 fail2ban/jail.d/00-defaults.local create mode 100644 fail2ban/jail.d/nginx-phoenix.local create mode 100644 fail2ban/jail.d/sshd.local diff --git a/.gitignore b/.gitignore index ebf23c9..ae1bb82 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ database assets -fail2ban +fail2ban/data https_portal logs nginx diff --git a/docker-compose.yaml b/docker-compose.yaml index e560a3b..cba7255 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -250,7 +250,7 @@ services: start_period: 60s # Grace period before health checks start volumes: - "./assets:/usr/src/app/packages/dev-server/assets" - - "./server_custom:/usr/src/app/packages/dev-server/custom" + # - "./logs:/usr/src/app/packages/dev-server/logs" phoenix-worker: restart: always @@ -306,7 +306,7 @@ services: start_period: 30s # Grace period before health checks start volumes: - "./assets:/usr/src/app/packages/dev-server/assets" - - "./server_custom:/usr/src/app/packages/dev-server/custom" + # - "./logs:/usr/src/app/packages/dev-server/logs" phoenix-redis: image: 'bitnami/redis:latest' diff --git a/fail2ban/filter.d/http-get-dos-compressed.conf b/fail2ban/filter.d/http-get-dos-compressed.conf new file mode 100644 index 0000000..6675fde --- /dev/null +++ b/fail2ban/filter.d/http-get-dos-compressed.conf @@ -0,0 +1,8 @@ +[Definition] +# Count any GET/POST from the same client IP. We prefer X-Forwarded-For +# (real client IP) when present; otherwise fall back to remote_addr. +failregex = ^.*"x_forwarded_for":"(?:, [^"]+)?".*"request_method":"(?:GET|POST)".*$ + ^.*"remote_addr":"".*"request_method":"(?:GET|POST)".*$ + +# Ignore safe/health endpoints (adjust to your env) +ignoreregex = ^.*"request_uri":"\/(?:stub_status|health\/system|health\/worker|pgadmin4(?:\/|$)|\.well-known\/acme-challenge\/|.*\.(?:css|js|png|jpg|jpeg|gif|svg|ico|webp|woff2?))".*$ \ No newline at end of file diff --git a/fail2ban/filter.d/http-get-dos.conf b/fail2ban/filter.d/http-get-dos.conf new file mode 100644 index 0000000..926ab29 --- /dev/null +++ b/fail2ban/filter.d/http-get-dos.conf @@ -0,0 +1,8 @@ +[Definition] +# Count lots of requests from same IP regardless of status code. +# Prefer X-Forwarded-For (client IP behind proxy), fallback to remote_addr. +failregex = ^.*"x_forwarded_for":"".*"(GET|POST|HEAD)".*$ + ^.*"remote_addr":"".*"(GET|POST|HEAD)".*$ + +# Ignore health and stub_status hits +ignoreregex = ^.*"request_uri":"\/(?:stub_status|health\/system|health\/worker|pgadmin4(?:\/|$)|\.well-known\/acme-challenge\/|.*\.(?:css|js|png|jpg|jpeg|gif|svg|ico|webp|woff2?))".*$ \ No newline at end of file diff --git a/fail2ban/filter.d/nginx-429.conf b/fail2ban/filter.d/nginx-429.conf new file mode 100644 index 0000000..7c6e5f8 --- /dev/null +++ b/fail2ban/filter.d/nginx-429.conf @@ -0,0 +1,6 @@ +[Definition] +# JSON access log with x_forwarded_for preferred, else remote_addr +failregex = ^.*"x_forwarded_for":"(?:, [^"]+)?".*"status":429.*$ + ^.*"remote_addr":"".*"status":429.*$ + +ignoreregex = ^.*"request_uri":"\/(?:stub_status|health\/system|health\/worker|pgadmin4(?:\/|$)|\.well-known\/acme-challenge\/|.*\.(?:css|js|png|jpg|jpeg|gif|svg|ico|webp|woff2?))".*$ \ No newline at end of file diff --git a/fail2ban/filter.d/nginx-4xx.conf b/fail2ban/filter.d/nginx-4xx.conf new file mode 100644 index 0000000..1be4e0e --- /dev/null +++ b/fail2ban/filter.d/nginx-4xx.conf @@ -0,0 +1,6 @@ +[Definition] +# Match either x_forwarded_for (preferred) or remote_addr +failregex = ^.*"x_forwarded_for":"".*"status":4\d\d.*$ + ^.*"remote_addr":"".*"status":4\d\d.*$ + +ignoreregex = ^.*"request_uri":"\/(?:stub_status|health\/system|health\/worker|pgadmin4(?:\/|$)|\.well-known\/acme-challenge\/|.*\.(?:css|js|png|jpg|jpeg|gif|svg|ico|webp|woff2?))".*$ \ No newline at end of file diff --git a/fail2ban/filter.d/nginx-badbots.conf b/fail2ban/filter.d/nginx-badbots.conf new file mode 100644 index 0000000..77865a4 --- /dev/null +++ b/fail2ban/filter.d/nginx-badbots.conf @@ -0,0 +1,12 @@ +[Definition] +# JSON logs with ISO-8601 timestamps +datepattern = {^LN-BEG}%%Y-%%m-%%dT%%H:%%M:%%S(?:[.,]\\d+)?(?:Z|[+\\-]\\d{2}:\\d{2})? + +# Catch typical scanners and CLI/automation libraries (case-insensitive via (?i)) +# Prefer x_forwarded_for (real client IP) if present; fall back to remote_addr. +# NOTE: One "failregex =" key, multiple indented lines. No backslashes for wrapping. +failregex = (?i)^.*"x_forwarded_for":"(?:, [^"]+)?".*"http_user_agent":"[^"]*(?:sqlmap|nikto|acunetix|wpscan|dirbuster|gobuster|masscan|zgrab|ZmEu|nessus|openvas|libwww-perl|mechanize|lwp-trivial|python-requests|python-urllib|urllib|aiohttp|httpx|scrapy|curl|wget|Go-http-client|okhttp|httpclient|jakarta|java)[^"]*".*$ + (?i)^.*"remote_addr":"".*"http_user_agent":"[^"]*(?:sqlmap|nikto|acunetix|wpscan|dirbuster|gobuster|masscan|zgrab|ZmEu|nessus|openvas|libwww-perl|mechanize|lwp-trivial|python-requests|python-urllib|urllib|aiohttp|httpx|scrapy|curl|wget|Go-http-client|okhttp|httpclient|jakarta|java)[^"]*".*$ + +# Ignore your health/status endpoints +ignoreregex = ^.*"request_uri":"\/(?:stub_status|health\/system|health\/worker|pgadmin4(?:\/|$)|\.well-known\/acme-challenge\/|.*\.(?:css|js|png|jpg|jpeg|gif|svg|ico|webp|woff2?))".*$ \ No newline at end of file diff --git a/fail2ban/filter.d/nginx-botsearch.conf b/fail2ban/filter.d/nginx-botsearch.conf new file mode 100644 index 0000000..ee81112 --- /dev/null +++ b/fail2ban/filter.d/nginx-botsearch.conf @@ -0,0 +1,6 @@ +[Definition] +datepattern = {^LN-BEG}%%Y-%%m-%%dT%%H:%%M:%%S(?:[.,]\\d+)?(?:Z|[+\\-]\\d{2}:\\d{2})? +failregex = ^.*"x_forwarded_for":"(?:, [^"]+)?".*"request_uri":"/(?:\\.env|\\.git/|wp-login\\.php|xmlrpc\\.php|wp-admin(?:/|$)|phpinfo\\.php|vendor/phpunit|setup\\.php|manager/html|id\\.php|shell\\.php|\\.DS_Store)[^"]*".*"status":(?:40[0-4]|403|404).*$ + ^.*"remote_addr":"".*"request_uri":"/(?:\\.env|\\.git/|wp-login\\.php|xmlrpc\\.php|wp-admin(?:/|$)|phpinfo\\.php|vendor/phpunit|setup\\.php|manager/html|id\\.php|shell\\.php|\\.DS_Store)[^"]*".*"status":(?:40[0-4]|403|404).*$ + +ignoreregex = ^.*"request_uri":"\/(?:stub_status|health\/system|health\/worker|pgadmin4(?:\/|$)|\.well-known\/acme-challenge\/|.*\.(?:css|js|png|jpg|jpeg|gif|svg|ico|webp|woff2?))".*$ \ No newline at end of file diff --git a/fail2ban/filter.d/sshd-pubkey.conf b/fail2ban/filter.d/sshd-pubkey.conf new file mode 100644 index 0000000..0f46263 --- /dev/null +++ b/fail2ban/filter.d/sshd-pubkey.conf @@ -0,0 +1,7 @@ +[Definition] +failregex = ^%(__prefix_line)s(?:error: )?Received disconnect from port \d+: .* \[preauth\]$ + ^%(__prefix_line)sInvalid user .* from port \d+ \[preauth\]$ + ^%(__prefix_line)sFailed publickey for .* from port \d+ ssh2$ + ^%(__prefix_line)sConnection closed by (invalid user )?.* port \d+ \[preauth\]$ + +ignoreregex = \ No newline at end of file diff --git a/fail2ban/jail.d/00-defaults.local b/fail2ban/jail.d/00-defaults.local new file mode 100644 index 0000000..56978d7 --- /dev/null +++ b/fail2ban/jail.d/00-defaults.local @@ -0,0 +1,7 @@ +[DEFAULT] +banaction = iptables-multiport +# Push the rule into DOCKER-USER and cover your ports +action = iptables-multiport[name=nginx, port="80,443,3000", protocol=tcp, chain=DOCKER-USER] +ignoreip = 127.0.0.1/8 ::1 \ + 172.19.0.0/16 172.20.0.0/16 172.22.0.0/16 \ + 5.75.153.161 167.235.254.4 \ No newline at end of file diff --git a/fail2ban/jail.d/nginx-phoenix.local b/fail2ban/jail.d/nginx-phoenix.local new file mode 100644 index 0000000..20c3d53 --- /dev/null +++ b/fail2ban/jail.d/nginx-phoenix.local @@ -0,0 +1,88 @@ +# /etc/fail2ban/jail.d/nginx-phoenix.local +[DEFAULT] +backend = polling +findtime = 10m + +# ban timing: start short, escalate for repeat offenders, add jitter to avoid stampedes +bantime = 5m +bantime.increment = true +bantime.factor = 1.5 +bantime.overalljails = true +bantime.rndtime = 60s + +# honor your global ignore list from 00-defaults.local (don’t repeat here) + +# ----------------------------- +# Common scanners / bad bots +# ----------------------------- +[nginx-badbots] +enabled = true +filter = nginx-badbots +logpath = /data/nginx-logs/access_json.log +port = 80,443,3000 +findtime = 2m +maxretry = 5 +bantime = 30m + +# ----------------------------- +# Bot search / generic probing +# (odd paths, wp-admin, phpinfo, etc.) +# ----------------------------- +[nginx-botsearch] +enabled = true +filter = nginx-botsearch +logpath = /data/nginx-logs/access_json.log +port = 80,443,3000 +findtime = 5m +maxretry = 6 +bantime = 30m + +# ----------------------------- +# Many 4xx in a short window +# (likely brute/scan or broken client) +# ----------------------------- +[nginx-4xx] +enabled = true +filter = nginx-4xx +logpath = /data/nginx-logs/access_json.log +port = 80,443,3000 +findtime = 5m +maxretry = 20 +bantime = 15m + +# ----------------------------- +# Simple HTTP GET/POST flood +# (rate-based; pairs well with nginx rate limit) http-get-dos +# Not in use anymore, because to avoid bloking phx url paths, i would have to manually add all of them to avoi be banned. Instead we use +# nginx-429 witch is managed by nginx and once it hits the rate limit, it send back a 429 status code. +# ----------------------------- +# [http-get-dos] +# enabled = true +# filter = http-get-dos-compressed +# logpath = /data/nginx-logs/access_json.log +# port = 80,443,3000 +# findtime = 60s +# maxretry = 20 +# bantime = 10m + +# ----------------------------- +# (Optional) recidive: longer ban for repeat offenders across jails +# Requires fail2ban.log inside the container (already present by default) +# ----------------------------- +# [recidive] +# enabled = true +# logpath = /data/fail2ban.log /var/log/fail2ban.log +# backend = auto +# banaction = nftables-allports +# findtime = 12h +# maxretry = 4 +# bantime = 24h + +[nginx-429] +enabled = true +filter = nginx-429 +logpath = /data/nginx-logs/access_json.log +port = 80,443,3000 +findtime = 60s +maxretry = 10 +bantime = 10m \ No newline at end of file diff --git a/fail2ban/jail.d/sshd.local b/fail2ban/jail.d/sshd.local new file mode 100644 index 0000000..97774bf --- /dev/null +++ b/fail2ban/jail.d/sshd.local @@ -0,0 +1,17 @@ +[DEFAULT] +banaction = nftables-multiport +backend = auto +findtime = 10m +bantime = 30m +maxretry = 3 +bantime.increment = false +bantime.factor = 1 +bantime.maxtime = 24h +mode = aggressive + +[sshd] +enabled = true +port = 2522 +filter = sshd +logpath = /var/log/auth.log +action = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="tcp"] \ No newline at end of file