linuxserver / docker-swag

Nginx webserver and reverse proxy with php support and a built-in Certbot (Let's Encrypt) client. It also contains fail2ban for intrusion prevention.
https://docs.linuxserver.io/general/swag
GNU General Public License v3.0
2.89k stars 246 forks source link

Fail2ban stops banning IP's after a few days #94

Closed theincredibleman closed 1 year ago

theincredibleman commented 3 years ago

linuxserver.io


Expected Behavior

Fail2ban should continue to ban IP's that are found in the Nginx access log.

Current Behavior

When Swag is started, IP's are banned successfully according to a custom jail configuration. After a week or so, IP's are still found (as seen in fail2ban.log), but they are no longer banned. Fail2ban.log just displays "Found -IP-" over and over again without actually banning it. When Swag is restarted, Fail2ban starts banning IP's again.

Steps to Reproduce

  1. Start Swag, test Fail2ban IP ban with a test system. IP is successfully banned after 3 attempts according to jail configuration. Fail2ban.log shows Found -IP-, Found -IP-, Found -IP-, Ban -IP-.
  2. Monitor fail2ban.log, notice that after a few days IP's are still found but no longer banned.
  3. Test Fail2ban IP ban with a test system. IP is found multiple times, but no longer banned.
  4. Jail still shows active using fail2ban-client status.
  5. Restart Swag, test Fail2ban IP ban with a test system again, notice IP is banned and Ban -IP- is logged after 3 attempts according to jail configuration.

Environment

OS: Docker on Debian (Armbian) CPU architecture: arm32 How docker service was installed: From the official Docker repo

Custom jail for Wordpress: Jail.local [wordpress] enabled = true port = http,https filter = wordpress-auth logpath = /config/log/nginx/access.log maxretry = 3 bantime = 3600 wordpress-auth.conf* [Definition] failregex = .POST.(wp-login.php|xmlrpc.php). 403

Command used to create docker container (run/create/compose/screenshot)

Screenshot 2021-02-23 at 18 42 29

Docker logs


      _         ()
     | |  ___   _    __
     | | / __| | |  /  \ 
     | | \__ \ | | | () |
     |_| |___/ |_|  \__/

Brought to you by linuxserver.io

To support the app dev(s) visit: Certbot: https://supporters.eff.org/donate/support-work-on-certbot

To support LSIO projects visit: https://www.linuxserver.io/donate/

GID/UID

User uid: 911 User gid: 911

[cont-init.d] 10-adduser: exited 0. [cont-init.d] 20-config: executing... [cont-init.d] 20-config: exited 0. [cont-init.d] 30-keygen: executing... using keys found in /config/keys [cont-init.d] 30-keygen: exited 0. [cont-init.d] 50-config: executing... Variables set: PUID= PGID= TZ=Europe/Amsterdam URL=contoso.com SUBDOMAINS=www EXTRA_DOMAINS= ONLY_SUBDOMAINS=false VALIDATION=http DNSPLUGIN= EMAIL=bill@contoso.com STAGING=

SUBDOMAINS entered, processing SUBDOMAINS entered, processing Sub-domains processed are: -d www.contoso.com E-mail address entered: bill@contoso.com http validation is selected Certificate exists; parameters unchanged; starting nginx Starting 2019/12/30, GeoIP2 databases require personal license key to download. Please retrieve a free license key from MaxMind, and add a new env variable "MAXMINDDB_LICENSE_KEY", set to your license key. [cont-init.d] 50-config: exited 0. [cont-init.d] 60-renew: executing... The cert does not expire within the next day. Letting the cron script handle the renewal attempts overnight (2:08am). [cont-init.d] 60-renew: exited 0. [cont-init.d] 99-custom-files: executing... [custom-init] no custom files found exiting... [cont-init.d] 99-custom-files: exited 0. [cont-init.d] done. [services.d] starting services [services.d] done. nginx: [alert] detected a LuaJIT version which is not OpenResty's; many optimizations will be disabled and performance will be compromised (see https://github.com/openresty/luajit2 for OpenResty's LuaJIT or, even better, consider using the OpenResty releases from https://openresty.org/en/download.html) Server ready

Extract from Fail2ban log when it stops banning IP's

2021-02-22 04:22:49,634 fail2ban.filter [395]: INFO [wordpress] Found 149.202.188.62 - 2021-02-22 04:22:49 2021-02-22 04:22:50,237 fail2ban.filter [395]: INFO [wordpress] Found 149.202.188.62 - 2021-02-22 04:22:49 2021-02-22 04:22:50,238 fail2ban.filter [395]: INFO [wordpress] Found 149.202.188.62 - 2021-02-22 04:22:50 2021-02-22 04:22:50,373 fail2ban.actions [395]: NOTICE [wordpress] Ban 149.202.188.62 2021-02-22 04:30:29,367 fail2ban.actions [395]: NOTICE [wordpress] Unban 161.35.166.73 2021-02-22 04:51:56,074 fail2ban.filter [395]: INFO [wordpress] Found 47.244.166.23 - 2021-02-22 04:51:55 2021-02-22 04:54:09,663 fail2ban.filter [395]: INFO [wordpress] Found 195.181.166.95 - 2021-02-22 04:54:09 2021-02-22 05:22:50,528 fail2ban.actions [395]: NOTICE [wordpress] Unban 149.202.188.62 2021-02-22 05:23:17,061 fail2ban.filter [395]: INFO [wordpress] Found 62.210.113.228 - 2021-02-22 05:23:16 2021-02-22 05:23:17,673 fail2ban.filter [395]: INFO [wordpress] Found 5.180.62.73 - 2021-02-22 05:23:17 2021-02-22 05:23:17,676 fail2ban.filter [395]: INFO [wordpress] Found 62.210.113.228 - 2021-02-22 05:23:17 2021-02-22 05:23:17,879 fail2ban.filter [395]: INFO [wordpress] Found 62.210.113.228 - 2021-02-22 05:23:17 2021-02-22 05:23:18,641 fail2ban.actions [395]: NOTICE [wordpress] Ban 62.210.113.228 2021-02-22 05:57:33,106 fail2ban.filter [395]: INFO [wordpress] Found 52.231.102.178 - 2021-02-22 05:57:32 2021-02-22 06:21:52,927 fail2ban.filter [395]: INFO [wordpress] Found 69.12.66.198 - 2021-02-22 06:21:52 2021-02-22 06:26:59,247 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:26:58 2021-02-22 06:26:59,851 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:26:59 2021-02-22 06:27:00,456 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:00 2021-02-22 06:27:01,663 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:01 2021-02-22 06:27:02,866 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:02 2021-02-22 06:27:03,470 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:03 2021-02-22 06:27:04,673 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:04 2021-02-22 06:27:05,281 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:05 2021-02-22 06:27:06,490 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:06 2021-02-22 06:27:07,698 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:07 2021-02-22 06:27:08,306 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:08 2021-02-22 06:27:08,909 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:08 2021-02-22 06:27:10,130 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:09 2021-02-22 06:27:11,339 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:10 2021-02-22 06:27:11,942 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:11 2021-02-22 06:27:13,146 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:12 2021-02-22 06:27:13,749 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:13 2021-02-22 06:27:14,353 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:14 2021-02-22 06:27:15,557 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:15 2021-02-22 06:27:16,160 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:16 2021-02-22 06:27:17,364 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:17 2021-02-22 06:27:18,568 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:18 2021-02-22 06:27:19,174 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:18 2021-02-22 06:27:20,382 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:19 2021-02-22 06:27:20,985 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:20 2021-02-22 06:27:21,591 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:21 2021-02-22 06:27:22,799 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:22 2021-02-22 06:27:23,403 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:23 2021-02-22 06:27:24,607 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:24 2021-02-22 06:27:25,811 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:25 2021-02-22 06:27:26,418 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:26 2021-02-22 06:27:27,628 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:27 2021-02-22 06:27:28,231 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:27 2021-02-22 06:27:28,839 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:28 2021-02-22 06:27:30,048 fail2ban.filter [395]: INFO [wordpress] Found 18.231.94.162 - 2021-02-22 06:27:29 2021-02-22 06:29:36,569 fail2ban.filter [395]: INFO [wordpress] Found 193.36.116.149 - 2021-02-22 06:29:36 2021-02-22 06:32:57,263 fail2ban.filter [395]: INFO [wordpress] Found 51.89.241.11 - 2021-02-22 06:32:57 2021-02-22 07:12:07,716 fail2ban.filter [395]: INFO [wordpress] Found 151.106.35.44 - 2021-02-22 07:12:07 2021-02-22 07:53:40,069 fail2ban.filter [395]: INFO [wordpress] Found 134.209.123.101 - 2021-02-22 07:53:39 2021-02-22 08:05:52,959 fail2ban.filter [395]: INFO [wordpress] Found 178.239.173.180 - 2021-02-22 08:05:52 2021-02-22 08:35:30,088 fail2ban.filter [395]: INFO [wordpress] Found 34.83.67.111 - 2021-02-22 08:35:29 2021-02-22 08:53:32,005 fail2ban.filter [395]: INFO [wordpress] Found 217.138.216.12 - 2021-02-22 08:53:31 2021-02-22 09:17:46,713 fail2ban.filter [395]: INFO [wordpress] Found 217.182.140.117 - 2021-02-22 09:17:46 2021-02-22 09:49:11,741 fail2ban.filter [395]: INFO [wordpress] Found 34.83.107.200 - 2021-02-22 09:49:11 2021-02-22 09:49:13,558 fail2ban.filter [395]: INFO [wordpress] Found 34.83.107.200 - 2021-02-22 09:49:13 2021-02-22 09:49:14,167 fail2ban.filter [395]: INFO [wordpress] Found 34.83.107.200 - 2021-02-22 09:49:13 2021-02-22 09:56:50,925 fail2ban.filter [395]: INFO [wordpress] Found 196.240.57.188 - 2021-02-22 09:56:50 2021-02-22 10:30:15,998 fail2ban.filter [395]: INFO [wordpress] Found 109.70.144.27 - 2021-02-22 10:30:15 2021-02-22 10:45:37,255 fail2ban.filter [395]: INFO [wordpress] Found 206.189.85.88 - 2021-02-22 10:45:37 2021-02-22 10:45:43,269 fail2ban.filter [395]: INFO [wordpress] Found 206.189.85.88 - 2021-02-22 10:45:42 2021-02-22 10:45:45,275 fail2ban.filter [395]: INFO [wordpress] Found 206.189.85.88 - 2021-02-22 10:45:44 2021-02-22 11:11:27,173 fail2ban.filter [395]: INFO [wordpress] Found 103.241.205.1 - 2021-02-22 11:11:26 2021-02-22 11:39:32,578 fail2ban.filter [395]: INFO [wordpress] Found 157.230.208.124 - 2021-02-22 11:39:32 2021-02-22 12:08:26,183 fail2ban.filter [395]: INFO [wordpress] Found 160.153.249.218 - 2021-02-22 12:08:25 2021-02-22 12:08:26,988 fail2ban.filter [395]: INFO [wordpress] Found 160.153.249.218 - 2021-02-22 12:08:26 2021-02-22 12:08:26,989 fail2ban.filter [395]: INFO [wordpress] Found 160.153.249.218 - 2021-02-22 12:08:26 2021-02-22 12:32:45,835 fail2ban.filter [395]: INFO [wordpress] Found 185.143.230.225 - 2021-02-22 12:32:45 2021-02-22 12:41:38,976 fail2ban.filter [395]: INFO [wordpress] Found 103.241.205.1 - 2021-02-22 12:41:38 2021-02-22 12:41:42,186 fail2ban.filter [395]: INFO [wordpress] Found 103.241.205.1 - 2021-02-22 12:41:41 2021-02-22 12:41:43,389 fail2ban.filter [395]: INFO [wordpress] Found 103.241.205.1 - 2021-02-22 12:41:43 2021-02-22 13:18:28,882 fail2ban.filter [395]: INFO [wordpress] Found 188.166.214.213 - 2021-02-22 13:18:28 2021-02-22 13:18:39,509 fail2ban.filter [395]: INFO [wordpress] Found 188.166.214.213 - 2021-02-22 13:18:39 2021-02-22 13:18:45,527 fail2ban.filter [395]: INFO [wordpress] Found 188.166.214.213 - 2021-02-22 13:18:44 2021-02-22 13:19:54,123 fail2ban.filter [395]: INFO [wordpress] Found 196.240.57.188 - 2021-02-22 13:19:54 2021-02-22 13:20:33,236 fail2ban.filter [395]: INFO [wordpress] Found 69.12.66.210 - 2021-02-22 13:20:32 2021-02-22 13:54:36,401 fail2ban.filter [395]: INFO [wordpress] Found 23.96.52.55 - 2021-02-22 13:54:35 2021-02-22 14:35:46,601 fail2ban.filter [395]: INFO [wordpress] Found 139.99.196.183 - 2021-02-22 14:35:46 2021-02-22 14:35:49,808 fail2ban.filter [395]: INFO [wordpress] Found 139.99.196.183 - 2021-02-22 14:35:49 2021-02-22 14:35:51,012 fail2ban.filter [395]: INFO [wordpress] Found 139.99.196.183 - 2021-02-22 14:35:50 2021-02-22 15:05:14,716 fail2ban.filter [395]: INFO [wordpress] Found 178.239.173.168 - 2021-02-22 15:05:14 2021-02-22 15:18:40,342 fail2ban.filter [395]: INFO [wordpress] Found 139.99.148.4 - 2021-02-22 15:18:39 2021-02-22 15:18:43,550 fail2ban.filter [395]: INFO [wordpress] Found 139.99.148.4 - 2021-02-22 15:18:42 2021-02-22 15:18:44,753 fail2ban.filter [395]: INFO [wordpress] Found 139.99.148.4 - 2021-02-22 15:18:44 2021-02-22 15:50:00,189 fail2ban.filter [395]: INFO [wordpress] Found 109.70.144.27 - 2021-02-22 15:50:00 2021-02-22 16:03:35,678 fail2ban.filter [395]: INFO [wordpress] Found 103.101.162.209 - 2021-02-22 16:03:35 2021-02-22 16:46:23,627 fail2ban.filter [395]: INFO [wordpress] Found 188.165.239.119 - 2021-02-22 16:46:23 2021-02-22 16:50:00,761 fail2ban.filter [395]: INFO [wordpress] Found 103.77.207.150 - 2021-02-22 16:50:00 2021-02-22 17:13:22,278 fail2ban.filter [395]: INFO [wordpress] Found 69.12.66.251 - 2021-02-22 17:13:21 2021-02-22 17:39:34,147 fail2ban.filter [395]: INFO [wordpress] Found 178.239.173.168 - 2021-02-22 17:39:34 2021-02-22 17:53:05,544 fail2ban.filter [395]: INFO [wordpress] Found 185.72.146.134 - 2021-02-22 17:53:05 2021-02-22 17:56:21,465 fail2ban.filter [395]: INFO [wordpress] Found 109.38.130.213 - 2021-02-22 17:56:21 2021-02-22 17:56:33,704 fail2ban.filter [395]: INFO [wordpress] Found 109.38.130.213 - 2021-02-22 17:56:33 2021-02-22 17:56:41,722 fail2ban.filter [395]: INFO [wordpress] Found 109.38.130.213 - 2021-02-22 17:56:41 2021-02-22 17:56:48,942 fail2ban.filter [395]: INFO [wordpress] Found 109.38.130.213 - 2021-02-22 17:56:48 2021-02-22 17:56:58,169 fail2ban.filter [395]: INFO [wordpress] Found 109.38.130.213 - 2021-02-22 17:56:57 2021-02-22 17:57:07,390 fail2ban.filter [395]: INFO [wordpress] Found 109.38.130.213 - 2021-02-22 17:57:06 2021-02-22 18:11:47,769 fail2ban.filter [395]: INFO [wordpress] Found 192.95.30.59 - 2021-02-22 18:11:47 2021-02-22 18:30:05,723 fail2ban.filter [395]: INFO [wordpress] Found 79.137.77.213 - 2021-02-22 18:30:05 2021-02-22 18:31:29,131 fail2ban.filter [395]: INFO [wordpress] Found 188.165.211.206 - 2021-02-22 18:31:28 2021-02-22 18:34:19,573 fail2ban.filter [395]: INFO [wordpress] Found 195.181.170.137 - 2021-02-22 18:34:19

github-actions[bot] commented 3 years ago

Thanks for opening your first issue here! Be sure to follow the bug or feature issue templates!

Roxedus commented 3 years ago

bantime = 3600 This is an hour. It will unbanned after 3600 seconds.

theincredibleman commented 3 years ago

bantime = 3600 This is an hour. It will unbanned after 3600 seconds.

I understand that. It is not related to my issue in any way though.

Roxedus commented 3 years ago

Fill out the missing info from the template, like how you deployed swag, and logs from fail2ban as well as swag

theincredibleman commented 3 years ago

Fill out the missing info from the template, like how you deployed swag, and logs from fail2ban as well as swag

Added logs and config, although there doesn't seem to be a clue in there.

nishantbb commented 3 years ago

I sometimes see a similar issue. Finding continues but banning will stop. In a normal scenario, it would give an Already Banned notice. Typically this only happens for one of the few filters I use, and it is the most heavily employed one. As noted, the issue is resolved with a reset.

github-actions[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

drizuid commented 3 years ago

couple things i noticed are 1) the puid/pgid are not set 2) compose was not provided as this was deployed in an unsupported way

please replicate the issue after deploying with docker-compose or docker cli and report back.

github-actions[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

lordraiden commented 2 years ago

@theincredibleman did you fix it? I have the same problem it was working before, all the logs indicate everything is fine as always but it doesn't ban the IP

theincredibleman commented 2 years ago

No I never fixed the issue, I was able to implement a workaround however by reducing the attempts on my site by changing the admin URL to something non-standard. Blocking remains stable as long as there are not that many attempts.

lordraiden commented 2 years ago

Any idea why the ban doesn't work?? The IP is banned and I can still browse it imagen

github-actions[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

lordraiden commented 2 years ago

please some help imagen it's working but not applying any rule in the ip tables

iptables -L INPUT -v -n | less imagen iptables -S imagen

and despite this the IP is not being block, I can still access and login.

github-actions[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

drizuid commented 2 years ago

please some help imagen it's working but not applying any rule in the ip tables

did you recreate the issue while installing this container in a supported manner, as mentioned to you on august 31, 2021? If yes, provide the docker compose or docker run command you used to deploy. We do NOT support deploying our containers with portainer.

github-actions[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Erriez commented 2 years ago

Current Behavior

Fail2ban detects a thread correctly and adds the IP address to iptables. Seconds/minutes/hours/days later, the NGINX access.log shows the IP address again in the NGINX log and was not blocked by iptables.

config/log/nginx/access.log

1.   1.2.3.4 - - [10/Aug/2022:03:02:57 +0200] "GET / HTTP/1.0" 444 0 "-" "-" "US"
2.   1.2.3.4 - - [10/Aug/2022:03:02:57 +0200] "OPTIONS / HTTP/1.0" 444 0 "-" "-" "US"
3.   1.2.3.4 - - [10/Aug/2022:03:02:57 +0200] "OPTIONS / RTSP/1.0" 400 150 "-" "-" "US"
4.   1.2.3.4 - - [10/Aug/2022:03:02:59 +0200] "l\x00\x0B\x00\x00\x00\x00\x00\x00\x00\x00\x00" 400 150 "-" "-" "US"
5.   1.2.3.4 - - [10/Aug/2022:03:02:59 +0200] "GET /nice%20ports%2C/Tri%6Eity.txt%2ebak HTTP/1.0" 444 0 "-" "-" "US"

config/log/fail2ban/fail2ban.log

1.   2022-08-10 03:02:57,750 fail2ban.filter         [290]: INFO    [nginx-access-custom] Found 1.2.3.4 - 2022-08-10 03:02:57
2.   2022-08-10 03:02:57,754 fail2ban.filter         [290]: INFO    [nginx-access-custom] Found 1.2.3.4 - 2022-08-10 03:02:57
3.   2022-08-10 03:02:57,912 fail2ban.actions        [290]: NOTICE  [nginx-access-custom] Ban 1.2.3.4
4.   2022-08-10 03:02:57,962 fail2ban.filter         [290]: INFO    [nginx-access-custom] Found 1.2.3.4 - 2022-08-10 03:02:57
5.   2022-08-10 03:02:59,967 fail2ban.filter         [290]: INFO    [nginx-access-custom] Found 1.2.3.4 - 2022-08-10 03:02:59
6.   2022-08-10 03:02:59,970 fail2ban.filter         [290]: INFO    [nginx-access-custom] Found 1.2.3.4 - 2022-08-10 03:02:59

IP tables

$ docker exec -it swag iptables -L -n --line-numbers

Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    f2b-nginx-access-custom  tcp  --  0.0.0.0/0            0.0.0.0/0           
2    f2b-nginx-error-custom  tcp  --  0.0.0.0/0            0.0.0.0/0           
3    f2b-nginx-deny  tcp  --  0.0.0.0/0            0.0.0.0/0           

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination         

Chain f2b-nginx-access-custom (1 references)
num  target     prot opt source               destination         
...
22   REJECT     all  --  1.2.3.4       0.0.0.0/0            reject-with icmp-port-unreachable

Issue

Environment

OS: Ubuntu 20.04 server CPU architecture: arm64 How docker service was installed: ghcr.io/linuxserver/swag:version-1.29.0

Any suggestion why rejected IP addresses are sometimes passed to NGINX?

Erriez commented 2 years ago

Reproducible with a clean Swag setup: NGINX access.log shows incoming and handled Ethernet packets, even when the IP address is blocked with iptables rules configured by fail2ban.

Further investigation is required to configure and use iptables correctly in the Swag Docker container according to the official Docker documentation section Docker and iptables: https://docs.docker.com/network/iptables/

drizuid commented 2 years ago

Environment

OS: Ubuntu 20.04 server CPU architecture: arm64 How docker service was installed: ghcr.io/linuxserver/swag:version-1.29.0

Any suggestion why rejected IP addresses are sometimes passed to NGINX?

You didn't say how the docker service was installed, share your run or compose snippet, or provide container logs. We cannot reproduce this issue internally.

Erriez commented 2 years ago

Thanks for your reply and testing internally. Please find more details about my configuration below:

Expectation:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.3 LTS
Release:        20.04
Codename:       focal

$ uname -a
Linux piserver 5.4.0-1066-raspi #76-Ubuntu SMP PREEMPT Mon Jun 27 11:02:52 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux

$ docker --version
Docker version 20.10.7, build 20.10.7-0ubuntu5~20.04.2
$ which docker
/usr/bin/docker
$ ls -la /usr/bin/docker
-rwxr-xr-x 1 root root 68726808 Oct 22  2021 /usr/bin/docker

$ docker-compose --version
docker-compose version 1.25.0, build unknown
$ which docker-compose
/usr/bin/docker-compose
$ ls -la /usr/bin/docker-compose
-rwxr-xr-x 1 root root 420 Nov 23  2019 /usr/bin/docker-compose*

docker/docker-compose.yml

version: '3'

services:
  # Other containers ommitted

  swag:
    image: ghcr.io/linuxserver/swag:version-1.29.0
    container_name: swag
    restart: always
    ports:
      - 80:80
      - 443:443
    networks:
      default:
        # A fixed IP address for Swag is required for another mail container
        ipv4_address: 192.168.203.200
    cap_add:
      - NET_ADMIN
    environment:
      - PUID=$DOCKER_UID
      - PGID=$DOCKER_GID
      - EMAIL=${SWAG_EMAIL}
      - URL=${ROOT_DOMAIN}
      - SUBDOMAINS=${SWAG_SUBDOMAINS}
      - VALIDATION=${SWAG_VALIDATION}
      - DNSPLUGIN=${SWAG_DNSPLUGIN}
      - PROPAGATION=${SWAG_PROPAGATION}
      - ONLY_SUBDOMAINS=${SWAG_ONLY_SUBDOMAINS}
      - DOCKER_MODS=linuxserver/mods:swag-maxmind
      - MAXMINDDB_LICENSE_KEY=${MAXMINDDB_LICENSE_KEY}
      - TZ=${TZ}
    volumes:
      - ${VOLUME_DIR}/swag/config:/config

# ----------------------------------------------
networks:
  default:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.203.0/24

docker/.env

VOLUME_DIR=./volumes

TZ=Europe/Amsterdam

DOCKER_UID=1000
DOCKER_GID=1000

ROOT_DOMAIN=<REMOVED>
SWAG_EMAIL=<REMOVED>
SWAG_ONLY_SUBDOMAINS=false
SWAG_SUBDOMAINS=wildcard
SWAG_VALIDATION=dns
SWAG_DNSPLUGIN=<REMOVED>
SWAG_PROPAGATION=300
MAXMINDDB_LICENSE_KEY=<REMOVED>

DNS

domain -> <IP>:80 -> raspberry
domain -> <IP>:443 -> raspberry

Docker Swag log

$ docker-compose up swag
Attaching to swag
swag             | [mod-init] Curl/JQ was not found on this system for Docker mods installing
swag             | fetch http://dl-cdn.alpinelinux.org/alpine/v3.14/main/aarch64/APKINDEX.tar.gz
swag             | fetch http://dl-cdn.alpinelinux.org/alpine/v3.14/community/aarch64/APKINDEX.tar.gz
swag             | (1/1) Installing jq (1.6-r1)
swag             | Executing busybox-1.33.1-r8.trigger
swag             | OK: 255 MiB in 228 packages
swag             | [mod-init] Attempting to run Docker Modification Logic
swag             | [mod-init] Applying linuxserver/mods:swag-maxmind files to container
swag             | s6-rc: info: service s6rc-oneshot-runner: starting
swag             | s6-rc: info: service s6rc-oneshot-runner successfully started
swag             | s6-rc: info: service fix-attrs: starting
swag             | s6-rc: info: service 00-legacy: starting
swag             | s6-rc: info: service 00-legacy successfully started
swag             | s6-rc: info: service fix-attrs successfully started
swag             | s6-rc: info: service legacy-cont-init: starting
swag             | cont-init: info: running /etc/cont-init.d/01-envfile
swag             | cont-init: info: /etc/cont-init.d/01-envfile exited 0
swag             | cont-init: info: running /etc/cont-init.d/02-tamper-check
swag             | cont-init: info: /etc/cont-init.d/02-tamper-check exited 0
swag             | cont-init: info: running /etc/cont-init.d/10-adduser
swag             | 
swag             | -------------------------------------
swag             |           _         ()
swag             |          | |  ___   _    __
swag             |          | | / __| | |  /  \
swag             |          | | \__ \ | | | () |
swag             |          |_| |___/ |_|  \__/
swag             | 
swag             | 
swag             | Brought to you by linuxserver.io
swag             | -------------------------------------
swag             | 
swag             | To support the app dev(s) visit:
swag             | Certbot: https://supporters.eff.org/donate/support-work-on-certbot
swag             | 
swag             | To support LSIO projects visit:
swag             | https://www.linuxserver.io/donate/
swag             | -------------------------------------
swag             | GID/UID
swag             | -------------------------------------
swag             | 
swag             | User uid:    1000
swag             | User gid:    1000
swag             | -------------------------------------
swag             | 
swag             | cont-init: info: /etc/cont-init.d/10-adduser exited 0
swag             | cont-init: info: running /etc/cont-init.d/20-config
swag             | cont-init: info: /etc/cont-init.d/20-config exited 0
swag             | cont-init: info: running /etc/cont-init.d/30-keygen
swag             | using keys found in /config/keys
swag             | cont-init: info: /etc/cont-init.d/30-keygen exited 0
swag             | cont-init: info: running /etc/cont-init.d/50-config
swag             | Variables set:
swag             | PUID=1000
swag             | PGID=1000
swag             | TZ=Europe/Amsterdam
swag             | URL=<REMOVED>
swag             | SUBDOMAINS=wildcard
swag             | EXTRA_DOMAINS=
swag             | ONLY_SUBDOMAINS=false
swag             | VALIDATION=dns
swag             | CERTPROVIDER=
swag             | DNSPLUGIN=<REMOVED>
swag             | EMAIL=<REMOVED>
swag             | STAGING=
swag             | 
swag             | Using Let's Encrypt as the cert provider
swag             | SUBDOMAINS entered, processing
swag             | Wildcard cert for <REMOVED> will be requested
swag             | E-mail address entered: <REMOVED>
swag             | dns validation via transip plugin is selected
swag             | Certificate exists; parameters unchanged; starting nginx
swag             | cont-init: info: /etc/cont-init.d/50-config exited 0
swag             | cont-init: info: running /etc/cont-init.d/60-renew
swag             | The cert does not expire within the next day. Letting the cron script handle the renewal attempts overnight (2:08am).
swag             | cont-init: info: /etc/cont-init.d/60-renew exited 0
swag             | cont-init: info: running /etc/cont-init.d/70-templates
swag             | cont-init: info: /etc/cont-init.d/70-templates exited 0
swag             | cont-init: info: running /etc/cont-init.d/90-custom-folders
swag             | cont-init: info: /etc/cont-init.d/90-custom-folders exited 0
swag             | cont-init: info: running /etc/cont-init.d/98-maxmind
swag             | Applying the maxmind mod...
swag             | Applied the maxmind mod
swag             | cont-init: info: /etc/cont-init.d/98-maxmind exited 0
swag             | cont-init: info: running /etc/cont-init.d/99-custom-files
swag             | [custom-init] no custom files found exiting...
swag             | cont-init: info: /etc/cont-init.d/99-custom-files exited 0
swag             | s6-rc: info: service legacy-cont-init successfully started
swag             | s6-rc: info: service legacy-services: starting
swag             | services-up: info: copying legacy longrun cron (no readiness notification)
swag             | services-up: info: copying legacy longrun fail2ban (no readiness notification)
swag             | services-up: info: copying legacy longrun nginx (no readiness notification)
swag             | services-up: info: copying legacy longrun php-fpm (no readiness notification)
swag             | s6-rc: info: service legacy-services successfully started
swag             | s6-rc: info: service 99-ci-service-check: starting
swag             | [ls.io-init] done.
swag             | s6-rc: info: service 99-ci-service-check successfully started
swag             | Server ready

www Default webpage, +robots.txt.

$  ls volumes/swag/config/www/
502.html  index.html  robots.txt

Nginx Additional logging added for fail2ban to block all IP addresses, except some countries in Europe.

$ cat volumes/swag/config/nginx/site-confs/default
error_page 502 /502.html;

# redirect all traffic to https
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    # Do not log my own IP address:
    access_log /config/log/nginx/access.log root if=$log_ip;

    # Allow LAN IP and close connection when country is not whitelisted
    if ($lan-ip = yes) { set $geo-whitelist yes; }
    if ($geo-whitelist = no) { return 444; }

    # Redirect to HTTPS
    return 301 https://$host$request_uri;
}

# main server block
server {
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;

    root /config/www;
    index index.html index.htm index.php;

    server_name _;

    # Do not log my own IP address:
    access_log /config/log/nginx/access.log root if=$log_ip;

    # enable subfolder method reverse proxy confs
    include /config/nginx/proxy-confs/*.subfolder.conf;

    # all ssl related config moved to ssl.conf
    include /config/nginx/ssl.conf;

    client_max_body_size 0;

    # Allow LAN IP and close connection when country is not whitelisted
    if ($lan-ip = yes) { set $geo-whitelist yes; }
    if ($geo-whitelist = no) { return 444; }

    location / {
        try_files $uri $uri/ /index.html /index.php?$args =404;
    }

    location ~ \.php$ {
        deny all;

        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include /etc/nginx/fastcgi_params;
    }
}

# enable subdomain method reverse proxy confs
include /config/nginx/proxy-confs/*.subdomain.conf;
# enable proxy cache for auth
proxy_cache_path cache/ keys_zone=auth_cache:10m;

nginx.conf

$ cat volumes/swag/config/nginx/nginx.conf
http {
    # Add lines below for extensive logging:

    # Sets the path, format, and configuration for a buffered log write.
    access_log /config/log/nginx/access.log;

    map $remote_addr $log_ip {
        default 1;

        "127.0.0.1" 0;
        "<MY_IP_ADDRESS>" 0;
    }

    # GeoIP2 countrycode is used to ban IP
    log_format root '$remote_addr - $remote_user [$time_local] '
                '"$request" $status $body_bytes_sent '
                '"$http_referer" "$http_user_agent" '
                '"$geoip2_data_country_iso_code" "$host" "$server_port"';

maxmind.conf

$ cat volumes/swag/config/nginx/maxmind.conf
geoip2 /config/geoip2db/GeoLite2-City.mmdb {
    auto_reload 1w;
    $geoip2_data_city_name   city names en;
    $geoip2_data_postal_code postal code;
    $geoip2_data_latitude    location latitude;
    $geoip2_data_longitude   location longitude;
    $geoip2_data_state_name  subdivisions 0 names en;
    $geoip2_data_state_code  subdivisions 0 iso_code;
    $geoip2_data_continent_code   continent code;
    $geoip2_data_country_iso_code country iso_code;
}

# Country Codes: https://en.wikipedia.org/wiki/ISO_3166-2

map $geoip2_data_country_iso_code $geo-whitelist {
    default no;
    # Example for whitelisting a country, comment out 'default yes;' above and uncomment 'default no;' and the whitelisted country below
    # default no;
    # For testing purposes allow only one country
    NL yes;
}

map $geoip2_data_country_iso_code $geo-blacklist {
    default yes;
    # Example for blacklisting a country, uncomment the blacklisted country below
    # UK no;
 }

geo $lan-ip {
    default no;
    10.0.0.0/8 yes;
    172.16.0.0/12 yes;
    192.168.0.0/16 yes;
    127.0.0.1 yes;
}

fail2ban

$ cat volumes/swag/config/fail2ban/jail.local
## Version 2022/01/09 - Changelog: https://github.com/linuxserver/docker-swag/commits/master/root/defaults/jail.local
# This is the custom version of the jail.conf for fail2ban
# Feel free to modify this and add additional filters
# Then you can drop the new filter conf files into the fail2ban-filters
# folder and restart the container

[DEFAULT]
# Prevents banning LAN subnets
ignoreip = 10.0.0.0/8
           192.168.0.0/16
           172.16.0.0/12

# Changes the default ban action from "iptables-multiport", which causes issues on some platforms, to "iptables-allports".
banaction = iptables-allports-erriez

# "bantime" is the number of seconds that a host is banned.
bantime  = 365d

# A host is banned if it has generated "maxretry" during the last "findtime"
# seconds.
findtime  = 600

# "maxretry" is the number of failures before a host get banned.
maxretry = 4

[ssh]
enabled = false

[nginx-http-auth]
enabled  = true
filter   = nginx-http-auth
port     = http,https
logpath  = /config/log/nginx/error.log

[nginx-badbots]
enabled  = true
port     = http,https
filter   = nginx-badbots
logpath  = /config/log/nginx/access.log
maxretry = 1

[nginx-botsearch]
enabled  = true
port     = http,https
filter   = nginx-botsearch
logpath  = /config/log/nginx/access.log
maxretry = 1

[nginx-deny]
enabled  = true
port     = http,https
filter   = nginx-deny
logpath  = /config/log/nginx/error.log

[nginx-unauthorized]
enabled  = true
port     = http,https
filter   = nginx-unauthorized
logpath  = /config/log/nginx/unauthorized.log

# Added fail2ban rules
[nginx-access-erriez]
enabled  = true
port     = http,https
filter   = nginx-access-erriez
logpath  = /config/log/nginx/access.log
ignoreip = <MY_IP>
maxretry = 1
# Try to block hackers from common attacks and unwanted countries
$ cat volumes/swag/config/fail2ban/filter.d/nginx-access-erriez.conf
[Definition]

failregex = ^<HOST> -.*"\\x.*"$
            ^<HOST> -.*"CONNECT whois.*"$
            ^<HOST> -.*"CONNECT leakix.*"$
            ^<HOST> -.*"HTTP.*"$
            ^<HOST> -.*"MGLNDD.*"$
            ^<HOST> -.*"SSH.*"$
            ^<HOST> -.*"SSTP.*"$
            ^<HOST> -.*"sh.*"$
            ^<HOST> -.*"l9tcpid.*"$
            ^<HOST> -.*" 444 0 "$
            ^<HOST> -.*"(GET|POST|HEAD) >.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /1.php.*"$
            ^<HOST> -.*"(GET|POST|HEAD) beacon.*"$
            ^<HOST> -.*"(GET|POST|HEAD) example.*"$
            ^<HOST> -.*"(GET|POST|HEAD) (?i)(http).*"$
            ^<HOST> -.*"(GET|POST|HEAD) /\..*"$
            ^<HOST> -.*"(GET|POST|HEAD) ///.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /\?XDEBUG.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /_ignition.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /0bef.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /ab2.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /actuator.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /admin.*"$
            ^<HOST> -.*"(GET|POST|HEAD) (?i)(/autodiscover).*"$
            ^<HOST> -.*"(GET|POST|HEAD) /app/.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /aws.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /backup.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /backend.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /bc.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /bk.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /boa.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /blog.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /c/.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /cdn.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /config.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /console.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /cred.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /d/201ED61C-.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /database.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /demo.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /dns-query.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /ecp.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /epa/.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /ext-js.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /flu.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /gb.*"$
            ^<HOST> -.*"(GET|POST|HEAD) (?i)(/hnap)*"$
            ^<HOST> -.*"(GET|POST|HEAD) /hudson.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /indice.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /invoker.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /jenkins.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /jindex.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /js.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /leaf.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /login.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /library.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /map/.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /manager.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /mailer.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /mgmt.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /mifs.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /msts.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /new.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /nice.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /nmap.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /old.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /owa.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /php.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /pma.*"
            ^<HOST> -.*"(GET|POST|HEAD) /pool.*"
            ^<HOST> -.*"(GET|POST|HEAD) (?i)(/portal).*"$
            ^<HOST> -.*"(GET|POST|HEAD) /public.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /publish.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /query.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /resolve.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /script.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /sdk.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /setup.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /shell.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /sites.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /shop.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /soft.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /solr.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /sql.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /sss.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /stalker.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /stream.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /system.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /template.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /telescope.*"$
            ^<HOST> -.*"(GET|POST|HEAD) (?i)(/uploader).*"$
            ^<HOST> -.*"(GET|POST|HEAD) /users.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /vendor.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /v2.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /wp.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /web.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /wso.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /word.*"$
            ^<HOST> -.*"(GET|POST|HEAD) (?i)(/wuel).*"$
            ^<HOST> -.*"(GET|POST|HEAD) /www.*"$
            ^<HOST> -.*"(GET|POST|HEAD) /zbilakntkhdame.*"$
            ^<HOST> -.*"(GET|POST|HEAD).*(?i)(palo alto).*"$
            ^<HOST> -.*"(GET|POST|HEAD).*(?i)(thinkchaos).*"$
            ^<HOST> -.*"(GET|POST|HEAD).*(?i)(censys).*"$
            ^<HOST> -.*"(GET|POST|HEAD).*(?i)(netsystemsresearch).*"$
            ^<HOST> -.*"(GET|POST|HEAD).*masscan.*"$
            ^<HOST> -.*"(GET|POST|HEAD).*zgrab/.*"$
            ^<HOST> -.*"(GET|POST|HEAD).*python-requests/.*"$
            ^<HOST> -.*" "(AE|AL|AR|AT|AU|AZ|BD|BG|BO|BR|BZ|CA|CH|CL|CM|CN|CO|CY|CZ|EE|EG|ES|FI|GB|HK|HN|HU|ID|IE|IN|IR|IS|JP|KE|KH|KR|LU|LT|MC|MK|MN|MX|MY|NG|NI|NP|PA|PL|PT|PW|RO|RS|RU|SE|SG|TH|TR|TW|TZ|TW|UA|US|VE|VN)" ".*$

ignoreregex =
nemchik commented 2 years ago

If your container is recreated (ex: updated by running docker compose pull && docker compose up -d or similar, or if you're using anything that automatically updates containers like watchtower) the iptables will not persist through the recreation of the container.

Erriez commented 2 years ago

All IP tables are reconstructed correctly after a docker-compose down && docker compose up as fail2ban maintains a database volumes/swag/config/fail2ban/fail2ban.sqlite3 stored in a volume. I did not test docker pull which is not used in my setup. The problem occurs when Swag container is continuously running:

$ docker-compose down
$ docker-compose up

$ docker exec -it swag fail2ban-client status nginx-access-erriez
Status for the jail: nginx-access-erriez
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     8
|  `- File list:        /config/log/nginx/access.log
`- Actions
   |- Currently banned: 10377
   |- Total banned:     10377
   `- Banned IP list: <REMOVED>

And are restored by checking:

$ docker exec -it swag iptables-save
# Generated by iptables-save v1.8.7 on Thu Aug 11 21:09:28 2022
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:f2b-nginx-access-erriez - [0:0]
:f2b-nginx-deny - [0:0]
-A INPUT -p tcp -j f2b-nginx-access-erriez
-A INPUT -p tcp -j f2b-nginx-deny
-A f2b-nginx-access-erriez -s <IP>/32 -j REJECT --reject-with icmp-port-unreachable
...
-A f2b-nginx-access-erriez -j RETURN
-A f2b-nginx-deny -s <IP>/32 -j REJECT --reject-with icmp-port-unreachable
...
-A f2b-nginx-deny -j RETURN
COMMIT
# Completed on Thu Aug 11 21:09:28 2022
# Generated by iptables-save v1.8.7 on Thu Aug 11 21:09:28 2022
*nat
:PREROUTING ACCEPT [5:1210]
:INPUT ACCEPT [2:236]
:OUTPUT ACCEPT [3:180]
:POSTROUTING ACCEPT [17:1017]
:DOCKER_OUTPUT - [0:0]
:DOCKER_POSTROUTING - [0:0]
-A OUTPUT -d 127.0.0.11/32 -j DOCKER_OUTPUT
-A POSTROUTING -d 127.0.0.11/32 -j DOCKER_POSTROUTING
-A DOCKER_OUTPUT -d 127.0.0.11/32 -p tcp -m tcp --dport 53 -j DNAT --to-destination 127.0.0.11:42731
-A DOCKER_OUTPUT -d 127.0.0.11/32 -p udp -m udp --dport 53 -j DNAT --to-destination 127.0.0.11:34699
-A DOCKER_POSTROUTING -s 127.0.0.11/32 -p tcp -m tcp --sport 42731 -j SNAT --to-source :53
-A DOCKER_POSTROUTING -s 127.0.0.11/32 -p udp -m udp --sport 34699 -j SNAT --to-source :53
COMMIT
# Completed on Thu Aug 11 21:09:28 2022
nemchik commented 2 years ago

What is your iptables-allports-erriez action?

Erriez commented 2 years ago

Good catch: It was an experiment to enforce DROP instead of REJECT. Reason is that the original .conf files are restored when restarting Swag container.

Actually, there is no difference between DROP and REJECT, so I reverted back to iptables-allports in jail.local:

# Changes the default ban action from "iptables-multiport", which causes issues on some platforms, to "iptables-allports".
banaction = iptables-allports

[Init?family=inet6] ... -blocktype = REJECT --reject-with icmp6-port-unreachable +blocktype = DROP

nemchik commented 2 years ago

You should be able to do banaction = iptables-allports[blocktype=DROP] in jail.conf (to avoid the extra file). I don't think that's related to your issue though.

Erriez commented 2 years ago

I was not aware of this setting. Thanks for your suggestion. Correct, this is not the issue.

I'll try to simplify the testcase by blocking all IP addresses by fail2ban.

nemchik commented 2 years ago

Try running

docker exec -it swag bash

Then inside the container (replace <IP> with an IP you expect to be banned)

apk add sqlite
sqlite3 /config/fail2ban/fail2ban.sqlite3 "select * from bans where ip = '<IP>'"

This will confirm it exists in the f2b database. then try checking iptables for that same IP. If it is not in iptables then f2b is not correctly reapplying the ban when the container restarts, or it is prematurely removing the ban while the container is running.

Erriez commented 2 years ago

Yes, the fail2ban database is OK (I already checked with another tool):

$ docker exec -it swag bash

# sqlite3 /config/fail2ban/fail2ban.sqlite3 "select * from bans where ip='<IP>'"
nginx-access-erriez|<REMOVED>|1660165370|7776000|1|{"matches": [["<REMOVED>- - [", "10/Aug/2022:23:02:50 +0200", "] \"GET / HTTP/1.0\" 444 0 \"-\" \"Mozilla/5.0 (compatible; <REMOVED>/1.0; +<REMOVED>)\" \"US\" \"<REMOVED>\" \"443\""]], "failures": 1, "ip4": "<IP>"}

# iptables-save | grep <IP>
-A f2b-nginx-access-erriez -s <IP>/32 -j REJECT --reject-with icmp-port-unreachable
Erriez commented 2 years ago

I'm now running a fresh Swag overnight without any other containers and blocks all IP addresses (except my own). I started with an empty volume, so all fail2ban.sqlite3 and iptables are cleared for this test:

Same .env and docker-compose.yml as listed in https://github.com/linuxserver/docker-swag/issues/94#issuecomment-1212347103.

$ cat volumes/swag/config/fail2ban/filter.d/nginx-access-erriez.conf
[Definition]

failregex = ^<HOST> -.*"(GET|POST|HEAD).*"$

ignoreregex =
$ git diff

diff --git a/volumes/swag/config/fail2ban/jail.local b/volumes/swag/config/fail2ban/jail.local
index ebac564..690d529 100644
--- a/volumes/swag/config/fail2ban/jail.local
+++ b/volumes/swag/config/fail2ban/jail.local
@@ -14,7 +14,7 @@ ignoreip = 10.0.0.0/8
 banaction = iptables-allports

 # "bantime" is the number of seconds that a host is banned.
-bantime  = 600
+bantime  = 365d

 # A host is banned if it has generated "maxretry" during the last "findtime"
 # seconds.
@@ -57,3 +57,11 @@ enabled  = true
 port     = http,https
 filter   = nginx-unauthorized
 logpath  = /config/log/nginx/unauthorized.log
+
+[nginx-access-erriez]
+enabled  = true
+port     = http,https
+filter   = nginx-access-erriez
+logpath  = /config/log/nginx/access.log
+ignoreip = <MY>IP>
+maxretry = 1

Waiting for some hackers...

Erriez commented 2 years ago

Results of last test-run:

  1. A hacker tried 3 requests after each other with > 1 second delay. (This is sufficient time for fail2ban to detect and ban the IP)
  2. Fail2ban blocked the IP address on first request and is added to IP tables.
  3. The second request from the IP address of the hacker was not blocked and passed to NGINX.
  4. Fail2ban tried to block the same IP address again and reports: already banned.
  5. Third request same as 3 and 4.
  6. Luckily the hacker gave up this time. (The reported IP address was marked as critical confidence of abuse)

access.log

<IP> - - [12/Aug/2022:04:51:36 +0200] "\x02" 400 150 "-" "-"
<IP> - - [12/Aug/2022:04:51:40 +0200] "hello" 400 150 "-" "-"
<IP> - - [12/Aug/2022:04:51:41 +0200] "\x9EaQ[Q\x22?;\x077\x06(3V4\x18\x04,WR" 400 150 "-" "-"

fail2ban.log

2022-08-12 04:51:36,162 fail2ban.filter         [291]: INFO    [nginx-access-erriez] Found <IP> - 2022-08-12 04:51:36
2022-08-12 04:51:36,286 fail2ban.actions        [291]: NOTICE  [nginx-access-erriez] Ban <IP>
2022-08-12 04:51:40,782 fail2ban.filter         [291]: INFO    [nginx-access-erriez] Found <IP> - 2022-08-12 04:51:40
2022-08-12 04:51:40,967 fail2ban.actions        [291]: NOTICE  [nginx-access-erriez] <IP> already banned
2022-08-12 04:51:41,391 fail2ban.filter         [291]: INFO    [nginx-access-erriez] Found <IP> - 2022-08-12 04:51:41
2022-08-12 04:51:41,612 fail2ban.actions        [291]: NOTICE  [nginx-access-erriez] <IP> already banned

IP tables

$ docker exec -it swag iptables-save | grep <IP>
    -A f2b-nginx-access-erriez -s <IP>/32 -j REJECT --reject-with icmp-port-unreachable

Any suggestions how to investigate further?

nemchik commented 2 years ago

Just to clarify, the iptables still have the IP banned, but they were still able to access nginx?

Also, between items 2 (f2b blocked the ip) and 3 (bad IP still able to access nginx) the container was running the whole time? no restarts, recreates, etc? Your f2b log seems to show these two events happening only a few seconds apart, but I want to be sure I'm understanding it the same way you are.

Erriez commented 2 years ago

Just to clarify, the iptables still have the IP banned, but they were still able to access nginx?

Correct.

Also, between items 2 (f2b blocked the ip) and 3 (bad IP still able to access nginx) the container was running the whole time?

Yes.

no restarts, recreates, etc?

No restarts, no recreates. The Swag container was running last night and happened within 5 seconds (between 04:51:36 and 04:51:41).

aptalca commented 2 years ago

If an entry was added to iptables, then fail2ban did its job. If the IP is not blocked, that suggests an issue with iptables, not fail2ban, or perhaps fail2ban was misconfigured so it didn't add the entry correctly.

Is the access port correct? 80 or 443?

When I try here, the entries are added to iptables and the IPs are blocked successfully.

Erriez commented 2 years ago

If an entry was added to iptables, then fail2ban did its job. If the IP is not blocked, that suggests an issue with iptables, not fail2ban, or perhaps fail2ban was misconfigured so it didn't add the entry correctly.

Yes, I had the same conclusion.

Is the access port correct? 80 or 443?

Both ports 80 and 443 are routed from internet to the Swag container in docker-compose.yml:

version: '3'

services:
  swag:
    image: ghcr.io/linuxserver/swag:version-1.29.0
    container_name: swag-test
    restart: always
    ports:
      - 80:80
      - 443:443
    networks:
      default:
        ipv4_address: 192.168.203.200
    cap_add:
      - NET_ADMIN
    environment:
      - PUID=$DOCKER_UID
      - PGID=$DOCKER_GID
      - EMAIL=${SWAG_EMAIL}
      - URL=${ROOT_DOMAIN}
      - SUBDOMAINS=${SWAG_SUBDOMAINS}
      - VALIDATION=${SWAG_VALIDATION}
      - DNSPLUGIN=${SWAG_DNSPLUGIN}
      - PROPAGATION=${SWAG_PROPAGATION}
      - ONLY_SUBDOMAINS=${SWAG_ONLY_SUBDOMAINS}
      - TZ=${TZ}
    volumes:
      - ${VOLUME_DIR}/swag/config:/config

# ----------------------------------------------
networks:
  default:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.203.0/24

When I try here, the entries are added to iptables and the IPs are blocked successfully.

When my cellphone IP address is blocked by iptables, I can't reached the Swag server and no entry is added to access.log as expected. So it looks the iptables works for most situations, but sometimes (and I don't know when) requests are passed and still reaches NGINX server. When it occurs, I noticed that most IP address are marked as abused by other users and access.log does not show normal HTTP data transfer.

github-actions[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

neofright commented 2 years ago

Apologies for bumping a stale issue, but I wanted to say that as someone who is new to fail2ban, having all of the config examples and information in this thread has been extremely valuable.

I have adjusted my ban times to 365 days, and used the following one liner to update the existing sqlite database: sqlite3 fail2ban.sqlite3 "UPDATE bips SET bantime = 31536000 WHERE bantime = 600;" I verified that the previously ubnanned addresses (after the default of 600 seconds) were indeed re-added to iptables after a restart of the container.

I am also using the nginx-access-erriez.conf filter list/jail that was provided and I find it to be working very well.

github-actions[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

drizuid commented 1 year ago

With the OP using an unsupported deployment method and our new user obfuscating their dnsplugin, usually when I see this issue, the user is using cloudflare with the proxy enabled. fail2ban will ban a cf ip, but not the actual user, thus the ban is ineffectual. This is what the cloudflare real ip docker mod is for.

What DNS plugin is being used in your setup @Erriez ?

Erriez commented 1 year ago

What DNS plugin is being used in your setup @Erriez ?

DNSPLUGIN=transip

drizuid commented 1 year ago

What DNS plugin is being used in your setup @Erriez ?

DNSPLUGIN=transip

Thank you, that rules out my cloudflare thought (i think, I'm not familiar with transip at all)

github-actions[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. This might be due to missing feedback from OP. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] commented 1 year ago

This issue is locked due to inactivity