Thursday, November 17, 2016

nginx+fail2ban tutorial/document.

fail2ban + Nginx

In this system fail2ban is supposed to parse nginx logs (customized) for 404 and 403 status codes and add iptables rules to block IPs on the network layer from which excessive 404 and 403 are coming up.

Under a DDOS, because of the verity of IPs available, the frequency of banning and unbanning will be large, as a result there the iptables command will run too many times, resulting in an overhead. A system has been created to prevent this overhead even when there are 1000s of Ips being banned and unbanned.

Objective is to prevent overload of the application, brute force attacks by sending frequent failed authentication requests. 404s have also been taken care of to prevent path discovery apart from the same reasons as previously stated.

Architecture

Instead of the banning iptables being run directly by fail2ban, it's indirectly executed by a bash script on a cron job which runs a single iptables command to ban/unban any no. of IPs in bulk.

fail2ban runs as an unprivileged user, writes to files containing the IPs to be banned/unbanned which the script parses and bans/unbans them in bulk using a single execution of iptables command.

Implementation

Since this is done for testing purposes on a minimal local system (Gentoo) which runs a custom kernel (no iptables FILTER table support), a Debian VM will be created which will contain the actual implementation of the project.

Hits to the VM will be done from the base machine.

Prepare VM --

$ cat /etc/gentoo-release

Gentoo Base System release 2.3

Create rootfs image from template --

qemu-img create -f qcow2 -o cluster_size=512,lazy_refcounts=on,backing_file=Debian8NetworkedSSHRepoPackagesEnhancedUpdate.qcow Debian8NetworkedSSHRepoPackagesEnhancedUpdate_fail2ban.qcow 20G

Load KVM modules (not loaded because of minimum and highly customized OS) --

modprobe kvm_intel

Create tap device veth for the VM to connect to the base machine --

modprobe tun;ip tuntap add mode tap veth

Assign ipv6 and ipv4 addresses on a temporary basis --

ip a add fc00::1:1/112 dev veth;ip link set dev veth up

ip a add 192.168.3.1/24 dev veth

Enable KSM --

echo 1 > /sys/kernel/mm/ksm/run

echo 30000 > /sys/kernel/mm/ksm/sleep_millisecs

Start VM –

qemu-system-x86_64 -machine accel=kvm,kernel_irqchip=on,mem-merge=on -drive file=/home/de/large/VM_images/Debian8NetworkedSSHRepoPackagesEnhancedUpdate_fail2ban.qcow,id=centos,if=ide,media=disk,cache=unsafe,aio=threads,index=0 -vnc :1 -device e1000,id=ethnet,vlan=0 -net tap,ifname=veth,script=no,downscript=no,vlan=0 -m 512 -smp 4 -daemonize -device e1000,id=inet,vlan=1,mac=52:54:0F:12:34:57 -net user,id=internet,net=192.168.2.0/24,vlan=1

Login to the VM –

$ ssh root@fc00::1:2

root@fc00::1:2's password:



The programs included with the Debian GNU/Linux system are free software;

the exact distribution terms for each program are described in the

individual files in /usr/share/doc/*/copyright.



Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent

permitted by applicable law.

Last login: Thu Nov 17 12:03:07 2016 from fc00::1:1

root@LINUXADMIN:~#

Configure ipv4 address for the VM

In /etc/network/interfaces –

source /etc/network/interfaces.d/*



# The loopback network interface

auto lo

iface lo inet loopback



# The primary network interface

allow-hotplug eth0

iface eth0 inet6 static

address fc00::1:2

netmask 112

#gateway fc00::1

# dns-* options are implemented by the resolvconf package, if installed

#dns-nameservers fc00::1

#dns-search LinuxAdmin



iface eth0 inet static

address 192.168.3.2

netmask 24



auto eth1

iface eth1 inet dhcp

Bring up the changes via console –

ifdown eth0; ifup eth0

Setup nginx –

This setup is just for testing.

aptitude install nginx

The following NEW packages will be installed:

fontconfig-config{a} fonts-dejavu-core{a} geoip-database{a} libfontconfig1{a} libgd3{a} libgeoip1{a} libjbig0{a}

libjpeg62-turbo{a} libtiff5{a} libvpx1{a} libxml2{a} libxpm4{a} libxslt1.1{a} nginx nginx-common{a} nginx-full{a}

sgml-base{a} xml-core{a}

0 packages upgraded, 18 newly installed, 0 to remove and 27 not upgraded.

Need to get 6,076 kB of archives. After unpacking 16.7 MB will be used.

Do you want to continue? [Y/n/?]



systemctl enable nginx

Synchronizing state for nginx.service with sysvinit using update-rc.d...

Executing /usr/sbin/update-rc.d nginx defaults

Executing /usr/sbin/update-rc.d nginx enable

root@LINUXADMIN:~# systemctl start nginx

Setup virtualhost –

rm /etc/nginx/sites-enabled/default

Create /etc/nginx/conf.d/default.conf

server {

listen *:8080;

root /home/docroot;

}

Setup custom log format for nginx as per requirement, tune it as per VM specs –

user www-data;

worker_processes 1;

pid /run/nginx.pid;



events {

worker_connections 768;

# multi_accept on;

}



http {



##

# Basic Settings

##



sendfile on;

tcp_nopush on;

tcp_nodelay on;

keepalive_timeout 65;

types_hash_max_size 2048;

# server_tokens off;



# server_names_hash_bucket_size 64;

# server_name_in_redirect off;



include /etc/nginx/mime.types;

default_type application/octet-stream;



##

# Logging Settings

##



log_format custom "[$time_local] $remote_addr $status $request";

access_log /var/log/nginx/access.log custom;

error_log /var/log/nginx/error.log;



##

# Gzip Settings

##



gzip on;

gzip_disable "msie6";



# gzip_vary on;

# gzip_proxied any;

# gzip_comp_level 6;

# gzip_buffers 16 8k;

# gzip_http_version 1.1;

# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;



##

# Virtual Host Configs

##



include /etc/nginx/conf.d/*.conf;

include /etc/nginx/sites-enabled/*;

}

Test nginx and start –

nginx -t

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok

nginx: configuration file /etc/nginx/nginx.conf test is successful

systemctl start nginx

Setup fail2ban –

aptitude install fail2ban

The following NEW packages will be installed:

fail2ban file{a} libmagic1{a} libpython-stdlib{a} libpython2.7-minimal{a} libpython2.7-stdlib{a} mime-support{a}

python{a} python-minimal{a} python-pyinotify{a} python2.7{a} python2.7-minimal{a} whois{a}

0 packages upgraded, 13 newly installed, 0 to remove and 0 not upgraded.

Need to get 4,687 kB of archives. After unpacking 20.6 MB will be used.

Do you want to continue? [Y/n/?]

Configure fail2ban to start as unprivileged user –

mkdir /var/fail2ban

useradd -G adm fail2ban

chown fail2ban /var/fail2ban

Group adm is to allow fail2ban to read nginx access logs.

Allow fail2ban user to write logs –

chown fail2ban /var/log/fail2ban.log

Modify fail2ban logrotation config to create new empty log files with the correct permission –

/var/log/fail2ban.log {



weekly

rotate 4

compress



delaycompress

missingok

postrotate

fail2ban-client flushlogs 1>/dev/null

endscript



# If fail2ban runs as non-root it still needs to have write access

# to logfiles.

# create 640 fail2ban adm

create 640 fail2ban adm

}

Create /etc/fail2ban/fail2ban.local to make changes to allow running as the unprivileged user –

[Definition]

socket = /var/fail2ban/fail2ban.sock

pidfile = /var/fail2ban/fail2ban.pid

Make changes to /etc/default/fail2ban –

FAIL2BAN_USER="fail2ban"

Start and enable fail2ban –

systemctl start fail2ban

systemctl enable fail2ban

Synchronizing state for fail2ban.service with sysvinit using update-rc.d...

Executing /usr/sbin/update-rc.d fail2ban defaults

Executing /usr/sbin/update-rc.d fail2ban enable

Create actions –

cat /etc/fail2ban/action.d/nginx.local

[Definition]

actionban = echo -n <ip>, >> /var/fail2ban/ban

actionunban = echo -n <ip>, >> /var/fail2ban/unban

As stated before, these actions append to a file containing the IPs to be banned/unbanned as CSV values (that's why >> has been used).

Create filters –

cat /etc/fail2ban/filter.d/nginx40{3,4}.local

[Definition]

failregex = ^\[ \+0530\] <HOST> 403 .*$

[Definition]

failregex = ^\[ \+0530\] <HOST> 404 .*$

The anchors (^, $) specify that the whole log has been considered.

Create the jail –

cat /etc/fail2ban/jail.local

[nginx_403]

filter = nginx403

logpath = /var/log/nginx/access.log

action = nginx

findtime = 30

maxretry = 5

bantime = 300

usedns = no

enabled = true



[nginx_404]

filter = nginx404

logpath = /var/log/nginx/access.log

action = nginx

findtime = 30

maxretry = 50

bantime = 120

usedns = no

enabled = true



[ssh]

enabled = false

Since ssh service was not a part of the project, but enabled in fail2ban by default on Debian, it has been disabled here.

Make fail2ban read the changes and verify status of jails –

fail2ban-client reload

fail2ban-client status

Status

|- Number of jail: 2

`- Jail list: nginx_404, nginx_403

Create iptables scripts to read files /var/fail2ban/ban, /var/fail2ban/unban and add iptables rules.

cat /usr/bin/fail2ban_iptables.sh

#! /bin/bash

PATH="$PATH:/sbin"

if test -e /var/fail2ban/ban

then

iptables -A INPUT -s `cat /var/fail2ban/ban | sed s/,$//` -j DROP

rm /var/fail2ban/ban

fi



if test -e /var/fail2ban/unban

then

iptables -D INPUT -s `cat /var/fail2ban/unban | sed s/,$//` -j DROP

rm /var/fail2ban/unban

fi

Changes to PATH environment variables are there since cron has a very minimal set of executable search paths.

Fix permissions of the file –

chmod 744 /usr/bin/fail2ban_iptables.sh

Make a cron job to execute the script as root –

root@LINUXADMIN:~# crontab -l | grep -v ^\#

* * * * * /usr/bin/fail2ban_iptables.sh

Testing –

2016-11-17 12:54:57,403 fail2ban.actions[1500]: WARNING [nginx_404] Ban 192.168.3.1

cat /var/fail2ban/ban

192.168.3.1,

After some time (once cron job runs) –

iptables -L

Chain INPUT (policy ACCEPT)

target prot opt source destination

DROP all -- 192.168.3.1 anywhere



Chain FORWARD (policy ACCEPT)

target prot opt source destination



Chain OUTPUT (policy ACCEPT)

target prot opt source destination

The same client on hitting the server –

wget --timeout 5 http://192.168.3.2/xyzz

--2016-11-17 12:55:04-- http://192.168.3.2/xyzz

Connecting to 192.168.3.2:80... failed: Connection timed out.

Retrying.



--2016-11-17 12:55:10-- (try: 2) http://192.168.3.2/xyzz

Connecting to 192.168.3.2:80... failed: Connection timed out.

Retrying.



--2016-11-17 12:55:15-- (try: 3) http://192.168.3.2/xyzz

Connecting to 192.168.3.2:80... failed: Connection timed out.

Retrying.



--2016-11-17 12:55:22-- (try: 4) http://192.168.3.2/xyzz

Connecting to 192.168.3.2:80... failed: Connection timed out.

Retrying.

After 2 minutes –

2016-11-17 12:56:57,541 fail2ban.actions[1500]: WARNING [nginx_404] Unban 192.168.3.1

cat /var/fail2ban/unban

192.168.3.1,

After some time (once cron job runs) –

Chain INPUT (policy ACCEPT)

target prot opt source destination



Chain FORWARD (policy ACCEPT)

target prot opt source destination



Chain OUTPUT (policy ACCEPT)

target prot opt source destination



No comments:

Post a Comment