I’ve been using fail2ban to protect a number of services from external attacks. The software works well, but what I wanted to do is to have fail2ban update an ACL on a Cisco IOS router rather then the IPtables on the host itself. Here’s the code and some tips on setting it up.
Contents
The Code
Fail2ban requires just 2 files to implement a new method to block attacks. Here’s the files:
/etc/fail2ban/action.d/ciscoios-acl.py
The Python code to perform the router changes uses Netmiko – an excellent Python library to manage network devices.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#!/usr/bin/python # emacs: -*- mode: python; indent-tabs-mode: t -*- # # import sys from netmiko import ConnectHandler platform = 'cisco_ios' host ='router_ip_address' username = 'fail2ban' password = 'fail2ban_secret_password' enable_pass = 'enable_secret_password' acl_name = 'fail2ban_banned' def main(argv): device = ConnectHandler(device_type=platform, ip=host, username=username, password=password, secret=enable_pass) device.enable() device.config_mode() command = argv[1] ipaddr = argv[2] if command == 'ban': # Ban the IP print "Banning %s" % ipaddr device.send_command('ip access-list standard %s' % acl_name, expect_string='\(config-std-nacl\)\#') device.send_command('permit %s' % ipaddr, expect_string='\(config-std-nacl\)\#' ) device.send_command('exit', expect_string='\(config\)\#') elif command == 'unban': # UnBan the IP print "Un-Banning %s" % ipaddr device.send_command('ip access-list standard %s' % acl_name, expect_string='\(config-std-nacl\)\#') device.send_command('no permit %s' % ipaddr, expect_string='\(config-std-nacl\)\#') device.send_command('exit', expect_string='\(config\)\#') else: # Unknown command print "Unknown command" # All done, exit config mode device.send_command('exit', expect_string='\#') #device.send_command('copy running-config startup-config') # save device.disconnect() # Main if __name__ == "__main__": main(sys.argv) |
Note that the address of the router is in here, and so it the username, password, and enable password. You will need to set these to whatever your router is configured with. Because this code contains the username and password, make sure that it is readable only by the root user.
/etc/fail2ban/action.d/ciscoios-acl.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# emacs: -*- mode: conf; indent-tabs-mode: t -*- # Fail2Ban configuration file for Cisco IOS ACLs # # Author: Kerry Thompson # # [Definition] # Option: actionstart # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # #actionstart = touch /var/run/fail2ban/ciscoios_acl.txt actionstart = # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # # actionstop = rm -f /var/run/fail2ban/ciscoios_acl.txt actionstop = # Option: actioncheck # Notes.: command executed once before each actionban command # Values: CMD # actioncheck = # Option: actionban # Notes.: command executed when banning an IP. Take care that the # command is executed with Fail2Ban user rights. # Tags: See jail.conf(5) man page # Values: CMD # actionban = /etc/fail2ban/action.d/ciscoios-acl.py ban <ip> /bin/logger -t fail2ban "Cisco IOS Banning IP <ip>" # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the # command is executed with Fail2Ban user rights. # Tags: See jail.conf(5) man page # Values: CMD # actionunban = /etc/fail2ban/action.d/ciscoios-acl.py unban <ip> /bin/logger -t fail2ban "Cisco IOS Un-Banning IP <ip>" [Init] |
You can see here that I’m calling the Python code to perform the banning & un-banning. I’m also calling logger to create an entry in the system’s syslog.
Configuring and Testing
You need to first log into the router and pre-create the access control list ( ACL ), and bind it to an interface. I’m using an ACL in coordination with the zone-based policy rules, so my router configuration looks a bit like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
! class-map type inspect match-any banned match access-group name fail2ban_banned ! policy-map type inspect Internet-Inside-policy class type inspect banned drop ! ! ... other policy rules in here ! ! zone security Inside zone security Internet zone-pair security Internet-Inside source Internet destination Inside service-policy type inspect Internet-Inside-policy ! ip access-list standard fail2ban_banned ! |
Note that I’m blocking all access into the router for banned IP addresses – not just access to the service that triggered a ban. I’m figuring that if anyone attacks a single service then they should probably get blocked from accessing anything. Of course, you can customise this to meet your specific needs.
Instead of using the zone-based firewall, you could just as easily bind the access list to the external interface – but note that the python code is going to add entries as permit <Banned IP address> so you might need to reverse the logic of that in the code if you’re doing interface ACLs.
Once setup, you can test the code by running:
/etc/fail2ban/action.d/ciscoios-acl.py ban <test IP address>
and then checking that the driver adds the banned IP address to the ACL. Also test that the un-banning action works too.
Once tested, you can add the action into your fail2ban local rules in /etc/fail2ban/jail.local – an example for a simple server running mostly Email services is below:
/etc/fail2ban/jail.local
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# -*-conf-*- [DEFAULT] # Max 3 attempts in 30 mins findtime = 1800 maxretry = 3 # Ban hosts for 2 hours: bantime = 7200 # Override /etc/fail2ban/jail.d/00-firewalld.conf: banaction = ciscoios-acl # Ignore these subnets ignoreip = 127.0.0.1/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12 [sshd] enabled = true [cyrus-imap] enabled = true maxretry = 3 bantime = 7200 [postfix] enabled = true [postfix-sasl] enabled = true |
Note that I’m excluding loopback and RFC1918 addresses just to be safe from misuse and to protect my own access.
1 comments
Hello Kerry,
Are you sending syslog to the server where and fail2ban is hosted? I don’t see the log file you are matching in jail.local
like :
[asterisk-iptables]
enabled = true
filter = asterisk
action = iptables-allports[name=ASTERISK, protocol=all]
sendmail-whois[name=ASTERISK, dest=$EMAIL, sender=fail2ban@rai-stm-voip-02]
logpath = /var/log/asterisk/messages
maxretry = 40
bantime = 86400