technology Archive

Automation: Using Fail2Ban to populate an NSX IPSet

Posted August 24, 2018 By Phil Conway

Those of us who are Linux admins will be used to seeing the following on internet-exposed servers:

Aug 24 18:59:47 myinternetbox ftp: pam_unix(ftp:auth): authentication failure; logname= uid=0 euid=0 tty=/dev/ftpd11462 ruser=phil rhost=  user=phil
Aug 24 18:51:30 myinternetbox dovecot: imap-login: Disconnected (auth failed, 1 attempts in 7 secs): user=<root@local>, method=PLAIN, rip=, TLS, session=<yPggBjJ0swByYzMZ>
Aug 23 01:20:51 myinternetbox sendmail[18841]: warning: unknown[]: SASL LOGIN authentication failed: authentication failure

Those same admins will probably be aware of fail2ban, a really cool tool that acts as an intrusion detection mechanism. It scans Linux system logs looking for similar messages to the above (either using the present filters or any you’ve rolled yourself) to look for evidence that someone is trying to get into your system and failing.

Where such logons are detected, you can choose a multitude of retaliatory actions, including adding the offending IP address to iptables as a blocked address, effectively preventing that IP from initiating any further logon attempts to your app. This proves useful where someone is attempting to brute-force a connection to your box by running through all the possible logons and passwords – if they get blocked after the first five attempts, that really limits their potential combinations and chances for success.

Thing is, fail2ban only protects your local linux instance. What if you’re running lots of them (say virtual machines) and they’re hosted on an NSX-enabled cluster? Thats where the script below comes in.

I’ve created this as a way to instantly add malicious IPs to an NSX IPSet. You would typically have this IPSet as the source/destination of an NSX firewall rule preventing any connectivity to/from it.

The script reads the IPset you specify via an API call (GET), parses the XML, appends your offending IP to the existing list, increments the revision number for the IPSet, and then updates the IPset via a second API call (PUT).

In order for this to work, you’ll need:

1) NSX installed and configured correctly on the ESXi cluster you’re running your VMs on
2) At least one linux VM running fail2ban
3) An NSX service account with the necessary privileges to read/write to the NSX API (Security Admin or above). Don’t use your default admin account, this is bad security practice 😉
4) A new IPSet that is linked to a deny-all rule at or near the top of your firewall rule table in NSX (with a single sample IP to start, I used, unlikely to be used on anything internet-facing)
5) This script deploying on your fail2ban hosts and the configuration of a custom fail2ban action invoking it.

Once you have the script deployed, you’ll want to revist your fail2ban configuration to ensure that anyone trying to brute force your FTP/POP/IMAP/SMTP/SSH or HTTP instances is automatically added to the list. You’d tweak your settings as needed (don’t make them too intolerant of logins or your could lock legitimate users out of everything in one go).

To call it, just save it as a bash script (.sh), change the permissions to allow execution (chmod +x) and then execute it with the first variable passed being the IP address you want to add. As an example, if you named your script, and you wanted to add to the list, you’d call “./”.

I just created this as a bit of fun, it would probably be of most use on a honeypot VM that is internet facing, or on a web/mail VM that is being bombarded with these logons. I’ll be updating this post with a second script to remove the IP from the list shortly. Feel free to use this code as you see fit.

# Bash script used to send banned IP addresses to an NSX manager.
# Phil Conway / code geek at philconway dot net
# version 0.1 
# Free to redistribute, amend or edit, use at your own risk

# Variables:

nsxpassword=nsx password

# Fetch contents of current IPSet blacklist and temporarily store in file
curl -u "$nsxusername:$nsxpassword" -X GET https://$nsxmanip/api/2.0/services/ipset/$ipsetref -k > ipset.xml

#// For reference, XML structure should look like this:

#<?xml version="1.0" encoding="UTF-8"?>
# <objectId>ipset-6</objectId>
# <objectTypeName>IPSet</objectTypeName>
# <vsmUuid>42222DF6-97C4-E598-C3AA-28BD43CD79D7</vsmUuid>
# <nodeId>b91e63cf-6a9a-4edc-8f06-0f978ce52e48</nodeId>
# <revision>1</revision>
# <type>
# <typeName>IPSet</typeName>
# </type>
# <name>fail2ban IPBL</name>
# <description></description>
# <scope>
# <id>globalroot-0</id>
# <objectTypeName>GlobalRoot</objectTypeName>
# <name>Global</name>
# </scope>
# <clientHandle></clientHandle>
# <extendedAttributes/>
# <isUniversal>false</isUniversal>
# <universalRevision>0</universalRevision>
# <isTemporal>false</isTemporal>
# <inheritanceAllowed>false</inheritanceAllowed>
# <value></value>
# //

# Seperate out IPsets recorded to date and current revision:
revision=($(grep -oP '(?<=revision>)[^<]+' "ipset.xml" ))
ipsets=($(grep -oP '(?<=value>)[^<]+' "ipset.xml" ))

# Increment revision:
revision=$((revision + 1))

# Append New IP address to existing list:
newipset="$ipsets, $blacklistip"

# Construct updated IPSet XML:

echo '<?xml version="1.0" encoding="UTF-8"?>' > update.xml
echo '<ipset>' >> update.xml
echo '<objectId>ipset-6</objectId>' >> update.xml
echo "<revision>$revision</revision>" >> update.xml
echo "<name>fail2ban IPBL</name>" >> update.xml 
echo "<value>$newipset</value>" >> update.xml
echo '</ipset>' >> update.xml
# //

# Send as new PUT call to NSX manager: 
curl -u "$nsxusername:$nsxpassword" -X PUT https://$nsxmanip/api/2.0/services/ipset/$ipsetref -k --header "Content-Type: application/xml" -d @update.xml

rm -rf ipset.xml
rm -rf update.xml