Compare commits

...
29 Commits
Author SHA1 Message Date
sebres 57820eeda2 test cases extended for verifying ipv4/ipv6, normalized pf-action with test case 2016-05-03 19:47:33 +02:00
sebres 1f2e507135 partially cherry pick from branch 'multi-set', prepare for conditional config parameters logic:
- new readers logic (group some by multiple parameters 'set' -> 'multi-set';
- prevent to add 'known/' parameters twice (by merge section etc);
- test cases fixed;
2016-05-03 19:46:58 +02:00
sebres 2c5ba07f60 test cases extended 2016-05-03 11:41:54 +02:00
sebres c892d83315 preliminary bug fix: wrong actioncheck for IPv6 used;
test cases extended;
2016-05-02 23:07:27 +02:00
sebres b43dfc9383 fix suhosin_log in common paths - log files should be separated using "\n":
prevents to throw an error "File option must be 'head' or 'tail'", if jail suhosin will be enabled.
2016-05-02 19:00:06 +02:00
Yaroslav Halchenko 42144b05a0 RF/ENH: 1st wave of IPAddr pythonization - properties, logical statements, etc 2016-05-01 10:19:10 +02:00
Alexander Koeppe 8f035c13ad new testcase to test beautifier code 2016-05-01 10:19:10 +02:00
Alexander Koeppe 55c0fc3ba0 Add ipaddr.py to MANIFEST file 2016-04-30 20:37:25 +02:00
Alexander Koeppe 4196b8d801 Quite little adjustments on tests and return value 2016-04-30 20:37:25 +02:00
Alexander Koeppe 6e7b4a8221 use same naming for iptables chains for v4 and v6 2016-04-30 20:37:24 +02:00
Alexander Koeppe fd36287354 remove unnecessary changes due to introduction of iparg decorator 2016-04-30 20:37:24 +02:00
Alexander Koeppe caa4521169 optional IP address argument of iparg decorator 2016-04-30 20:36:28 +02:00
Alexander Koeppe 1494b14b37 move IPAddr related code to dedicated source file ipaddr.py 2016-04-30 20:36:28 +02:00
Yaroslav Halchenko ce54db79bc ENH: asip and @iparg helpers to centralize and uniformize conversion from str to IPAddr
Defined in the same file (and before) as IPAddr to avoid looping of
imports
2016-04-30 20:35:29 +02:00
Alexander Koeppe dcfa8c5aa4 treat IP address as objects also in tests 2016-04-30 20:35:29 +02:00
Alexander Koeppe a9d691b0f5 remove obsolete IP related code from DNSUtils class 2016-04-30 20:34:09 +02:00
Alexander Koeppe b65a9e54a1 Update ChangeLog 2016-04-30 20:34:08 +02:00
Alexander Koeppe 3d32099144 adding myself to THANKS 2016-04-30 20:34:08 +02:00
Alexander Koeppe ad2235f66b update jail.conf(5) man-page 2016-04-30 20:34:08 +02:00
Alexander Koeppe 3b8078d0e1 add ip6-loopback to default ignoreip statement 2016-04-30 20:34:08 +02:00
Alexander Koeppe 8724bbba93 convert downloaded IP list to IPAddr objects in badips action 2016-04-30 20:34:08 +02:00
Alexander Koeppe 4295ded9b6 improve PF action and make IPv6 aware 2016-04-30 20:34:08 +02:00
Alexander Koeppe 13ae078797 introduce action*6 statements for iptables config files 2016-04-30 20:34:08 +02:00
Alexander Koeppe 5e9fd58c45 dedicated action handling for IPv6 2016-04-30 20:34:08 +02:00
Alexander Koeppe cedb4d5f85 explicitely treat join arguments as string in beautifier 2016-04-30 20:34:07 +02:00
Alexander Koeppe 5fe2912c03 change IP address string to object handling part 1 2016-04-30 20:34:07 +02:00
Alexander Koeppe 674e15c851 Make DNS resolution IP address family idependent 2016-04-30 20:34:07 +02:00
Alexander Koeppe 7e689d20e7 Make ignoreip checking address family idependent 2016-04-30 20:34:07 +02:00
Alexander Koeppe 18ae5503d3 New class IPAddr for handling IPv4 and IPv6 addresses 2016-04-30 20:34:07 +02:00
43 changed files with 1441 additions and 266 deletions
+8
View File
@@ -78,6 +78,14 @@ ver. 0.9.4 (2016/03/08) - for-you-ladies
* sshd filter got new failregex to match "maximum authentication
attempts exceeded" (introduced in openssh 6.8)
* Added filter for Mac OS screen sharing (VNC) daemon
* IPv6 support:
- IP addresses are now handled as objects rather than strings capable for
handling both address types IPv4 and IPv6
- iptables related actions have been amended to support IPv6 specific actions
additionally
- hostsdeny and route actions have been tested to be aware of v4 and v6 already
- pf action for *BSD systems has been improved and supports now also v4 and v6
- Name resolution is now working for either address type
- Enhancements:
* Do not rotate empty log files
+2
View File
@@ -163,6 +163,7 @@ fail2ban/client/__init__.py
fail2ban/client/jailreader.py
fail2ban/client/jailsreader.py
fail2ban/exceptions.py
fail2ban/ipaddr.py
fail2ban/helpers.py
fail2ban/__init__.py
fail2ban/protocol.py
@@ -200,6 +201,7 @@ fail2ban/tests/actionstestcase.py
fail2ban/tests/actiontestcase.py
fail2ban/tests/banmanagertestcase.py
fail2ban/tests/clientreadertestcase.py
fail2ban/tests/clientbeautifiertestcase.py
fail2ban/tests/config/action.d/brokenaction.conf
fail2ban/tests/config/fail2ban.conf
fail2ban/tests/config/filter.d/simple.conf
+1
View File
@@ -12,6 +12,7 @@ Adrien Clerc
ache
ag4ve (Shawn)
Alasdair D. Campbell
Alexander Koeppe (IPv6 support)
Alexandre Perrin (kAworu)
Amir Caspi
Amy
+3 -2
View File
@@ -32,6 +32,7 @@ else:
from urllib import urlencode
from fail2ban.server.actions import ActionBase
from fail2ban.server.filter import IPAddr
class BadIPsAction(ActionBase):
@@ -259,7 +260,7 @@ class BadIPsAction(ActionBase):
for ip in ips:
try:
self._jail.actions[self.banaction].ban({
'ip': ip,
'ip': IPAddr(ip),
'failures': 0,
'matches': "",
'ipmatches': "",
@@ -280,7 +281,7 @@ class BadIPsAction(ActionBase):
for ip in ips:
try:
self._jail.actions[self.banaction].unban({
'ip': ip,
'ip': IPAddr(ip),
'failures': 0,
'matches': "",
'ipmatches': "",
+14
View File
@@ -3,6 +3,8 @@
# Author: Cyril Jaquier
# Modified: Yaroslav O. Halchenko <debian@onerussian.com>
# made active on all ports from original iptables.conf
# Modified: Alexander Koeppe <format_c@online.de>
# make config file IPv6 capable
#
#
@@ -21,6 +23,10 @@ actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -p <protocol> -j f2b-<name>
actionstart6 = <ip6tables> -N f2b-<name>
<ip6tables> -A f2b-<name> -j <returntype>
<ip6tables> -I <chain> -p <protocol> -j f2b-<name>
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
@@ -29,11 +35,16 @@ actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
<iptables> -F f2b-<name>
<iptables> -X f2b-<name>
actionstop6 = <ip6tables> -D <chain> -p <protocol> -j f2b-<name>
<ip6tables> -F f2b-<name>
<ip6tables> -X f2b-<name>
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
actioncheck6 = <ip6tables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
@@ -42,6 +53,7 @@ actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
# Values: CMD
#
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
actionban6 = <ip6tables> -I f2b-<name> 1 -s <ip> -j <blocktype6>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@@ -50,6 +62,8 @@ actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
# Values: CMD
#
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
actionunban6 = <ip6tables> -D f2b-<name> -s <ip> -j <blocktype6>
[Init]
+9
View File
@@ -6,6 +6,9 @@
# used in all iptables based actions by default.
#
# The user can override the defaults in iptables-common.local
#
# Modified: Alexander Koeppe <format_c@online.de>
# made config file IPv6 capable
[INCLUDES]
@@ -43,6 +46,7 @@ protocol = tcp
# REJECT, REJECT --reject-with icmp-port-unreachable
# Values: STRING
blocktype = REJECT --reject-with icmp-port-unreachable
blocktype6 = REJECT --reject-with icmp6-port-unreachable
# Option: returntype
# Note: This is the default rule on "actionstart". This should be RETURN
@@ -62,3 +66,8 @@ lockingopt = -w
# Notes.: Actual command to be executed, including common to all calls options
# Values: STRING
iptables = iptables <lockingopt>
# Option: ip6tables
# Notes.: Actual command to be executed, including common to all calls options
# Values: STRING
ip6tables = ip6tables <lockingopt>
@@ -12,6 +12,9 @@
#
# If you are running on an older kernel you make need to patch in external
# modules which probably won't be protocol version 6.
#
# Modified: Alexander Koeppe <format_c@online.de>
# made config file IPv6 capable
[INCLUDES]
@@ -26,6 +29,9 @@ before = iptables-common.conf
actionstart = ipset create f2b-<name> hash:ip timeout <bantime>
<iptables> -I <chain> -m set --match-set f2b-<name> src -j <blocktype>
actionstart6 = ipset create f2b-<name>6 hash:ip timeout <bantime> family inet6
<ip6tables> -I <chain> -m set --match-set f2b-<name>6 src -j <blocktype6>
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
@@ -34,6 +40,10 @@ actionstop = <iptables> -D <chain> -m set --match-set f2b-<name> src -j <blockty
ipset flush f2b-<name>
ipset destroy f2b-<name>
actionstop6 = <ip6tables> -D <chain> -m set --match-set f2b-<name>6 src -j <blocktype6>
ipset flush f2b-<name>6
ipset destroy f2b-<name>6
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
@@ -41,6 +51,7 @@ actionstop = <iptables> -D <chain> -m set --match-set f2b-<name> src -j <blockty
# Values: CMD
#
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
actionban6 = ipset add f2b-<name>6 <ip> timeout <bantime> -exist
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@@ -49,6 +60,7 @@ actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
# Values: CMD
#
actionunban = ipset del f2b-<name> <ip> -exist
actionunban6 = ipset del f2b-<name>6 <ip> -exist
[Init]
@@ -12,6 +12,9 @@
#
# If you are running on an older kernel you make need to patch in external
# modules.
#
# Modified: Alexander Koeppe <format_c@online.de>
# made config file IPv6 capable
[INCLUDES]
@@ -26,6 +29,9 @@ before = iptables-common.conf
actionstart = ipset create f2b-<name> hash:ip timeout <bantime>
<iptables> -I <chain> -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name> src -j <blocktype>
actionstart6 = ipset create f2b-<name>6 hash:ip timeout <bantime> family inet6
<ip6tables> -I <chain> -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name>6 src -j <blocktype6>
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
@@ -34,6 +40,10 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -m
ipset flush f2b-<name>
ipset destroy f2b-<name>
actionstop6 = <ip6tables> -D <chain> -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name>6 src -j <blocktype6>
ipset flush f2b-<name>6
ipset destroy f2b-<name>6
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
@@ -41,6 +51,7 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -m
# Values: CMD
#
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
actionban6 = ipset add f2b-<name>6 <ip> timeout <bantime> -exist
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@@ -49,6 +60,7 @@ actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
# Values: CMD
#
actionunban = ipset del f2b-<name> <ip> -exist
actionunban6 = ipset del f2b-<name>6 <ip> -exist
[Init]
@@ -7,6 +7,8 @@
# make "f2b-<name>-log" chain to log and drop
# insert a jump to f2b-<name> from -I <chain> if proto/port match
#
# Modified: Alexander Koeppe <format_c@online.de>
# made config file IPv6 capable
#
[INCLUDES]
@@ -26,6 +28,13 @@ actionstart = <iptables> -N f2b-<name>
<iptables> -I f2b-<name>-log -j LOG --log-prefix "$(expr f2b-<name> : '\(.\{1,23\}\)'):DROP " --log-level warning -m limit --limit 6/m --limit-burst 2
<iptables> -A f2b-<name>-log -j <blocktype>
actionstart6 = <ip6tables> -N f2b-<name>
<ip6tables> -A f2b-<name> -j <returntype>
<ip6tables> -I <chain> 1 -p <protocol> -m multiport --dports <port> -j f2b-<name>
<ip6tables> -N f2b-<name>-log
<ip6tables> -I f2b-<name>-log -j LOG --log-prefix "$(expr f2b-<name> : '\(.\{1,23\}\)'):DROP " --log-level warning -m limit --limit 6/m --limit-burst 2
<ip6tables> -A f2b-<name>-log -j <blocktype6>
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
@@ -36,11 +45,18 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -j
<iptables> -X f2b-<name>
<iptables> -X f2b-<name>-log
actionstop6 = <ip6tables> -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
<ip6tables> -F f2b-<name>
<ip6tables> -F f2b-<name>-log
<ip6tables> -X f2b-<name>
<ip6tables> -X f2b-<name>-log
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck = <iptables> -n -L f2b-<name>-log >/dev/null
actioncheck6 = <ip6tables> -n -L f2b-<name>-log >/dev/null
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
@@ -49,6 +65,7 @@ actioncheck = <iptables> -n -L f2b-<name>-log >/dev/null
# Values: CMD
#
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j f2b-<name>-log
actionban6 = <ip6tables> -I f2b-<name> 1 -s <ip> -j f2b-<name>-log
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@@ -57,6 +74,7 @@ actionban = <iptables> -I f2b-<name> 1 -s <ip> -j f2b-<name>-log
# Values: CMD
#
actionunban = <iptables> -D f2b-<name> -s <ip> -j f2b-<name>-log
actionunban6 = <ip6tables> -D f2b-<name> -s <ip> -j f2b-<name>-log
[Init]
+13
View File
@@ -2,6 +2,8 @@
#
# Author: Cyril Jaquier
# Modified by Yaroslav Halchenko for multiport banning
# Modified: Alexander Koeppe <format_c@online.de>
# made config file IPv6 capable
#
[INCLUDES]
@@ -18,6 +20,10 @@ actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
actionstart6 = <ip6tables> -N f2b-<name>
<ip6tables> -A f2b-<name> -j <returntype>
<ip6tables> -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
@@ -26,11 +32,16 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -j
<iptables> -F f2b-<name>
<iptables> -X f2b-<name>
actionstop6 = <ip6tables> -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
<ip6tables> -F f2b-<name>
<ip6tables> -X f2b-<name>
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
actioncheck6 = <ip6tables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
@@ -39,6 +50,7 @@ actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
# Values: CMD
#
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
actionban6 = <ip6tables> -I f2b-<name> 1 -s <ip> -j <blocktype6>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@@ -47,6 +59,7 @@ actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
# Values: CMD
#
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
actionunban6 = <ip6tables> -D f2b-<name> -s <ip> -j <blocktype6>
[Init]
+13
View File
@@ -4,6 +4,8 @@
# Copied from iptables.conf and modified by Yaroslav Halchenko
# to fulfill the needs of bugreporter dbts#350746.
#
# Modified: Alexander Koeppe <format_c@online.de>
# made config file IPv6 capable
#
[INCLUDES]
@@ -20,6 +22,10 @@ actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
actionstart6 = <ip6tables> -N f2b-<name>
<ip6tables> -A f2b-<name> -j <returntype>
<ip6tables> -I <chain> -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
@@ -28,11 +34,16 @@ actionstop = <iptables> -D <chain> -m state --state NEW -p <protocol> --dport <p
<iptables> -F f2b-<name>
<iptables> -X f2b-<name>
actionstop6 = <ip6tables> -D <chain> -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
<ip6tables> -F f2b-<name>
<ip6tables> -X f2b-<name>
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
actioncheck6 = <ip6tables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
@@ -41,6 +52,7 @@ actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
# Values: CMD
#
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
actionban6 = <ip6tables> -I f2b-<name> 1 -s <ip> -j <blocktype6>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@@ -49,6 +61,7 @@ actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
# Values: CMD
#
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
actionunban6 = <ip6tables> -D f2b-<name> -s <ip> -j <blocktype6>
[Init]
@@ -2,6 +2,8 @@
#
# Author: Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
#
# Modified: Alexander Koeppe <format_c@online.de>
# made config file IPv6 capable
#
[INCLUDES]
@@ -33,6 +35,7 @@ before = iptables-common.conf
# safeguard in case the fail2ban process dies unexpectedly. The
# shorter of the two timeouts actually matters.
actionstart = if [ `id -u` -eq 0 ];then <iptables> -I <chain> -m recent --update --seconds 3600 --name f2b-<name> -j <blocktype>;fi
actionstart6 = if [ `id -u` -eq 0 ];then <ip6tables> -I <chain> -m recent --update --seconds 3600 --name f2b-<name>6 -j <blocktype6>;fi
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
@@ -40,12 +43,15 @@ actionstart = if [ `id -u` -eq 0 ];then <iptables> -I <chain> -m recent --update
#
actionstop = echo / > /proc/net/xt_recent/f2b-<name>
if [ `id -u` -eq 0 ];then <iptables> -D <chain> -m recent --update --seconds 3600 --name f2b-<name> -j <blocktype>;fi
actionstop6 = echo / > /proc/net/xt_recent/f2b-<name>6
if [ `id -u` -eq 0 ];then <ip6tables> -D <chain> -m recent --update --seconds 3600 --name f2b-<name>6 -j <blocktype6>;fi
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck = test -e /proc/net/xt_recent/f2b-<name>
actioncheck6 = test -e /proc/net/xt_recent/f2b-<name>6
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
@@ -54,6 +60,7 @@ actioncheck = test -e /proc/net/xt_recent/f2b-<name>
# Values: CMD
#
actionban = echo +<ip> > /proc/net/xt_recent/f2b-<name>
actionban6 = echo +<ip> > /proc/net/xt_recent/f2b-<name>6
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@@ -62,6 +69,7 @@ actionban = echo +<ip> > /proc/net/xt_recent/f2b-<name>
# Values: CMD
#
actionunban = echo -<ip> > /proc/net/xt_recent/f2b-<name>
actionunban6 = echo -<ip> > /proc/net/xt_recent/f2b-<name>6
[Init]
+13
View File
@@ -2,6 +2,8 @@
#
# Author: Cyril Jaquier
#
# Modified: Alexander Koeppe <format_c@online.de>
# made config file IPv6 capable
#
[INCLUDES]
@@ -18,6 +20,10 @@ actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -p <protocol> --dport <port> -j f2b-<name>
actionstart6 = <ip6tables> -N f2b-<name>
<ip6tables> -A f2b-<name> -j <returntype>
<ip6tables> -I <chain> -p <protocol> --dport <port> -j f2b-<name>
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
@@ -26,11 +32,16 @@ actionstop = <iptables> -D <chain> -p <protocol> --dport <port> -j f2b-<name>
<iptables> -F f2b-<name>
<iptables> -X f2b-<name>
actionstop6 = <ip6tables> -D <chain> -p <protocol> --dport <port> -j f2b-<name>
<ip6tables> -F f2b-<name>
<ip6tables> -X f2b-<name>
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
actioncheck6 = <ip6tables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
@@ -39,6 +50,7 @@ actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
# Values: CMD
#
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
actionban6 = <ip6tables> -I f2b-<name> 1 -s <ip> -j <blocktype6>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@@ -47,6 +59,7 @@ actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
# Values: CMD
#
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
actionunban6 = <ip6tables> -D f2b-<name> -s <ip> -j <blocktype6>
[Init]
+25 -8
View File
@@ -3,6 +3,7 @@
# OpenBSD pf ban/unban
#
# Author: Nick Hilliard <nick@foobar.org>
# Modified by: Alexander Koeppe making PF work seamless and with IPv4 and IPv6
#
#
@@ -12,23 +13,27 @@
# Notes.: command executed once at the start of Fail2Ban.
# Values: CMD
#
# we don't enable PF automatically, as it will be enabled elsewhere
actionstart =
# we don't enable PF automatically; to enable run pfctl -e
# or add `pf_enable="YES"` to /etc/rc.conf (tested on FreeBSD)
actionstart = echo "table <<tablename>-<name>> persist counters" | pfctl -f-
echo "block proto <protocol> from <<tablename>-<name>> to any port <port>" | pfctl -f-
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
#
# we don't disable PF automatically either
actionstop =
# we only disable PF rules we've installed prior
actionstop = pfctl -sr 2>/dev/null | grep -v <tablename>-<name> | pfctl -f-
pfctl -t <tablename>-<name> -T flush
pfctl -t <tablename>-<name> -T kill
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck =
actioncheck = pfctl -sr | grep -q <tablename>-<name>
# Option: actionban
@@ -39,7 +44,7 @@ actioncheck =
# <time> unix timestamp of the ban time
# Values: CMD
#
actionban = /sbin/pfctl -t <tablename> -T add <ip>/32
actionban = pfctl -t <tablename>-<name> -T add <ip>
# Option: actionunban
@@ -51,12 +56,24 @@ actionban = /sbin/pfctl -t <tablename> -T add <ip>/32
# Values: CMD
#
# note -r option used to remove matching rule
actionunban = /sbin/pfctl -t <tablename> -T delete <ip>/32
actionunban = pfctl -t <tablename>-<name> -T delete <ip>
[Init]
# Option: tablename
# Notes.: The pf table name.
# Values: [ STRING ]
#
tablename = fail2ban
tablename = f2b
# Option: protocol
# Notes.: internally used by config reader for interpolations.
# Values: [ tcp | udp | icmp | ipv6-icmp ] Default: tcp
#
protocol = tcp
# Option: port
# Notes.: the port to block, defaults to any
# Values: [ STRING ]
#
port = any
+1 -1
View File
@@ -47,7 +47,7 @@ before = paths-debian.conf
# "ignoreip" can be an IP address, a CIDR mask or a DNS host. Fail2ban will not
# ban a host which matches an address in this list. Several addresses can be
# defined using space (and/or comma) separator.
ignoreip = 127.0.0.1/8
ignoreip = 127.0.0.1/8 ::1
# External command that will take an tagged arguments to ignore, e.g. <ip>,
# and return true if the IP is to be ignored. False otherwise.
+2 -1
View File
@@ -40,7 +40,8 @@ lighttpd_error_log = /var/log/lighttpd/error.log
# http://www.hardened-php.net/suhosin/configuration.html#suhosin.log.syslog.facility
# syslog_user is the default. Lighttpd also hooks errors into its log.
suhosin_log = %(syslog_user)s %(lighttpd_error_log)s
suhosin_log = %(syslog_user)s
%(lighttpd_error_log)s
# defaults to ftp or local2 if ftp doesn't exist
proftpd_log = %(syslog_ftp)s
+22 -21
View File
@@ -35,13 +35,18 @@ logSys = getLogger(__name__)
class ActionReader(DefinitionInitConfigReader):
_configOpts = [
["string", "actionstart", None],
["string", "actionstop", None],
["string", "actioncheck", None],
["string", "actionban", None],
["string", "actionunban", None],
]
_configOpts = {
"actionstart": ["string", None],
"actionstart6": ["string", None],
"actionstop": ["string", None],
"actionstop6": ["string", None],
"actioncheck": ["string", None],
"actioncheck6": ["string", None],
"actionban": ["string", None],
"actionban6": ["string", None],
"actionunban": ["string", None],
"actionunban6": ["string", None],
}
def __init__(self, file_, jailName, initOpts, **kwargs):
self._name = initOpts.get("actname", file_)
@@ -65,20 +70,16 @@ class ActionReader(DefinitionInitConfigReader):
head = ["set", self._jailName]
stream = list()
stream.append(head + ["addaction", self._name])
head.extend(["action", self._name])
for opt in self._opts:
if opt == "actionstart":
stream.append(head + ["actionstart", self._opts[opt]])
elif opt == "actionstop":
stream.append(head + ["actionstop", self._opts[opt]])
elif opt == "actioncheck":
stream.append(head + ["actioncheck", self._opts[opt]])
elif opt == "actionban":
stream.append(head + ["actionban", self._opts[opt]])
elif opt == "actionunban":
stream.append(head + ["actionunban", self._opts[opt]])
multi = []
for opt, optval in self._opts.iteritems():
if opt in self._configOpts:
multi.append([opt, optval])
if self._initOpts:
for p in self._initOpts:
stream.append(head + [p, self._initOpts[p]])
for opt, optval in self._initOpts.iteritems():
multi.append([opt, optval])
if len(multi) > 1:
stream.append(["multi-set", self._jailName, "action", self._name, multi])
elif len(multi):
stream.append(["set", self._jailName, "action", self._name] + multi[0])
return stream
+2 -2
View File
@@ -78,13 +78,13 @@ class Beautifier:
prefix1 = " " if n == len(response) - 1 else "| "
for m, res2 in enumerate(res1[1]):
prefix2 = prefix1 + ("`-" if m == len(res1[1]) - 1 else "|-")
val = " ".join(res2[1]) if isinstance(res2[1], list) else res2[1]
val = " ".join(map(str, res2[1])) if isinstance(res2[1], list) else res2[1]
msg.append("%s %s:\t%s" % (prefix2, res2[0], val))
else:
msg = ["Status"]
for n, res1 in enumerate(response):
prefix1 = "`-" if n == len(response) - 1 else "|-"
val = " ".join(res1[1]) if isinstance(res1[1], list) else res1[1]
val = " ".join(map(str, res1[1])) if isinstance(res1[1], list) else res1[1]
msg.append("%s %s:\t%s" % (prefix1, res1[0], val))
msg = "\n".join(msg)
elif inC[1] == "syslogsocket":
+2 -2
View File
@@ -231,7 +231,7 @@ after = 1.conf
# save previous known values, for possible using in local interpolations later:
sk = {}
for k, v in s2.iteritems():
if not k.startswith('known/'):
if not k.startswith('known/') and k != '__name__':
sk['known/'+k] = v
s2.update(sk)
# merge section
@@ -256,7 +256,7 @@ after = 1.conf
alls = self.get_sections()
sk = {}
for k, v in options.iteritems():
if pref == '' or not k.startswith(pref):
if pref == '' or (not k.startswith(pref) and k != '__name__'):
sk[pref+k] = v
alls[section].update(sk)
+30 -22
View File
@@ -203,40 +203,47 @@ class ConfigReaderUnshared(SafeConfigParserWithIncludes):
#
# Read the given option in the configuration file. Default values
# are used...
# Each optionValues entry is composed of an array with:
# 0 -> the type of the option
# 1 -> the name of the option
# 2 -> the default value for the option
# Each options entry is composed of an array with:
# [[type, name, default], ...]
# Or it is a dict:
# {name: [type, default], ...}
def getOptions(self, sec, options, pOptions=None):
values = dict()
for option in options:
try:
if option[0] == "bool":
v = self.getboolean(sec, option[1])
elif option[0] == "int":
v = self.getint(sec, option[1])
for optname in options:
if isinstance(options, (list,tuple)):
if len(optname) > 2:
opttype, optname, optvalue = optname
else:
v = self.get(sec, option[1])
if not pOptions is None and option[1] in pOptions:
(opttype, optname), optvalue = optname, None
else:
opttype, optvalue = options[optname]
try:
if opttype == "bool":
v = self.getboolean(sec, optname)
elif opttype == "int":
v = self.getint(sec, optname)
else:
v = self.get(sec, optname)
if not pOptions is None and optname in pOptions:
continue
values[option[1]] = v
values[optname] = v
except NoSectionError, e:
# No "Definition" section or wrong basedir
logSys.error(e)
values[option[1]] = option[2]
values[optname] = optvalue
# TODO: validate error handling here.
except NoOptionError:
if not option[2] is None:
if not optvalue is None:
logSys.warning("'%s' not defined in '%s'. Using default one: %r"
% (option[1], sec, option[2]))
values[option[1]] = option[2]
% (optname, sec, optvalue))
values[optname] = optvalue
elif logSys.getEffectiveLevel() <= logLevel:
logSys.log(logLevel, "Non essential option '%s' not defined in '%s'.", option[1], sec)
logSys.log(logLevel, "Non essential option '%s' not defined in '%s'.", optname, sec)
except ValueError:
logSys.warning("Wrong value for '" + option[1] + "' in '" + sec +
"'. Using default one: '" + repr(option[2]) + "'")
values[option[1]] = option[2]
logSys.warning("Wrong value for '" + optname + "' in '" + sec +
"'. Using default one: '" + repr(optvalue) + "'")
values[optname] = optvalue
return values
@@ -286,7 +293,8 @@ class DefinitionInitConfigReader(ConfigReader):
if self.has_section("Init"):
for opt in self.options("Init"):
v = self.get("Init", opt)
self._initOpts['known/'+opt] = v
if not opt.startswith('known/') and opt != '__name__':
self._initOpts['known/'+opt] = v
if not opt in self._initOpts:
self._initOpts[opt] = v
+8 -1
View File
@@ -291,7 +291,14 @@ class Fail2banRegex(object):
RegexStat(m[3])
for m in filter(
lambda x: x[0] == 'set' and x[2] == "add%sregex" % regextype,
readercommands)]
readercommands)
] + [
RegexStat(m)
for mm in filter(
lambda x: x[0] == 'multi-set' and x[2] == "add%sregex" % regextype,
readercommands)
for m in mm[3]
]
# Read out and set possible value of maxlines
for command in readercommands:
if command[2] == "maxlines":
+11 -11
View File
@@ -37,10 +37,10 @@ logSys = getLogger(__name__)
class FilterReader(DefinitionInitConfigReader):
_configOpts = [
["string", "ignoreregex", None],
["string", "failregex", ""],
]
_configOpts = {
"ignoreregex": ["string", None],
"failregex": ["string", ""],
}
def setFile(self, fileName):
self.__file = fileName
@@ -64,16 +64,16 @@ class FilterReader(DefinitionInitConfigReader):
if not len(opts):
return stream
for opt, value in opts.iteritems():
if opt == "failregex":
if opt in ("failregex", "ignoreregex"):
multi = []
for regex in value.split('\n'):
# Do not send a command if the rule is empty.
if regex != '':
stream.append(["set", self._jailName, "addfailregex", regex])
elif opt == "ignoreregex":
for regex in value.split('\n'):
# Do not send a command if the rule is empty.
if regex != '':
stream.append(["set", self._jailName, "addignoreregex", regex])
multi.append(regex)
if len(multi) > 1:
stream.append(["multi-set", self._jailName, "add" + opt, multi])
elif len(multi):
stream.append(["set", self._jailName, "add" + opt, multi[0]])
if self._initOpts:
if 'maxlines' in self._initOpts:
# We warn when multiline regex is used without maxlines > 1
+18 -18
View File
@@ -190,11 +190,11 @@ class JailReader(ConfigReader):
"""
stream = []
for opt in self.__opts:
for opt, value in self.__opts.iteritems():
if opt == "logpath" and \
self.__opts.get('backend', None) != "systemd":
found_files = 0
for path in self.__opts[opt].split("\n"):
for path in value.split("\n"):
path = path.rsplit(" ", 1)
path, tail = path if len(path) > 1 else (path[0], "head")
pathList = JailReader._glob(path)
@@ -208,32 +208,32 @@ class JailReader(ConfigReader):
raise ValueError(
"Have not found any log file for %s jail" % self.__name)
elif opt == "logencoding":
stream.append(["set", self.__name, "logencoding", self.__opts[opt]])
stream.append(["set", self.__name, "logencoding", value])
elif opt == "backend":
backend = self.__opts[opt]
backend = value
elif opt == "maxretry":
stream.append(["set", self.__name, "maxretry", self.__opts[opt]])
stream.append(["set", self.__name, "maxretry", value])
elif opt == "ignoreip":
for ip in splitcommaspace(self.__opts[opt]):
for ip in splitcommaspace(value):
stream.append(["set", self.__name, "addignoreip", ip])
elif opt == "findtime":
stream.append(["set", self.__name, "findtime", self.__opts[opt]])
stream.append(["set", self.__name, "findtime", value])
elif opt == "bantime":
stream.append(["set", self.__name, "bantime", self.__opts[opt]])
stream.append(["set", self.__name, "bantime", value])
elif opt == "usedns":
stream.append(["set", self.__name, "usedns", self.__opts[opt]])
elif opt == "failregex":
for regex in self.__opts[opt].split('\n'):
stream.append(["set", self.__name, "usedns", value])
elif opt in ("failregex", "ignoreregex"):
multi = []
for regex in value.split('\n'):
# Do not send a command if the rule is empty.
if regex != '':
stream.append(["set", self.__name, "addfailregex", regex])
multi.append(regex)
if len(multi) > 1:
stream.append(["multi-set", self.__name, "add" + opt, multi])
elif len(multi):
stream.append(["set", self.__name, "add" + opt, multi[0]])
elif opt == "ignorecommand":
stream.append(["set", self.__name, "ignorecommand", self.__opts[opt]])
elif opt == "ignoreregex":
for regex in self.__opts[opt].split('\n'):
# Do not send a command if the rule is empty.
if regex != '':
stream.append(["set", self.__name, "addignoreregex", regex])
stream.append(["set", self.__name, "ignorecommand", value])
if self.__filter:
stream.extend(self.__filter.convert())
for action in self.__actions:
+297
View File
@@ -0,0 +1,297 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Author: Alexander Koeppe
#
__author__ = "Alexander Koeppe"
__copyright__ = "Copyright (c) 2016 Alexander Koeppe"
__license__ = "GPL"
import inspect
import socket
import struct
import re
from functools import wraps
##
# Helper functions / decorator
# Thanks to Yaroslav Halchenko (yarikoptic)
#
def asip(ip):
"""A little helper to guarantee ip being an IPAddr instance"""
return ip if isinstance(ip, IPAddr) or ip is None else IPAddr(ip)
def iparg(f):
"""A helper decorator to simplify use of asip throughout the code"""
args = inspect.getargspec(f).args
# I consider clarity better than trying to avoid any duplication here
# also better to make a decision at code parsing stage, not within the
# actual decorator function (i.e. checkip)
if args and args[0] == 'self':
# method -- just above simpler version
@wraps(f)
def checkip(self, ip=None, *argv, **kwargs):
return f(self, asip(ip), *argv, **kwargs)
else:
@wraps(f)
def checkip(ip=None, *argv, **kwargs):
return f(asip(ip), *argv, **kwargs)
return checkip
##
# Class for IP address handling.
#
# This class contains methods for handling IPv4 and IPv6 addresses.
class IPAddr:
"""Encapsulate functionality for IPv4 and IPv6 addresses
"""
IP_CRE = re.compile("^(?:\d{1,3}\.){3}\d{1,3}$")
IP6_CRE = re.compile("^[0-9a-fA-F]{4}[0-9a-fA-F:]+:[0-9a-fA-F]{1,4}|::1$")
# object attributes
_addr = 0
_family = socket.AF_UNSPEC
_plen = 0
_isValid = False
_raw = ""
# object methods
def __init__(self, ipstring, cidr=-1):
""" initialize IP object by converting IP address string
to binary to integer
"""
for family in [socket.AF_INET, socket.AF_INET6]:
try:
binary = socket.inet_pton(family, ipstring)
except socket.error:
continue
else:
self._isValid = True
break
if self.isValid and family == socket.AF_INET:
# convert host to network byte order
self._addr, = struct.unpack("!L", binary)
self._family = family
self._plen = 32
# mask out host portion if prefix length is supplied
if cidr is not None and cidr >= 0:
mask = ~(0xFFFFFFFFL >> cidr)
self._addr &= mask
self._plen = cidr
elif self.isValid and family == socket.AF_INET6:
# convert host to network byte order
hi, lo = struct.unpack("!QQ", binary)
self._addr = (hi << 64) | lo
self._family = family
self._plen = 128
# mask out host portion if prefix length is supplied
if cidr is not None and cidr >= 0:
mask = ~(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL >> cidr)
self._addr &= mask
self._plen = cidr
# if IPv6 address is a IPv4-compatible, make instance a IPv4
elif self.isInNet(_IPv6_v4COMPAT):
self._addr = lo & 0xFFFFFFFFL
self._family = socket.AF_INET
self._plen = 32
else:
# string couldn't be converted neither to a IPv4 nor
# to a IPv6 address - retain raw input for later use
# (e.g. DNS resolution)
self._raw = ipstring
def __repr__(self):
return self.ntoa
def __str__(self):
return self.ntoa
@property
def addr(self):
return self._addr
@property
def family(self):
return self._family
@property
def plen(self):
return self._plen
@property
def raw(self):
"""The raw address
Should only be set to a non-empty string if prior address
conversion wasn't possible
"""
return self._raw
@property
def isValid(self):
"""Either the object corresponds to a valid IP address
"""
return self._isValid
@iparg
def __eq__(self, other):
if not (self.isValid or other.isValid):
return self.raw == other.raw
return (
(self.isValid and other.isValid) and
(self.addr == other.addr) and
(self.family == other.family) and
(self.plen == other.plen)
)
@iparg
def __ne__(self, other):
return not (self == other)
@iparg
def __lt__(self, other):
return self.family < other.family or self.addr < other.addr
@iparg
def __add__(self, other):
return "%s%s" % (self, other)
@iparg
def __radd__(self, other):
return "%s%s" % (other, self)
def __hash__(self):
return hash(self.addr) ^ hash((self.plen << 16) | self.family)
@property
def hexdump(self):
"""Hex representation of the IP address (for debug purposes)
"""
if self.family == socket.AF_INET:
return "%08x" % self.addr
elif self.family == socket.AF_INET6:
return "%032x" % self.addr
else:
return ""
# TODO: could be lazily evaluated
@property
def ntoa(self):
""" represent IP object as text like the deprecated
C pendant inet.ntoa but address family independent
"""
if self.isIPv4:
# convert network to host byte order
binary = struct.pack("!L", self._addr)
elif self.isIPv6:
# convert network to host byte order
hi = self.addr >> 64
lo = self.addr & 0xFFFFFFFFFFFFFFFFL
binary = struct.pack("!QQ", hi, lo)
else:
return self.raw
return socket.inet_ntop(self.family, binary)
def getPTR(self, suffix=""):
""" return the DNS PTR string of the provided IP address object
If "suffix" is provided it will be appended as the second and top
level reverse domain.
If omitted it is implicitly set to the second and top level reverse
domain of the according IP address family
"""
if self.isIPv4:
exploded_ip = self.ntoa.split(".")
if not suffix:
suffix = "in-addr.arpa."
elif self.isIPv6:
exploded_ip = self.hexdump()
if not suffix:
suffix = "ip6.arpa."
else:
return ""
return "%s.%s" % (".".join(reversed(exploded_ip)), suffix)
@property
def isIPv4(self):
"""Either the IP object is of address family AF_INET
"""
return self.family == socket.AF_INET
@property
def isIPv6(self):
"""Either the IP object is of address family AF_INET6
"""
return self.family == socket.AF_INET6
@iparg
def isInNet(self, net):
"""Return either the IP object is in the provided network
"""
if self.family != net.family:
return False
if self.isIPv4:
mask = ~(0xFFFFFFFFL >> net.plen)
elif self.isIPv6:
mask = ~(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL >> net.plen)
else:
return False
return self.addr & mask == net.addr
@staticmethod
@iparg
def masktoplen(mask):
"""Convert mask string to prefix length
To be used only for IPv4 masks
"""
mask = mask.addr # to avoid side-effect within original mask
plen = 0
while mask:
mask = (mask << 1) & 0xFFFFFFFFL
plen += 1
return plen
@staticmethod
def searchIP(text):
"""Search if text is an IP address, and return it if so, else None
"""
match = IPAddr.IP_CRE.match(text)
if not match:
match = IPAddr.IP6_CRE.match(text)
return match if match else None
# An IPv4 compatible IPv6 to be reused
_IPv6_v4COMPAT = IPAddr("::ffff:0:0", 96)
+99 -5
View File
@@ -203,9 +203,13 @@ class CommandAction(ActionBase):
Attributes
----------
actionban
actionban6
actionstart
actionstart6
actionstop
actionstop6
actionunban
actionunban6
timeout
"""
@@ -216,14 +220,24 @@ class CommandAction(ActionBase):
self.timeout = 60
## Command executed in order to initialize the system.
self.actionstart = ''
## Command executed in order to initialize the system.
self.actionstart6 = ''
## Command executed when an IP address gets banned.
self.actionban = ''
## Command executed when an IPv6 address gets banned.
self.actionban6 = ''
## Command executed when an IP address gets removed.
self.actionunban = ''
## Command executed when an IPv6 address gets removed.
self.actionunban6 = ''
## Command executed in order to check requirements.
self.actioncheck = ''
## Command executed in order to check requirements.
self.actioncheck6 = ''
## Command executed in order to stop the system.
self.actionstop = ''
## Command executed in order to stop the system.
self.actionstop6 = ''
self._logSys.debug("Created %s" % self.__class__)
@classmethod
@@ -264,6 +278,17 @@ class CommandAction(ActionBase):
self._actionstart = value
self._logSys.debug("Set actionstart = %s" % value)
@property
def actionstart6(self):
"""The command executed on start of the jail/action.
"""
return self._actionstart6
@actionstart6.setter
def actionstart6(self, value):
self._actionstart6 = value
self._logSys.debug("Set actionstart6 = %s" % value)
def start(self):
"""Executes the "actionstart" command.
@@ -279,10 +304,16 @@ class CommandAction(ActionBase):
startCmd = self.replaceTag(self.actionstart, self._properties)
if not self.executeCmd(startCmd, self.timeout):
raise RuntimeError("Error starting action")
# Start actionstart6 if available
if self.actionstart6:
startCmd = self.replaceTag(self.actionstart6, self._properties)
if not self.executeCmd(startCmd, self.timeout):
raise RuntimeError("Error starting action")
@property
def actionban(self):
"""The command used when a ban occurs.
"""The command used when a ban on IPv4 address occurs.
"""
return self._actionban
@@ -291,6 +322,17 @@ class CommandAction(ActionBase):
self._actionban = value
self._logSys.debug("Set actionban = %s" % value)
@property
def actionban6(self):
"""The command used when a ban on IPv6 address occurs.
"""
return self._actionban6
@actionban6.setter
def actionban6(self, value):
self._actionban6 = value
self._logSys.debug("Set actionban6 = %s" % value)
def ban(self, aInfo):
"""Executes the "actionban" command.
@@ -303,7 +345,10 @@ class CommandAction(ActionBase):
Dictionary which includes information in relation to
the ban.
"""
if not self._processCmd(self.actionban, aInfo):
banaction = self.actionban
if "ip" in aInfo and aInfo["ip"] and aInfo["ip"].isIPv6 and self.actionban6:
banaction = self.actionban6
if not self._processCmd(banaction, aInfo):
raise RuntimeError("Error banning %(ip)s" % aInfo)
@property
@@ -317,6 +362,17 @@ class CommandAction(ActionBase):
self._actionunban = value
self._logSys.debug("Set actionunban = %s" % value)
@property
def actionunban6(self):
"""The command used when an unban occurs.
"""
return self._actionunban6
@actionunban6.setter
def actionunban6(self, value):
self._actionunban6 = value
self._logSys.debug("Set actionunban6 = %s" % value)
def unban(self, aInfo):
"""Executes the "actionunban" command.
@@ -329,7 +385,11 @@ class CommandAction(ActionBase):
Dictionary which includes information in relation to
the ban.
"""
if not self._processCmd(self.actionunban, aInfo):
unbanaction = self.actionunban
if "ip" in aInfo and aInfo["ip"] and aInfo["ip"].isIPv6 and self.actionunban6:
unbanaction = self.actionunban6
if not self._processCmd(unbanaction, aInfo):
raise RuntimeError("Error unbanning %(ip)s" % aInfo)
@property
@@ -347,6 +407,21 @@ class CommandAction(ActionBase):
self._actioncheck = value
self._logSys.debug("Set actioncheck = %s" % value)
@property
def actioncheck6(self):
"""The command used to check the environment.
This is used prior to a ban taking place to ensure the
environment is appropriate. If this check fails, `stop` and
`start` is executed prior to the check being called again.
"""
return self._actioncheck6
@actioncheck6.setter
def actioncheck6(self, value):
self._actioncheck6 = value
self._logSys.debug("Set actioncheck6 = %s" % value)
@property
def actionstop(self):
"""The command executed when the jail/actions stops.
@@ -358,8 +433,19 @@ class CommandAction(ActionBase):
self._actionstop = value
self._logSys.debug("Set actionstop = %s" % value)
@property
def actionstop6(self):
"""The command executed when the jail/actions stops.
"""
return self._actionstop6
@actionstop6.setter
def actionstop6(self, value):
self._actionstop6 = value
self._logSys.debug("Set actionstop6 = %s" % value)
def stop(self):
"""Executes the "actionstop" command.
"""Executes the "actionstop6" command.
Replaces the tags in the action command with actions properties
and executes the resulting command.
@@ -367,6 +453,11 @@ class CommandAction(ActionBase):
stopCmd = self.replaceTag(self.actionstop, self._properties)
if not self.executeCmd(stopCmd, self.timeout):
raise RuntimeError("Error stopping action")
# Start actionstop6 if available
if self.actionstop6:
stopCmd = self.replaceTag(self.actionstop6, self._properties)
if not self.executeCmd(stopCmd, self.timeout):
raise RuntimeError("Error stopping action")
@classmethod
def substituteRecursiveTags(cls, tags):
@@ -510,7 +601,10 @@ class CommandAction(ActionBase):
self._logSys.debug("Nothing to do")
return True
checkCmd = self.replaceTag(self.actioncheck, self._properties)
actioncheck = self.actioncheck
if "ip" in aInfo and aInfo["ip"] and aInfo["ip"].isIPv6 and self.actioncheck6:
actioncheck = self.actioncheck6
checkCmd = self.replaceTag(actioncheck, self._properties)
if not self.executeCmd(checkCmd, self.timeout):
self._logSys.error(
"Invariant check failed. Trying to restore a sane environment")
+2
View File
@@ -43,6 +43,7 @@ from .jailthread import JailThread
from .action import ActionBase, CommandAction, CallingMap
from .mytime import MyTime
from ..helpers import getLogger
from ..ipaddr import iparg
# Gets the instance of the logger.
logSys = getLogger(__name__)
@@ -178,6 +179,7 @@ class Actions(JailThread, Mapping):
def getBanTime(self):
return self.__banManager.getBanTime()
@iparg
def removeBannedIP(self, ip):
"""Removes banned IP calling actions' unban method
+4 -3
View File
@@ -152,9 +152,10 @@ class BanManager:
for banData in self.__banList:
ip = banData.getIP()
# Reference: http://www.team-cymru.org/Services/ip-to-asn.html#dns
# TODO: IPv6 compatibility
reversed_ip = ".".join(reversed(ip.split(".")))
question = "%s.origin.asn.cymru.com" % reversed_ip
question = ip.getPTR(
"origin.asn.cymru.com" if ip.isIPv4
else "origin6.asn.cymru.com"
)
try:
answers = dns.resolver.query(question, "TXT")
for rdata in answers:
+11 -6
View File
@@ -32,6 +32,7 @@ from threading import RLock
from .mytime import MyTime
from .ticket import FailTicket
from ..ipaddr import IPAddr, iparg
from ..helpers import getLogger
# Gets the instance of the logger.
@@ -413,7 +414,7 @@ class Fail2BanDb(object):
#TODO: Implement data parts once arbitrary match keys completed
cur.execute(
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
(jail.name, ticket.getIP(), int(round(ticket.getTime())),
(jail.name, ticket.getIP().ntoa, int(round(ticket.getTime())),
{"matches": ticket.getMatches(),
"failures": ticket.getAttempt()}))
@@ -428,7 +429,7 @@ class Fail2BanDb(object):
ip : str
IP to be removed.
"""
queryArgs = (jail.name, ip);
queryArgs = (jail.name, ip.ntoa);
cur.execute(
"DELETE FROM bans WHERE jail = ? AND ip = ?",
queryArgs);
@@ -446,7 +447,7 @@ class Fail2BanDb(object):
queryArgs.append(MyTime.time() - bantime)
if ip is not None:
query += " AND ip=?"
queryArgs.append(ip)
queryArgs.append(ip.ntoa)
query += " ORDER BY ip, timeofban"
return cur.execute(query, queryArgs)
@@ -462,7 +463,7 @@ class Fail2BanDb(object):
Ban time in seconds, such that bans returned would still be
valid now. Negative values are equivalent to `None`.
Default `None`; no limit.
ip : str
ip : IPAddr object
IP Address to filter bans by. Default `None`; all IPs.
Returns
@@ -471,12 +472,14 @@ class Fail2BanDb(object):
List of `Ticket`s for bans stored in database.
"""
tickets = []
for ip, timeofban, data in self._getBans(**kwargs):
for ipstr, timeofban, data in self._getBans(**kwargs):
ip = IPAddr(ipstr)
#TODO: Implement data parts once arbitrary match keys completed
tickets.append(FailTicket(ip, timeofban, data.get('matches')))
tickets[-1].setAttempt(data.get('failures', 1))
return tickets
@iparg
def getBansMerged(self, ip=None, jail=None, bantime=None):
"""Get bans from the database, merged into single ticket.
@@ -491,7 +494,7 @@ class Fail2BanDb(object):
Ban time in seconds, such that bans returned would still be
valid now. Negative values are equivalent to `None`.
Default `None`; no limit.
ip : str
ip : IPAddr object
IP Address to filter bans by. Default `None`; all IPs.
Returns
@@ -512,6 +515,8 @@ class Fail2BanDb(object):
ticket = None
results = list(self._getBans(ip=ip, jail=jail, bantime=bantime))
# Convert IP strings to IPAddr objects
results = map(lambda i:(IPAddr(i[0]),)+i[1:], results)
if results:
prev_banip = results[0][0]
matches = []
+2
View File
@@ -30,6 +30,7 @@ import logging
from .faildata import FailData
from .ticket import FailTicket
from ..helpers import getLogger
from ..ipaddr import iparg
# Gets the instance of the logger.
logSys = getLogger(__name__)
@@ -136,6 +137,7 @@ class FailManager:
finally:
self.__lock.release()
@iparg
def __delFailure(self, ip):
if ip in self.__failList:
del self.__failList[ip]
+61 -81
View File
@@ -28,6 +28,7 @@ import os
import re
import sys
from .failmanager import FailManagerEmpty, FailManager
from .ticket import FailTicket
from .jailthread import JailThread
@@ -37,6 +38,7 @@ from .mytime import MyTime
from .failregex import FailRegex, Regex, RegexException
from .action import CommandAction
from ..helpers import getLogger
from ..ipaddr import IPAddr, iparg
# Gets the instance of the logger.
logSys = getLogger(__name__)
@@ -303,12 +305,19 @@ class Filter(JailThread):
def getIgnoreCommand(self):
return self.__ignoreCommand
##
# create new IPAddr object from IP address string
@iparg
def newIP(self, ip):
return ip
##
# Ban an IP - http://blogs.buanzo.com.ar/2009/04/fail2ban-patch-ban-ip-address-manually.html
# Arturo 'Buanzo' Busleiman <buanzo@buanzo.com.ar>
#
# to enable banip fail2ban-client BAN command
@iparg
def addBannedIP(self, ip):
if self.inIgnoreIPList(ip):
logSys.warning('Requested to manually ban an ignored IP %s. User knows best. Proceeding to ban it.' % ip)
@@ -334,14 +343,31 @@ class Filter(JailThread):
# when finding failures. CIDR mask and DNS are also accepted.
# @param ip IP address to ignore
def addIgnoreIP(self, ip):
def addIgnoreIP(self, ipstr):
# An empty string is always false
if ipstr == "":
return
s = ipstr.split('/', 1)
# IP address without CIDR mask
if len(s) == 1:
s.insert(1, -1) # <0 means no CIDR
elif "." in s[1]: # 255.255.255.0 style mask
s[1] = IPAddr.masktoplen(s[1])
s[1] = long(s[1])
# Create IP address object
ip = IPAddr(s[0], s[1])
# log and append to ignore list
logSys.debug("Add " + ip + " to ignore list")
self.__ignoreIpList.append(ip)
@iparg
def delIgnoreIP(self, ip):
logSys.debug("Remove " + ip + " from ignore list")
self.__ignoreIpList.remove(ip)
@iparg
def logIgnoreIp(self, ip, log_ignore, ignore_source="unknown source"):
if log_ignore:
logSys.info("[%s] Ignore %s by %s" % (self.jail.name, ip, ignore_source))
@@ -354,34 +380,23 @@ class Filter(JailThread):
#
# Check if the given IP address matches an IP address/DNS or a CIDR
# mask in the ignore list.
# @param ip IP address
# @param ip IP address object
# @return True if IP address is in ignore list
@iparg
def inIgnoreIPList(self, ip, log_ignore=False):
for i in self.__ignoreIpList:
# An empty string is always false
if i == "":
continue
s = i.split('/', 1)
# IP address without CIDR mask
if len(s) == 1:
s.insert(1, '32')
elif "." in s[1]: # 255.255.255.0 style mask
s[1] = len(re.search(
"(?<=b)1+", bin(DNSUtils.addr2bin(s[1]))).group())
s[1] = long(s[1])
try:
a = DNSUtils.addr2bin(s[0], cidr=s[1])
b = DNSUtils.addr2bin(ip, cidr=s[1])
except Exception:
for net in self.__ignoreIpList:
# if it isn't a valid IP address, try DNS resolution
if not net.isValid and net.raw != "":
# Check if IP in DNS
ips = DNSUtils.dnsToIp(i)
ips = DNSUtils.dnsToIp(net.raw)
if ip in ips:
self.logIgnoreIp(ip, log_ignore, ignore_source="dns")
return True
else:
continue
if a == b:
# check if the IP is covered by ignore IP
if ip.isInNet(net):
self.logIgnoreIp(ip, log_ignore, ignore_source="ip")
return True
@@ -389,6 +404,7 @@ class Filter(JailThread):
command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } )
logSys.debug('ignore command: ' + command)
ret_ignore = CommandAction.executeCmd(command)
self.logIgnoreIp(ip, log_ignore and ret_ignore, ignore_source="command")
return ret_ignore
@@ -524,16 +540,17 @@ class Filter(JailThread):
try:
host = failRegex.getHost()
if returnRawHost:
failList.append([failRegexIndex, host, date,
ip = IPAddr(host)
failList.append([failRegexIndex, ip, date,
failRegex.getMatchedLines()])
if not checkAllRegex:
break
else:
ipMatch = DNSUtils.textToIp(host, self.__useDns)
if ipMatch:
for ip in ipMatch:
failList.append([failRegexIndex, ip, date,
failRegex.getMatchedLines()])
ips = DNSUtils.textToIp(host, self.__useDns)
if ips:
for ip in ips:
failList.append([failRegexIndex, ip,
date, failRegex.getMatchedLines()])
if not checkAllRegex:
break
except RegexException, e: # pragma: no cover - unsure if reachable
@@ -837,10 +854,10 @@ class JournalFilter(Filter): # pragma: systemd no cover
return []
##
# Utils class for DNS and IP handling.
# Utils class for DNS handling.
#
# This class contains only static methods used to handle DNS
#
# This class contains only static methods used to handle DNS and IP
# addresses.
import socket
import struct
@@ -848,66 +865,45 @@ import struct
class DNSUtils:
IP_CRE = re.compile("^(?:\d{1,3}\.){3}\d{1,3}$")
@staticmethod
def dnsToIp(dns):
""" Convert a DNS into an IP address using the Python socket module.
Thanks to Kevin Drapel.
"""
# retrieve ip (todo: use AF_INET6 for IPv6)
try:
return set([i[4][0] for i in socket.getaddrinfo(dns, None, socket.AF_INET, 0, socket.IPPROTO_TCP)])
ips = list()
for result in socket.getaddrinfo(dns, None, 0, 0,
socket.IPPROTO_TCP):
ip = IPAddr(result[4][0])
if ip.isValid:
ips.append(ip)
return ips
except socket.error, e:
logSys.warning("Unable to find a corresponding IP address for %s: %s"
% (dns, e))
return list()
except socket.error, e:
logSys.warning("Socket error raised trying to resolve hostname %s: %s"
% (dns, e))
return list()
@staticmethod
@iparg
def ipToName(ip):
try:
return socket.gethostbyaddr(ip)[0]
return socket.gethostbyaddr(ip.ntoa)[0]
except socket.error, e:
logSys.debug("Unable to find a name for the IP %s: %s" % (ip, e))
return None
@staticmethod
def searchIP(text):
""" Search if an IP address if directly available and return
it.
"""
match = DNSUtils.IP_CRE.match(text)
if match:
return match
else:
return None
@staticmethod
def isValidIP(string):
""" Return true if str is a valid IP
"""
s = string.split('/', 1)
try:
socket.inet_aton(s[0])
return True
except socket.error:
return False
@staticmethod
def textToIp(text, useDns):
""" Return the IP of DNS found in a given text.
"""
ipList = list()
# Search for plain IP
plainIP = DNSUtils.searchIP(text)
if not plainIP is None:
plainIPStr = plainIP.group(0)
if DNSUtils.isValidIP(plainIPStr):
ipList.append(plainIPStr)
plainIP = IPAddr.searchIP(text)
if plainIP is not None:
ip = IPAddr(plainIP.group(0))
if ip.isValid:
ipList.append(ip)
# If we are allowed to resolve -- give it a try if nothing was found
if useDns in ("yes", "warn") and not ipList:
@@ -920,19 +916,3 @@ class DNSUtils:
return ipList
@staticmethod
def addr2bin(ipstring, cidr=None):
""" Convert a string IPv4 address into binary form.
If cidr is supplied, return the network address for the given block
"""
if cidr is None:
return struct.unpack("!L", socket.inet_aton(ipstring))[0]
else:
MASK = 0xFFFFFFFFL
return ~(MASK >> cidr) & MASK & DNSUtils.addr2bin(ipstring)
@staticmethod
def bin2addr(ipbin):
""" Convert a binary IPv4 address into string n.n.n.n form.
"""
return socket.inet_ntoa(struct.pack("!L", ipbin))
+14 -4
View File
@@ -263,8 +263,13 @@ class Server:
def getIgnoreCommand(self, name):
return self.__jails[name].filter.getIgnoreCommand()
def addFailRegex(self, name, value):
self.__jails[name].filter.addFailRegex(value)
def addFailRegex(self, name, value, multiple=False):
flt = self.__jails[name].filter
if multiple:
for value in value:
flt.addFailRegex(value)
else:
flt.addFailRegex(value)
def delFailRegex(self, name, index):
self.__jails[name].filter.delFailRegex(index)
@@ -272,8 +277,13 @@ class Server:
def getFailRegex(self, name):
return self.__jails[name].filter.getFailRegex()
def addIgnoreRegex(self, name, value):
self.__jails[name].filter.addIgnoreRegex(value)
def addIgnoreRegex(self, name, value, multiple=False):
flt = self.__jails[name].filter
if multiple:
for value in value:
flt.addIgnoreRegex(value)
else:
flt.addIgnoreRegex(value)
def delIgnoreRegex(self, name, index):
self.__jails[name].filter.delIgnoreRegex(index)
+5 -4
View File
@@ -25,6 +25,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from ..helpers import getLogger
from ..ipaddr import iparg
# Gets the instance of the logger.
logSys = getLogger(__name__)
@@ -32,6 +33,7 @@ logSys = getLogger(__name__)
class Ticket:
@iparg
def __init__(self, ip, time, matches=None):
"""Ticket constructor
@@ -48,7 +50,9 @@ class Ticket:
def __str__(self):
return "%s: ip=%s time=%s #attempts=%d matches=%r" % \
(self.__class__.__name__.split('.')[-1], self.__ip, self.__time, self.__attempt, self.__matches)
(self.__class__.__name__.split('.')[-1],
self.__ip.ntoa, self.__time, self.__attempt,
self.__matches)
def __repr__(self):
return str(self)
@@ -63,9 +67,6 @@ class Ticket:
return False
def setIP(self, value):
if isinstance(value, basestring):
# guarantee using regular str instead of unicode for the IP
value = str(value)
self.__ip = value
def getIP(self):
+27 -10
View File
@@ -99,6 +99,8 @@ class Transmitter:
return None
elif command[0] == "flushlogs":
return self.__server.flushLogs()
elif command[0] == "multi-set":
return self.__commandSet(command[1:], True)
elif command[0] == "set":
return self.__commandSet(command[1:])
elif command[0] == "get":
@@ -109,7 +111,7 @@ class Transmitter:
return version.version
raise Exception("Invalid command")
def __commandSet(self, command):
def __commandSet(self, command, multiple=False):
name = command[0]
# Logging
if name == "loglevel":
@@ -196,7 +198,9 @@ class Transmitter:
return self.__server.getJournalMatch(name)
elif command[1] == "addfailregex":
value = command[2]
self.__server.addFailRegex(name, value)
self.__server.addFailRegex(name, value, multiple=multiple)
if multiple:
return True
return self.__server.getFailRegex(name)
elif command[1] == "delfailregex":
value = int(command[2])
@@ -204,7 +208,9 @@ class Transmitter:
return self.__server.getFailRegex(name)
elif command[1] == "addignoreregex":
value = command[2]
self.__server.addIgnoreRegex(name, value)
self.__server.addIgnoreRegex(name, value, multiple=multiple)
if multiple:
return True
return self.__server.getIgnoreRegex(name)
elif command[1] == "delignoreregex":
value = int(command[2])
@@ -254,15 +260,26 @@ class Transmitter:
return None
elif command[1] == "action":
actionname = command[2]
actionkey = command[3]
action = self.__server.getAction(name, actionname)
if callable(getattr(action, actionkey, None)):
actionvalue = json.loads(command[4]) if len(command)>4 else {}
return getattr(action, actionkey)(**actionvalue)
if multiple:
for cmd in command[3]:
actionkey = cmd[0]
if callable(getattr(action, actionkey, None)):
actionvalue = json.loads(cmd[1]) if len(cmd)>1 else {}
getattr(action, actionkey)(**actionvalue)
else:
actionvalue = cmd[1]
setattr(action, actionkey, actionvalue)
return True
else:
actionvalue = command[4]
setattr(action, actionkey, actionvalue)
return getattr(action, actionkey)
actionkey = command[3]
if callable(getattr(action, actionkey, None)):
actionvalue = json.loads(command[4]) if len(command)>4 else {}
return getattr(action, actionkey)(**actionvalue)
else:
actionvalue = command[4]
setattr(action, actionkey, actionvalue)
return getattr(action, actionkey)
raise Exception("Invalid command (no set action or not yet implemented)")
def __commandGet(self, command):
+2
View File
@@ -29,6 +29,8 @@ import unittest
from ..server.banmanager import BanManager
from ..server.ticket import BanTicket
from .utils import assert_dict_equal
from ..ipaddr import IPAddr
class AddFailure(unittest.TestCase):
def setUp(self):
+109
View File
@@ -0,0 +1,109 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
__author__ = "Alexander Koeppe"
__copyright__ = "Copyright (c) 2016 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
__license__ = "GPL"
import unittest
from ..client.beautifier import Beautifier
from ..version import version
from ..ipaddr import IPAddr
class BeautifierTest(unittest.TestCase):
def setUp(self):
""" Call before every test case """
self.b = Beautifier()
def tearDown(self):
""" Call after every test case """
def testGetInputCmd(self):
cmd = ["test"]
self.b.setInputCmd(cmd)
self.assertEqual(self.b.getInputCmd(), cmd)
def testPing(self):
self.b.setInputCmd(["ping"])
self.assertEqual(self.b.beautify("pong"), "Server replied: pong")
def testVersion(self):
self.b.setInputCmd(["version"])
self.assertEqual(self.b.beautify(version), version)
def testAddJail(self):
self.b.setInputCmd(["add"])
self.assertEqual(self.b.beautify("ssh"), "Added jail ssh")
def testStartJail(self):
self.b.setInputCmd(["start"])
self.assertEqual(self.b.beautify(None), "Jail started")
def testFlushLogs(self):
self.b.setInputCmd(["flushlogs"])
self.assertEqual(self.b.beautify("rolled over"), "logs: rolled over")
def testStopJail(self):
self.b.setInputCmd(["stop", "ssh"])
self.assertEqual(self.b.beautify(None), "Jail stopped")
def testShutdown(self):
self.b.setInputCmd(["stop"])
self.assertEqual(self.b.beautify(None), "Shutdown successful")
def testStatus(self):
self.b.setInputCmd(["status"])
response = (("Number of jails", 0), ("Jail list", ["ssh", "exim4"]))
output = "Status\n|- Number of jails:\t0\n`- Jail list:\tssh exim4"
self.assertEqual(self.b.beautify(response), output)
self.b.setInputCmd(["status", "ssh"])
response = (
("Filter", [
("Currently failed", 0),
("Total failed", 0),
("File list", "/var/log/auth.log")
]
),
("Actions", [
("Currently banned", 3),
("Total banned", 3),
("Banned IP list", [
IPAddr("192.168.0.1"),
IPAddr("::ffff:10.2.2.1"),
IPAddr("2001:db8::1")
]
)
]
)
)
output = """Status for the jail: ssh
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 3
|- Total banned: 3
`- Banned IP list: 192.168.0.1 10.2.2.1 2001:db8::1"""
self.assertEqual(self.b.beautify(response), output)
+27 -15
View File
@@ -273,7 +273,15 @@ class JailReaderTest(LogCaptureTestCase):
# convert and get stream
stream = jail.convert()
# get action and retrieve agent from it, compare with agent saved in version:
act = [o for o in stream if len(o) > 4 and (o[4] == 'agent' or o[4].endswith('badips.py'))]
act = []
for cmd in stream:
if len(cmd) <= 4:
continue
# differentiate between set and multi-set (wrop it here to single set):
if cmd[0] == 'set' and (cmd[4] == 'agent' or cmd[4].endswith('badips.py')):
act.append(cmd)
elif cmd[0] == 'multi-set':
act.extend([['set'] + cmd[1:4] + o for o in cmd[4] if o[0] == 'agent'])
useragent = 'Fail2Ban/%s' % version
self.assertEqual(len(act), 4)
self.assertEqual(act[0], ['set', 'blocklisttest', 'action', 'blocklist_de', 'agent', useragent])
@@ -309,23 +317,21 @@ class FilterReaderTest(unittest.TestCase):
self.__share_cfg = {}
def testConvert(self):
output = [['set', 'testcase01', 'addfailregex',
output = [['multi-set', 'testcase01', 'addfailregex', [
"^\\s*(?:\\S+ )?(?:kernel: \\[\\d+\\.\\d+\\] )?(?:@vserver_\\S+ )"
"?(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|"
"[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:)?\\s*(?:"
"error: PAM: )?Authentication failure for .* from <HOST>\\s*$"],
['set', 'testcase01', 'addfailregex',
"error: PAM: )?Authentication failure for .* from <HOST>\\s*$",
"^\\s*(?:\\S+ )?(?:kernel: \\[\\d+\\.\\d+\\] )?(?:@vserver_\\S+ )"
"?(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|"
"[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:)?\\s*(?:"
"error: PAM: )?User not known to the underlying authentication mo"
"dule for .* from <HOST>\\s*$"],
['set', 'testcase01', 'addfailregex',
"dule for .* from <HOST>\\s*$",
"^\\s*(?:\\S+ )?(?:kernel: \\[\\d+\\.\\d+\\] )?(?:@vserver_\\S+ )"
"?(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|"
"[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:)?\\s*(?:"
"error: PAM: )?User not known to the\\nunderlying authentication."
"+$<SKIPLINES>^.+ module for .* from <HOST>\\s*$"],
"+$<SKIPLINES>^.+ module for .* from <HOST>\\s*$"]],
['set', 'testcase01', 'addignoreregex',
"^.+ john from host 192.168.1.1\\s*$"],
['set', 'testcase01', 'addjournalmatch',
@@ -493,9 +499,11 @@ class JailsReaderTest(LogCaptureTestCase):
self.assertEqual(sorted(comm_commands),
sorted([['add', 'emptyaction', 'auto'],
['add', 'test-known-interp', 'auto'],
['set', 'test-known-interp', 'addfailregex', 'failure test 1 (filter.d/test.conf) <HOST>'],
['set', 'test-known-interp', 'addfailregex', 'failure test 2 (filter.d/test.local) <HOST>'],
['set', 'test-known-interp', 'addfailregex', 'failure test 3 (jail.local) <HOST>'],
['multi-set', 'test-known-interp', 'addfailregex', [
'failure test 1 (filter.d/test.conf) <HOST>',
'failure test 2 (filter.d/test.local) <HOST>',
'failure test 3 (jail.local) <HOST>'
]],
['start', 'test-known-interp'],
['add', 'missinglogfiles', 'auto'],
['set', 'missinglogfiles', 'addfailregex', '<IP>'],
@@ -658,12 +666,16 @@ class JailsReaderTest(LogCaptureTestCase):
self.assertTrue('blocktype' in action._initOpts)
# Verify that we have a call to set it up
blocktype_present = False
target_command = ['set', jail_name, 'action', action_name, 'blocktype']
target_command = [jail_name, 'action', action_name]
for command in commands:
if (len(command) > 5 and
command[:5] == target_command):
blocktype_present = True
continue
if (len(command) > 4 and command[0] == 'multi-set' and
command[1:4] == target_command):
blocktype_present = ('blocktype' in [cmd[0] for cmd in command[4]])
elif (len(command) > 5 and command[0] == 'set' and
command[1:4] == target_command and command[4] == 'blocktype'): # pragma: no cover - because of multi-set
blocktype_present = True
if blocktype_present:
break
self.assertTrue(
blocktype_present,
msg="Found no %s command among %s"
+2 -1
View File
@@ -28,6 +28,7 @@ import unittest
from ..server.failmanager import FailManager, FailManagerEmpty
from ..server.ticket import FailTicket
from ..server.filter import IPAddr
class AddFailure(unittest.TestCase):
@@ -89,7 +90,7 @@ class AddFailure(unittest.TestCase):
#ticket = FailTicket('193.168.0.128', None)
ticket = self.__failManager.toBan()
self.assertEqual(ticket.getIP(), "193.168.0.128")
self.assertTrue(isinstance(ticket.getIP(), str))
self.assertTrue(isinstance(ticket.getIP(), IPAddr))
# finish with rudimentary tests of the ticket
# verify consistent str
+2 -1
View File
@@ -1,2 +1,3 @@
Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2
Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.34 port 51332 ssh2
Aug 14 11:56:59 i60p295 sshd[12365]: Failed publickey for roehl from 172.31.0.34 port 51332 ssh2
Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:172.31.0.34 port 51332 ssh2
+32 -24
View File
@@ -41,6 +41,7 @@ from ..server.filterpoll import FilterPoll
from ..server.filter import Filter, FileFilter, DNSUtils
from ..server.failmanager import FailManagerEmpty
from ..server.mytime import MyTime
from ..ipaddr import IPAddr
from .utils import setUpMyTime, tearDownMyTime, mtimesleep, LogCaptureTestCase
from .dummyjail import DummyJail
@@ -85,8 +86,14 @@ def _assert_equal_entries(utest, found, output, count=None):
and report helpful failure reports instead of millions of seconds ;)
"""
utest.assertEqual(found[0], output[0]) # IP
utest.assertEqual(found[1], count or output[1]) # count
# if DNS is involved, multiple addresses may be returned
if isinstance(output[0], list):
utest.assertIn(found[0], output[0]) # IP
utest.assertEqual(found[1], count or output[1]) # count
else:
utest.assertEqual(found[0], output[0]) # IP
utest.assertEqual(found[1], count or output[1]) # count
found_time, output_time = \
MyTime.localtime(found[2]),\
MyTime.localtime(output[2])
@@ -963,12 +970,12 @@ class GetFailures(LogCaptureTestCase):
def testGetFailuresUseDNS(self):
# We should still catch failures with usedns = no ;-)
output_yes = ('93.184.216.34', 2, 1124013539.0,
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2',
u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.34 port 51332 ssh2'])
output_yes = (['93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946'], 1, 1124013299.0,
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2'])
output_no = ('93.184.216.34', 1, 1124013539.0,
[u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.34 port 51332 ssh2'])
output_no = ('172.31.0.34', 2, 1124013539.0,
[u'Aug 14 11:56:59 i60p295 sshd[12365]: Failed publickey for roehl from 172.31.0.34 port 51332 ssh2',
u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:172.31.0.34 port 51332 ssh2'])
# Actually no exception would be raised -- it will be just set to 'no'
#self.assertRaises(ValueError,
@@ -1067,9 +1074,11 @@ class DNSUtilsTests(unittest.TestCase):
res = DNSUtils.textToIp('www.example.com', 'no')
self.assertEqual(res, [])
res = DNSUtils.textToIp('www.example.com', 'warn')
self.assertEqual(res, ['93.184.216.34'])
self.assertIn('93.184.216.34', res)
self.assertIn('2606:2800:220:1:248:1893:25c8:1946', res)
res = DNSUtils.textToIp('www.example.com', 'yes')
self.assertEqual(res, ['93.184.216.34'])
self.assertIn('93.184.216.34', res)
self.assertIn('2606:2800:220:1:248:1893:25c8:1946', res)
def testTextToIp(self):
# Test hostnames
@@ -1081,32 +1090,31 @@ class DNSUtilsTests(unittest.TestCase):
for s in hostnames:
res = DNSUtils.textToIp(s, 'yes')
if s == 'www.example.com':
self.assertEqual(res, ['93.184.216.34'])
self.assertIn('93.184.216.34', res)
self.assertIn('2606:2800:220:1:248:1893:25c8:1946', res)
else:
self.assertEqual(res, [])
def testIpToName(self):
res = DNSUtils.ipToName('8.8.4.4')
self.assertEqual(res, 'google-public-dns-b.google.com')
res = DNSUtils.ipToName('2001:4860:4860::8844')
self.assertEqual(res, 'google-public-dns-b.google.com')
# invalid ip (TEST-NET-1 according to RFC 5737)
res = DNSUtils.ipToName('192.0.2.0')
self.assertEqual(res, None)
def testAddr2bin(self):
res = DNSUtils.addr2bin('10.0.0.0')
self.assertEqual(res, 167772160L)
res = DNSUtils.addr2bin('10.0.0.0', cidr=None)
self.assertEqual(res, 167772160L)
res = DNSUtils.addr2bin('10.0.0.0', cidr=32L)
self.assertEqual(res, 167772160L)
res = DNSUtils.addr2bin('10.0.0.1', cidr=32L)
self.assertEqual(res, 167772161L)
res = DNSUtils.addr2bin('10.0.0.1', cidr=31L)
self.assertEqual(res, 167772160L)
def testBin2addr(self):
res = DNSUtils.bin2addr(167772160L)
self.assertEqual(res, '10.0.0.0')
res = IPAddr('10.0.0.0')
self.assertEqual(res.addr, 167772160L)
res = IPAddr('10.0.0.0', cidr=None)
self.assertEqual(res.addr, 167772160L)
res = IPAddr('10.0.0.0', cidr=32L)
self.assertEqual(res.addr, 167772160L)
res = IPAddr('10.0.0.1', cidr=32L)
self.assertEqual(res.addr, 167772161L)
res = IPAddr('10.0.0.1', cidr=31L)
self.assertEqual(res.addr, 167772160L)
class JailTests(unittest.TestCase):
+15 -8
View File
@@ -71,14 +71,21 @@ def testSampleRegexsFactory(name):
filterConf.getOptions({})
for opt in filterConf.convert():
if opt[2] == "addfailregex":
self.filter.addFailRegex(opt[3])
elif opt[2] == "maxlines":
self.filter.setMaxLines(opt[3])
elif opt[2] == "addignoreregex":
self.filter.addIgnoreRegex(opt[3])
elif opt[2] == "datepattern":
self.filter.setDatePattern(opt[3])
if opt[0] == 'multi-set':
optval = opt[3]
elif opt[0] == 'set':
optval = [opt[3]]
else:
continue
for optval in optval:
if opt[2] == "addfailregex":
self.filter.addFailRegex(optval)
elif opt[2] == "addignoreregex":
self.filter.addIgnoreRegex(optval)
elif opt[2] == "maxlines":
self.filter.setMaxLines(optval)
elif opt[2] == "datepattern":
self.filter.setDatePattern(optval)
self.assertTrue(
os.path.isfile(os.path.join(TEST_FILES_DIR, "logs", name)),
+434 -5
View File
@@ -33,9 +33,11 @@ import sys
import platform
from ..server.failregex import Regex, FailRegex, RegexException
from ..server import actions as _actions
from ..server.server import Server
from ..server.jail import Jail
from ..server.jailthread import JailThread
from ..ipaddr import IPAddr
from .utils import LogCaptureTestCase
from ..helpers import getLogger
from .. import version
@@ -47,6 +49,8 @@ except ImportError: # pragma: no cover
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
logSys = getLogger("fail2ban")
class TestServer(Server):
def setLogLevel(self, *args, **kwargs):
@@ -110,18 +114,23 @@ class TransmitterBase(unittest.TestCase):
cmdAdd = "add" + cmd
cmdDel = "del" + cmd
# sorting IPAddr objects and strings differs so that the comparism values
# must also be sorted as IPAddr objects
# convert to IPAddr objects if values look like a list of IP address strings
ips = map(lambda x: IPAddr(x) if IPAddr.searchIP(x) else x , values)
self.assertEqual(
self.transm.proceed(["get", jail, cmd]), (0, []))
for n, value in enumerate(values):
ret = self.transm.proceed(["set", jail, cmdAdd, value])
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(values[:n+1])))
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(ips[:n+1])))
ret = self.transm.proceed(["get", jail, cmd])
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(values[:n+1])))
for n, value in enumerate(values):
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(ips[:n+1])))
for n, value in enumerate(ips):
ret = self.transm.proceed(["set", jail, cmdDel, value])
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(values[n+1:])))
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(ips[n+1:])))
ret = self.transm.proceed(["get", jail, cmd])
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(values[n+1:])))
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(ips[n+1:])))
def jailAddDelRegexTest(self, cmd, inValues, outValues, jail):
cmdAdd = "add" + cmd
@@ -936,3 +945,423 @@ class LoggingTests(LogCaptureTestCase):
sys.__excepthook__ = prev_exchook
self.assertEqual(len(x), 1)
self.assertEqual(x[0][0], RuntimeError)
from clientreadertestcase import ActionReader, JailReader, JailsReader, CONFIG_DIR, STOCK
class ServerConfigReaderTests(LogCaptureTestCase):
def __init__(self, *args, **kwargs):
super(ServerConfigReaderTests, self).__init__(*args, **kwargs)
self.__share_cfg = {}
def setUp(self):
"""Call before every test case."""
super(ServerConfigReaderTests, self).setUp()
self._execCmdLst = []
def tearDown(self):
"""Call after every test case."""
super(ServerConfigReaderTests, self).tearDown()
def _executeCmd(self, realCmd, timeout=60):
for l in realCmd.split('\n'):
if not l.startswith('#'):
logSys.debug('exec-cmd: `%s`', l)
else:
logSys.debug(l)
return True
def test_IPAddr(self):
self.assertTrue(IPAddr('192.0.2.1').isIPv4)
self.assertTrue(IPAddr('2001:DB8::').isIPv6)
if STOCK:
def testCheckStockJailActions(self):
jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=self.__share_cfg) # we are running tests from root project dir atm
self.assertTrue(jails.read()) # opens fine
self.assertTrue(jails.getOptions()) # reads fine
stream = jails.convert(allow_no_files=True)
server = TestServer()
transm = server._Server__transm
cmdHandler = transm._Transmitter__commandHandler
# for cmd in stream:
# print(cmd)
# filter all start commands (we want not start all jails):
for cmd in stream:
if cmd[0] != 'start':
# change to the fast init backend:
if cmd[0] == 'add':
cmd[2] = 'polling'
# change log path to test log of jail (to prevent "Permission denied" on /var/logs/ for test-user):
elif len(cmd) > 3 and cmd[0] == 'set' and cmd[2] == 'addlogpath':
cmd[3] = os.path.join(TEST_FILES_DIR, 'logs', cmd[1])
# add dummy regex to prevent too long compile of all regexp (we don't use it in this test at all):
# [todo sebres] remove `not hasattr(unittest, 'F2B') or `, after merge with "f2b-perfom-prepare-716" ...
elif (not hasattr(unittest, 'F2B') or unittest.F2B.fast) and (
len(cmd) > 3 and cmd[0] in ('set', 'multi-set') and cmd[2] == 'addfailregex'
):
cmd[0] = "set"
cmd[3] = "DUMMY-REGEX <HOST>"
# command to server, use cmdHandler direct instead of `transm.proceed(cmd)`:
try:
cmdHandler(cmd)
except Exception, e: # pragma: no cover
self.fail("Command %r has failed. Received %r" % (cmd, e))
# jails = server._Server__jails
# for j in jails:
# print(j, jails[j])
def getDefaultJailStream(self, jail, act):
act = act.replace('%(__name__)s', jail)
actName, actOpt = JailReader.extractOptions(act)
stream = [
['add', jail, 'polling'],
# ['set', jail, 'addfailregex', 'DUMMY-REGEX <HOST>'],
]
action = ActionReader(
actName, jail, actOpt,
share_config=self.__share_cfg, basedir=CONFIG_DIR)
self.assertTrue(action.read())
action.getOptions({})
stream.extend(action.convert())
return stream
def testCheckStockCommandActions(self):
# test cases to check valid ipv4/ipv6 action definition, tuple with (('jail', 'action[params]', 'tests', ...)
# where tests is a dictionary contains:
# 'ip4' - should not be found (logged) on ban/unban of IPv6 (negative test),
# 'ip6' - should not be found (logged) on ban/unban of IPv4 (negative test),
# 'start', 'stop' - should be found (logged) on action start/stop,
# etc.
testJailsActions = (
# iptables-multiport --
('j-w-iptables-mp', 'iptables-multiport[name=%(__name__)s, bantime="600", port="http,https", protocol="tcp", chain="INPUT"]', {
'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'),
'start': (
"`iptables -w -N f2b-j-w-iptables-mp`",
"`iptables -w -A f2b-j-w-iptables-mp -j RETURN`",
"`iptables -w -I INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`",
"`ip6tables -w -N f2b-j-w-iptables-mp`",
"`ip6tables -w -A f2b-j-w-iptables-mp -j RETURN`",
"`ip6tables -w -I INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`",
),
'stop': (
"`iptables -w -D INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`",
"`iptables -w -F f2b-j-w-iptables-mp`",
"`iptables -w -X f2b-j-w-iptables-mp`",
"`ip6tables -w -D INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`",
"`ip6tables -w -F f2b-j-w-iptables-mp`",
"`ip6tables -w -X f2b-j-w-iptables-mp`",
),
'ip4-check': (
r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-mp[ \t]'`""",
),
'ip6-check': (
r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-mp[ \t]'`""",
),
'ip4-ban': (
r"`iptables -w -I f2b-j-w-iptables-mp 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
),
'ip4-unban': (
r"`iptables -w -D f2b-j-w-iptables-mp -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
),
'ip6-ban': (
r"`ip6tables -w -I f2b-j-w-iptables-mp 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
),
'ip6-unban': (
r"`ip6tables -w -D f2b-j-w-iptables-mp -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
),
}),
# iptables-allports --
('j-w-iptables-ap', 'iptables-allports[name=%(__name__)s, bantime="600", protocol="tcp", chain="INPUT"]', {
'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'),
'start': (
"`iptables -w -N f2b-j-w-iptables-ap`",
"`iptables -w -A f2b-j-w-iptables-ap -j RETURN`",
"`iptables -w -I INPUT -p tcp -j f2b-j-w-iptables-ap`",
"`ip6tables -w -N f2b-j-w-iptables-ap`",
"`ip6tables -w -A f2b-j-w-iptables-ap -j RETURN`",
"`ip6tables -w -I INPUT -p tcp -j f2b-j-w-iptables-ap`",
),
'stop': (
"`iptables -w -D INPUT -p tcp -j f2b-j-w-iptables-ap`",
"`iptables -w -F f2b-j-w-iptables-ap`",
"`iptables -w -X f2b-j-w-iptables-ap`",
"`ip6tables -w -D INPUT -p tcp -j f2b-j-w-iptables-ap`",
"`ip6tables -w -F f2b-j-w-iptables-ap`",
"`ip6tables -w -X f2b-j-w-iptables-ap`",
),
'ip4-check': (
r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-ap[ \t]'`""",
),
'ip6-check': (
r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-ap[ \t]'`""",
),
'ip4-ban': (
r"`iptables -w -I f2b-j-w-iptables-ap 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
),
'ip4-unban': (
r"`iptables -w -D f2b-j-w-iptables-ap -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
),
'ip6-ban': (
r"`ip6tables -w -I f2b-j-w-iptables-ap 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
),
'ip6-unban': (
r"`ip6tables -w -D f2b-j-w-iptables-ap -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
),
}),
# iptables-ipset-proto6 --
('j-w-iptables-ipset', 'iptables-ipset-proto6[name=%(__name__)s, bantime="600", port="http", protocol="tcp", chain="INPUT"]', {
'ip4': (' f2b-j-w-iptables-ipset ',), 'ip6': (' f2b-j-w-iptables-ipset6 ',),
'start': (
"`ipset create f2b-j-w-iptables-ipset hash:ip timeout 600`",
"`iptables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset src -j REJECT --reject-with icmp-port-unreachable`",
"`ipset create f2b-j-w-iptables-ipset6 hash:ip timeout 600 family inet6`",
"`ip6tables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`",
),
'stop': (
"`iptables -w -D INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset src -j REJECT --reject-with icmp-port-unreachable`",
"`ipset flush f2b-j-w-iptables-ipset`",
"`ipset destroy f2b-j-w-iptables-ipset`",
"`ip6tables -w -D INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`",
"`ipset flush f2b-j-w-iptables-ipset6`",
"`ipset destroy f2b-j-w-iptables-ipset6`",
),
'ip4-check': (),
'ip6-check': (),
'ip4-ban': (
r"`ipset add f2b-j-w-iptables-ipset 192.0.2.1 timeout 600 -exist`",
),
'ip4-unban': (
r"`ipset del f2b-j-w-iptables-ipset 192.0.2.1 -exist`",
),
'ip6-ban': (
r"`ipset add f2b-j-w-iptables-ipset6 2001:db8:: timeout 600 -exist`",
),
'ip6-unban': (
r"`ipset del f2b-j-w-iptables-ipset6 2001:db8:: -exist`",
),
}),
# iptables-ipset-proto6-allports --
('j-w-iptables-ipset-ap', 'iptables-ipset-proto6-allports[name=%(__name__)s, bantime="600", chain="INPUT"]', {
'ip4': (' f2b-j-w-iptables-ipset-ap ',), 'ip6': (' f2b-j-w-iptables-ipset-ap6 ',),
'start': (
"`ipset create f2b-j-w-iptables-ipset-ap hash:ip timeout 600`",
"`iptables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`",
"`ipset create f2b-j-w-iptables-ipset-ap6 hash:ip timeout 600 family inet6`",
"`ip6tables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`",
),
'stop': (
"`iptables -w -D INPUT -m set --match-set f2b-j-w-iptables-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`",
"`ipset flush f2b-j-w-iptables-ipset-ap`",
"`ipset destroy f2b-j-w-iptables-ipset-ap`",
"`ip6tables -w -D INPUT -m set --match-set f2b-j-w-iptables-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`",
"`ipset flush f2b-j-w-iptables-ipset-ap6`",
"`ipset destroy f2b-j-w-iptables-ipset-ap6`",
),
'ip4-check': (),
'ip6-check': (),
'ip4-ban': (
r"`ipset add f2b-j-w-iptables-ipset-ap 192.0.2.1 timeout 600 -exist`",
),
'ip4-unban': (
r"`ipset del f2b-j-w-iptables-ipset-ap 192.0.2.1 -exist`",
),
'ip6-ban': (
r"`ipset add f2b-j-w-iptables-ipset-ap6 2001:db8:: timeout 600 -exist`",
),
'ip6-unban': (
r"`ipset del f2b-j-w-iptables-ipset-ap6 2001:db8:: -exist`",
),
}),
# iptables --
('j-w-iptables', 'iptables[name=%(__name__)s, bantime="600", port="http", protocol="tcp", chain="INPUT"]', {
'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'),
'start': (
"`iptables -w -N f2b-j-w-iptables`",
"`iptables -w -A f2b-j-w-iptables -j RETURN`",
"`iptables -w -I INPUT -p tcp --dport http -j f2b-j-w-iptables`",
"`ip6tables -w -N f2b-j-w-iptables`",
"`ip6tables -w -A f2b-j-w-iptables -j RETURN`",
"`ip6tables -w -I INPUT -p tcp --dport http -j f2b-j-w-iptables`",
),
'stop': (
"`iptables -w -D INPUT -p tcp --dport http -j f2b-j-w-iptables`",
"`iptables -w -F f2b-j-w-iptables`",
"`iptables -w -X f2b-j-w-iptables`",
"`ip6tables -w -D INPUT -p tcp --dport http -j f2b-j-w-iptables`",
"`ip6tables -w -F f2b-j-w-iptables`",
"`ip6tables -w -X f2b-j-w-iptables`",
),
'ip4-check': (
r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables[ \t]'`""",
),
'ip6-check': (
r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables[ \t]'`""",
),
'ip4-ban': (
r"`iptables -w -I f2b-j-w-iptables 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
),
'ip4-unban': (
r"`iptables -w -D f2b-j-w-iptables -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
),
'ip6-ban': (
r"`ip6tables -w -I f2b-j-w-iptables 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
),
'ip6-unban': (
r"`ip6tables -w -D f2b-j-w-iptables -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
),
}),
# iptables-new --
('j-w-iptables-new', 'iptables-new[name=%(__name__)s, bantime="600", port="http", protocol="tcp", chain="INPUT"]', {
'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'),
'start': (
"`iptables -w -N f2b-j-w-iptables-new`",
"`iptables -w -A f2b-j-w-iptables-new -j RETURN`",
"`iptables -w -I INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`",
"`ip6tables -w -N f2b-j-w-iptables-new`",
"`ip6tables -w -A f2b-j-w-iptables-new -j RETURN`",
"`ip6tables -w -I INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`",
),
'stop': (
"`iptables -w -D INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`",
"`iptables -w -F f2b-j-w-iptables-new`",
"`iptables -w -X f2b-j-w-iptables-new`",
"`ip6tables -w -D INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`",
"`ip6tables -w -F f2b-j-w-iptables-new`",
"`ip6tables -w -X f2b-j-w-iptables-new`",
),
'ip4-check': (
r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-new[ \t]'`""",
),
'ip6-check': (
r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-new[ \t]'`""",
),
'ip4-ban': (
r"`iptables -w -I f2b-j-w-iptables-new 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
),
'ip4-unban': (
r"`iptables -w -D f2b-j-w-iptables-new -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
),
'ip6-ban': (
r"`ip6tables -w -I f2b-j-w-iptables-new 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
),
'ip6-unban': (
r"`ip6tables -w -D f2b-j-w-iptables-new -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
),
}),
# iptables-xt_recent-echo --
('j-w-iptables-xtre', 'iptables-xt_recent-echo[name=%(__name__)s, bantime="600", chain="INPUT"]', {
'ip4': ('`iptables ', '/f2b-j-w-iptables-xtre`'), 'ip6': ('`ip6tables ', '/f2b-j-w-iptables-xtre6`'),
'start': (
"`if [ `id -u` -eq 0 ];then iptables -w -I INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre -j REJECT --reject-with icmp-port-unreachable;fi`",
"`if [ `id -u` -eq 0 ];then ip6tables -w -I INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre6 -j REJECT --reject-with icmp6-port-unreachable;fi`",
),
'stop': (
"`echo / > /proc/net/xt_recent/f2b-j-w-iptables-xtre`",
"`if [ `id -u` -eq 0 ];then iptables -w -D INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre -j REJECT --reject-with icmp-port-unreachable;fi`",
"`echo / > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`",
"`if [ `id -u` -eq 0 ];then ip6tables -w -D INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre6 -j REJECT --reject-with icmp6-port-unreachable;fi`",
),
'ip4-check': (
r"`test -e /proc/net/xt_recent/f2b-j-w-iptables-xtre`",
),
'ip6-check': (
r"`test -e /proc/net/xt_recent/f2b-j-w-iptables-xtre6`",
),
'ip4-ban': (
r"`echo +192.0.2.1 > /proc/net/xt_recent/f2b-j-w-iptables-xtre`",
),
'ip4-unban': (
r"`echo -192.0.2.1 > /proc/net/xt_recent/f2b-j-w-iptables-xtre`",
),
'ip6-ban': (
r"`echo +2001:db8:: > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`",
),
'ip6-unban': (
r"`echo -2001:db8:: > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`",
),
}),
# pf --
('j-w-pf', 'pf[name=%(__name__)s]', {
'ip4': (), 'ip6': (),
'start': (
'`echo "table <f2b-j-w-pf> persist counters" | pfctl -f-`',
'`echo "block proto tcp from <f2b-j-w-pf> to any port any" | pfctl -f-`',
),
'stop': (
'`pfctl -sr 2>/dev/null | grep -v f2b-j-w-pf | pfctl -f-`',
'`pfctl -t f2b-j-w-pf -T flush`',
'`pfctl -t f2b-j-w-pf -T kill`',
),
'ip4-check': ("`pfctl -sr | grep -q f2b-j-w-pf`",),
'ip6-check': ("`pfctl -sr | grep -q f2b-j-w-pf`",),
'ip4-ban': ("`pfctl -t f2b-j-w-pf -T add 192.0.2.1`",),
'ip4-unban': ("`pfctl -t f2b-j-w-pf -T delete 192.0.2.1`",),
'ip6-ban': ("`pfctl -t f2b-j-w-pf -T add 2001:db8::`",),
'ip6-unban': ("`pfctl -t f2b-j-w-pf -T delete 2001:db8::`",),
}),
)
server = TestServer()
transm = server._Server__transm
cmdHandler = transm._Transmitter__commandHandler
for jail, act, tests in testJailsActions:
stream = self.getDefaultJailStream(jail, act)
# for cmd in stream:
# print(cmd)
# filter all start commands (we want not start all jails):
for cmd in stream:
# command to server:
ret, res = transm.proceed(cmd)
self.assertEqual(ret, 0)
jails = server._Server__jails
for jail, act, tests in testJailsActions:
# print(jail, jails[jail])
for a in jails[jail].actions:
action = jails[jail].actions[a]
logSys.debug('# ' + ('=' * 50))
logSys.debug('# == %-44s ==', jail + ' - ' + action._name)
logSys.debug('# ' + ('=' * 50))
self.assertTrue(isinstance(action, _actions.CommandAction))
# wrap default command processor:
action.executeCmd = self._executeCmd
# test start :
logSys.debug('# === start ==='); self.pruneLog()
action.start()
self.assertLogged(*tests['start'], all=True)
# test ban ip4 :
logSys.debug('# === ban-ipv4 ==='); self.pruneLog()
action.ban({'ip': IPAddr('192.0.2.1')})
self.assertLogged(*tests['ip4-check']+tests['ip4-ban'], all=True)
self.assertNotLogged(*tests['ip6'], all=True)
# test unban ip4 :
logSys.debug('# === unban ipv4 ==='); self.pruneLog()
action.unban({'ip': IPAddr('192.0.2.1')})
self.assertLogged(*tests['ip4-check']+tests['ip4-unban'], all=True)
self.assertNotLogged(*tests['ip6'], all=True)
# test ban ip6 :
logSys.debug('# === ban ipv6 ==='); self.pruneLog()
action.ban({'ip': IPAddr('2001:DB8::')})
self.assertLogged(*tests['ip6-check']+tests['ip6-ban'], all=True)
self.assertNotLogged(*tests['ip4'], all=True)
# test unban ip6 :
logSys.debug('# === unban ipv6 ==='); self.pruneLog()
action.unban({'ip': IPAddr('2001:DB8::')})
self.assertLogged(*tests['ip6-check']+tests['ip6-unban'], all=True)
self.assertNotLogged(*tests['ip4'], all=True)
# test stop :
logSys.debug('# === stop ==='); self.pruneLog()
action.stop()
self.assertLogged(*tests['stop'], all=True)
+34 -10
View File
@@ -74,6 +74,7 @@ def gatherTests(regexps=None, no_network=False):
# Import all the test cases here instead of a module level to
# avoid circular imports
from . import banmanagertestcase
from . import clientbeautifiertestcase
from . import clientreadertestcase
from . import failmanagertestcase
from . import filtertestcase
@@ -108,6 +109,7 @@ def gatherTests(regexps=None, no_network=False):
tests.addTest(unittest.makeSuite(servertestcase.JailTests))
tests.addTest(unittest.makeSuite(servertestcase.RegexTests))
tests.addTest(unittest.makeSuite(servertestcase.LoggingTests))
tests.addTest(unittest.makeSuite(servertestcase.ServerConfigReaderTests))
tests.addTest(unittest.makeSuite(actiontestcase.CommandActionTest))
tests.addTest(unittest.makeSuite(actionstestcase.ExecuteActions))
# FailManager
@@ -119,6 +121,10 @@ def gatherTests(regexps=None, no_network=False):
tests.addTest(unittest.makeSuite(banmanagertestcase.StatusExtendedCymruInfo))
except ImportError:
pass
# ClientBeautifier
tests.addTest(unittest.makeSuite(clientbeautifiertestcase.BeautifierTest))
# ClientReaders
tests.addTest(unittest.makeSuite(clientreadertestcase.ConfigReaderTest))
tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest))
@@ -248,7 +254,7 @@ class LogCaptureTestCase(unittest.TestCase):
def _is_logged(self, s):
return s in self._log.getvalue()
def assertLogged(self, *s):
def assertLogged(self, *s, **kwargs):
"""Assert that one of the strings was logged
Preferable to assertTrue(self._is_logged(..)))
@@ -258,14 +264,23 @@ class LogCaptureTestCase(unittest.TestCase):
----------
s : string or list/set/tuple of strings
Test should succeed if string (or any of the listed) is present in the log
all : boolean (default False) if True should fail if any of s not logged
"""
logged = self._log.getvalue()
for s_ in s:
if s_ in logged:
return
raise AssertionError("None among %r was found in the log: %r" % (s, logged))
if not kwargs.get('all', False):
# at least one entry should be found:
for s_ in s:
if s_ in logged:
return
if True: # pragma: no cover
self.fail("None among %r was found in the log: ===\n%s===" % (s, logged))
else:
# each entry should be found:
for s_ in s:
if s_ not in logged: # pragma: no cover
self.fail("%r was not found in the log: ===\n%s===" % (s_, logged))
def assertNotLogged(self, *s):
def assertNotLogged(self, *s, **kwargs):
"""Assert that strings were not logged
Parameters
@@ -273,13 +288,22 @@ class LogCaptureTestCase(unittest.TestCase):
s : string or list/set/tuple of strings
Test should succeed if the string (or at least one of the listed) is not
present in the log
all : boolean (default False) if True should fail if any of s logged
"""
logged = self._log.getvalue()
for s_ in s:
if s_ not in logged:
return
raise AssertionError("All of the %r were found present in the log: %r" % (s, logged))
if not kwargs.get('all', False):
for s_ in s:
if s_ not in logged:
return
if True: # pragma: no cover
self.fail("All of the %r were found present in the log: ===\n%s===" % (s, logged))
else:
for s_ in s:
if s_ in logged: # pragma: no cover
self.fail("%r was found in the log: ===\n%s===" % (s_, logged))
def pruneLog(self):
self._log.truncate(0)
def getLog(self):
return self._log.getvalue()
+15
View File
@@ -269,17 +269,32 @@ The following commands can be present in the [Definition] section.
.B actionstart
command(s) executed when the jail starts.
.TP
.B actionstart6
IPv6 specific equivalent for actionstart
.TP
.B actionstop
command(s) executed when the jail stops.
.TP
.B actionstop6
IPv6 specific equivalent for actionstop
.TP
.B actioncheck
command(s) ran before any other action. It aims to verify if the environment is still ok.
.TP
.B actioncheck6
IPv6 specific equivalent for actioncheck
.TP
.B actionban
command(s) that bans the IP address after \fBmaxretry\fR log lines matches within last \fBfindtime\fR seconds.
.TP
.B actionban6
IPv6 specific equivalent for actionban
.TP
.B actionunban
command(s) that unbans the IP address after \fBbantime\fR.
.TP
.B actionunban6
IPv6 specific equivalent for actionunban
.PP
The [Init] section allows for action-specific settings. In \fIjail.conf/jail.local\fR these can be overwritten for a particular jail as options to the jail. The following are special tags which can be set in the [Init] section:
.TP