diff --git a/ChangeLog b/ChangeLog index cf2816bbc6..cc38e99144 100644 --- a/ChangeLog +++ b/ChangeLog @@ -79,6 +79,9 @@ ver. 0.10.5-dev-1 (20??/??/??) - development edition * `action.d/badips.py`: fixed start of banaction on demand (which may be IP-family related), gh-2390 * `action.d/helpers-common.conf`: rewritten grep arguments, now options `-wF` used to match only whole words and fixed string (not as pattern), gh-2298 +* `filter.d/apache-auth.conf`: + - ignore errors from mod_evasive in `normal` mode (mode-controlled now) (gh-2548); + - extended with option `mode` - `normal` (default) and `aggressive` * `filter.d/sshd.conf`: - matches `Bad protocol version identification` in `ddos` and `aggressive` modes (gh-2404). - captures `Disconnecting ...: Change of username or service not allowed` (gh-2239, gh-2279) @@ -138,6 +141,16 @@ filter = flt[logtype=short] attempts (failure) for IP (resp. failure-ID), see gh-2351; Syntax: - `fail2ban-client set attempt [ ... ]` +* `action.d/nftables.conf`: + - isolate fail2ban rules into a dedicated table and chain (gh-2254) + - `nftables-allports` supports multiple protocols in single rule now + - combined nftables actions to single action `nftables`: + * `nftables-common` is removed (replaced with single action `nftables` now) + * `nftables-allports` is obsolete, superseded by `nftables[type=allports]` + * `nftables-multiport` is obsolete, superseded by `nftables[type=multiport]` + - allowed multiple protocols in `nftables[type=multiport]` action (single set with multiple rules + in chain), following configuration in jail would replace 3 separate actions, see + https://github.com/fail2ban/fail2ban/pull/2254#issuecomment-534684675 * `action.d/badips.py`: option `loglevel` extended with level of summary message, following example configuration logging summary with NOTICE and rest with DEBUG log-levels: `action = badips.py[loglevel="debug, notice"]` diff --git a/config/action.d/nftables-allports.conf b/config/action.d/nftables-allports.conf index 6c69da39f6..908abe4006 100644 --- a/config/action.d/nftables-allports.conf +++ b/config/action.d/nftables-allports.conf @@ -6,17 +6,12 @@ # Modified: Alexander Belykh # adapted for nftables # +# Obsolete: superseded by nftables[type=allports] [INCLUDES] -before = nftables-common.conf +before = nftables.conf [Definition] -# Option: nftables_mode -# Notes.: additional expressions for nftables filter rule -# Values: nftables expressions -# -nftables_mode = meta l4proto - -[Init] +type = allports diff --git a/config/action.d/nftables-common.conf b/config/action.d/nftables-common.conf deleted file mode 100644 index 3704571284..0000000000 --- a/config/action.d/nftables-common.conf +++ /dev/null @@ -1,135 +0,0 @@ -# Fail2Ban configuration file -# -# Author: Daniel Black -# Author: Cyril Jaquier -# Modified: Yaroslav O. Halchenko -# made active on all ports from original iptables.conf -# Modified: Alexander Belykh -# adapted for nftables -# -# This is a included configuration file and includes the definitions for the nftables -# used in all nftables based actions by default. -# -# The user can override the defaults in nftables-common.local - -[INCLUDES] - -after = nftables-common.local - -[Definition] - -# Option: nftables_mode -# Notes.: additional expressions for nftables filter rule -# Values: nftables expressions -# -nftables_mode = dport \{ \} - -# Option: actionstart -# Notes.: command executed on demand at the first ban (or at the start of Fail2Ban if actionstart_on_demand is set to false). -# Values: CMD -# -actionstart = add set \{ type \; \} - insert rule %(nftables_mode)s saddr @ - -_nft_list = --handle --numeric list chain -_nft_get_handle_id = grep -m1 ' saddr @ # handle' | grep -oe ' handle [0-9]*' - -# Option: actionstop -# Notes.: command executed at the stop of jail (or at the end of Fail2Ban) -# Values: CMD -# -actionstop = HANDLE_ID=$(%(_nft_list)s | %(_nft_get_handle_id)s) - delete rule $HANDLE_ID - delete set - -# Option: actioncheck -# Notes.: command executed once before each actionban command -# Values: CMD -# -actioncheck = list chain | grep -q '@[ \t]' - -# Option: actionban -# Notes.: command executed when banning an IP. Take care that the -# command is executed with Fail2Ban user rights. -# Tags: See jail.conf(5) man page -# Values: CMD -# -actionban = add element \{ \} - -# Option: actionunban -# Notes.: command executed when unbanning an IP. Take care that the -# command is executed with Fail2Ban user rights. -# Tags: See jail.conf(5) man page -# Values: CMD -# -actionunban = delete element \{ \} - -[Init] - -# Option: nftables_type -# Notes.: address type to work with -# Values: [ipv4_addr | ipv6_addr] Default: ipv4_addr -# -nftables_type = ipv4_addr - -# Option: nftables_family -# Notes.: address family to work in -# Values: [ip | ip6 | inet] Default: inet -# -nftables_family = inet - -# Option: nftables_table -# Notes.: table in the address family to work in -# Values: STRING Default: filter -# -nftables_table = filter - -# Option: chain -# Notes specifies the nftables chain to which the Fail2Ban rules should be -# added -# Values: STRING Default: input -chain = input - -# Default name of the filtering set -# -name = default - -# Option: port -# Notes.: specifies port to monitor -# Values: [ NUM | STRING ] Default: -# -port = ssh - -# Option: protocol -# Notes.: internally used by config reader for interpolations. -# Values: [ tcp | udp ] Default: tcp -# -protocol = tcp - -# Option: blocktype -# Note: This is what the action does with rules. This can be any jump target -# as per the nftables man page (section 8). Common values are drop -# reject, reject with icmp type host-unreachable -# Values: STRING -blocktype = reject - -# Option: nftables -# Notes.: Actual command to be executed, including common to all calls options -# Values: STRING -nftables = nft - -# Option: set_name -# Notes.: The name of the nft set used to store banned addresses -# Values: STRING -set_name = f2b- - -# Option: address_family -# Notes.: The family of the banned addresses -# Values: [ ip | ip6 ] -address_family = ip - -[Init?family=inet6] - -nftables_type = ipv6_addr -set_name = f2b-6 -address_family = ip6 diff --git a/config/action.d/nftables-multiport.conf b/config/action.d/nftables-multiport.conf index d1afafb324..ba3ec92c9d 100644 --- a/config/action.d/nftables-multiport.conf +++ b/config/action.d/nftables-multiport.conf @@ -6,17 +6,12 @@ # Modified: Alexander Belykh # adapted for nftables # +# Obsolete: superseded by nftables[type=multiport] [INCLUDES] -before = nftables-common.conf +before = nftables.conf [Definition] -# Option: nftables_mode -# Notes.: additional expressions for nftables filter rule -# Values: nftables expressions -# -nftables_mode = dport \{ \} - -[Init] +type = multiport \ No newline at end of file diff --git a/config/action.d/nftables.conf b/config/action.d/nftables.conf new file mode 100644 index 0000000000..e7186c47f9 --- /dev/null +++ b/config/action.d/nftables.conf @@ -0,0 +1,184 @@ +# Fail2Ban configuration file +# +# Author: Daniel Black +# Author: Cyril Jaquier +# Modified: Yaroslav O. Halchenko +# made active on all ports from original iptables.conf +# Modified: Alexander Belykh +# adapted for nftables +# +# This is a included configuration file and includes the definitions for the nftables +# used in all nftables based actions by default. +# +# The user can override the defaults in nftables-common.local +# Example: redirect flow to honeypot +# +# [Init] +# table_family = ip +# chain_type = nat +# chain_hook = prerouting +# chain_priority = -50 +# blocktype = counter redirect to 2222 + +[INCLUDES] + +after = nftables-common.local + +[Definition] + +# Option: type +# Notes.: type of the action. +# Values: [ multiport | allports ] Default: multiport +# +type = multiport + +rule_match-custom = +rule_match-allports = meta l4proto \{ \} +rule_match-multiport = $proto dport \{ \} +match = > + +# Option: rule_stat +# Notes.: statement for nftables filter rule. +# leaving it empty will block all (include udp and icmp) +# Values: nftables statement +# +rule_stat = %(match)s saddr @ + +# optional interator over protocol's: +_nft_for_proto-custom-iter = +_nft_for_proto-custom-done = +_nft_for_proto-allports-iter = +_nft_for_proto-allports-done = +_nft_for_proto-multiport-iter = for proto in $(echo '' | sed 's/,/ /g'); do +_nft_for_proto-multiport-done = done + +_nft_list = -a list chain f2b-table f2b-chain +_nft_get_handle_id = grep -oP '@\s+.*\s+\Khandle\s+(\d+)$' + +_nft_add_set = add set f2b-table \{ type \; \} + <_nft_for_proto--iter> + add rule f2b-table f2b-chain %(rule_stat)s + <_nft_for_proto--done> +_nft_del_set = (%(_nft_list)s | %(_nft_get_handle_id)s) | while read -r hdl; do + delete rule f2b-table f2b-chain $hdl; done + delete set f2b-table + +# Option: actionstart +# Notes.: command executed on demand at the first ban (or at the start of Fail2Ban if actionstart_on_demand is set to false). +# Values: CMD +# +actionstart = add table f2b-table + -- add chain f2b-table f2b-chain \{ type hook priority \; \} + %(_nft_add_set)s + +# Option: actionflush +# Notes.: command executed once to flush IPS, by shutdown (resp. by stop of the jail or this action); +# uses `nft flush set ...` and as fallback (e. g. unsupported) recreates the set (with references) +# Values: CMD +# +actionflush = ( flush set f2b-table 2> /dev/null) || ( + %(_nft_del_set)s + %(_nft_add_set)s + ) + +# Option: actionstop +# Notes.: command executed at the stop of jail (or at the end of Fail2Ban) +# Values: CMD +# +actionstop = %(_nft_del_set)s + +# Option: actioncheck +# Notes.: command executed once before each actionban command +# Values: CMD +# +actioncheck = list chain f2b-table f2b-chain | grep -q '@[ \t]' + +# Option: actionban +# Notes.: command executed when banning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionban = add element f2b-table { } + +# Option: actionunban +# Notes.: command executed when unbanning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionunban = delete element f2b-table { } + +[Init] + +# Option: table_family +# Notes.: address family to work in +# Values: [ip | ip6 | inet] Default: inet +table_family = inet + +# Option: chain_type +# Notes.: refers to the kind of chain to be created +# Values: [filter | route | nat] Default: filter +# +chain_type = filter + +# Option: chain_hook +# Notes.: refers to the kind of chain to be created +# Values: [ prerouting | input | forward | output | postrouting ] Default: input +# +chain_hook = input + +# Option: chain_priority +# Notes.: priority in the chain. +# Values: NUMBER Default: -1 +# +chain_priority = -1 + +# Option: addr_type +# Notes.: address type to work with +# Values: [ipv4_addr | ipv6_addr] Default: ipv4_addr +# +addr_type = ipv4_addr + +# Default name of the filtering set +# +name = default + +# Option: port +# Notes.: specifies port to monitor +# Values: [ NUM | STRING ] Default: +# +port = ssh + +# Option: protocol +# Notes.: internally used by config reader for interpolations. +# Values: [ tcp | udp ] Default: tcp +# +protocol = tcp + +# Option: blocktype +# Note: This is what the action does with rules. This can be any jump target +# as per the nftables man page (section 8). Common values are drop, +# reject, reject with icmpx type host-unreachable, redirect to 2222 +# Values: STRING +blocktype = reject + +# Option: nftables +# Notes.: Actual command to be executed, including common to all calls options +# Values: STRING +nftables = nft + +# Option: addr_set +# Notes.: The name of the nft set used to store banned addresses +# Values: STRING +addr_set = addr-set- + +# Option: addr_family +# Notes.: The family of the banned addresses +# Values: [ ip | ip6 ] +addr_family = ip + +[Init?family=inet6] +addr_family = ip6 +addr_type = ipv6_addr +addr_set = addr6-set- diff --git a/config/filter.d/apache-auth.conf b/config/filter.d/apache-auth.conf index 91c89b2621..40f6d6e36c 100644 --- a/config/filter.d/apache-auth.conf +++ b/config/filter.d/apache-auth.conf @@ -9,6 +9,16 @@ before = apache-common.conf [Definition] +# Mode for filter: normal (default) and aggressive (allows DDoS & brute force detection of mod_evasive) +mode = normal + +# ignore messages of mod_evasive module: +apache-pref-ign-normal = (?!evasive) +# allow "denied by server configuration" from all modules: +apache-pref-ign-aggressive = +# mode related ignore prefix for common _apache_error_client substitution: +apache-pref-ignore = > + prefregex = ^%(_apache_error_client)s (?:AH\d+: )?.+$ # auth_type = ((?:Digest|Basic): )? diff --git a/config/filter.d/apache-common.conf b/config/filter.d/apache-common.conf index 3eec83d006..6577fe7d00 100644 --- a/config/filter.d/apache-common.conf +++ b/config/filter.d/apache-common.conf @@ -27,7 +27,9 @@ _daemon = (?:apache\d*|httpd(?:/\w+)?) apache-prefix = > -_apache_error_client = \[(:?error|\S+:\S+)\]( \[pid \d+(:\S+ \d+)?\])? \[client (:\d{1,5})?\] +apache-pref-ignore = + +_apache_error_client = \[(:?error|\S+:\S+)\]( \[pid \d+(:\S+ \d+)?\])? \[client (:\d{1,5})?\] datepattern = {^LN-BEG} diff --git a/fail2ban/tests/files/logs/apache-auth b/fail2ban/tests/files/logs/apache-auth index 93040b1dfa..fd3bae829e 100644 --- a/fail2ban/tests/files/logs/apache-auth +++ b/fail2ban/tests/files/logs/apache-auth @@ -134,6 +134,14 @@ # failJSON: { "time": "2018-03-28T01:31:42", "match": true , "host": "91.49.82.139" } [Wed Mar 28 01:31:42.355210 2018] [ssl:error] [pid 6586] [client 91.49.82.139:58028] AH02033: No hostname was provided via SNI for a name based virtual host +# failJSON: { "match": false, "desc": "ignore mod_evasive errors in normal mode (gh-2548)" } +[Thu Oct 17 18:43:40.160521 2019] [evasive20:error] [pid 22589] [client 192.0.2.1:56175] client denied by server configuration: /path/index.php, referer: https://hostname/path/ + +# filterOptions: {"mode": "aggressive"} + +# failJSON: { "time": "2019-10-17T18:43:40", "match": true, "host": "192.0.2.1", "desc": "accept mod_evasive errors in aggressive mode (gh-2548)" } +[Thu Oct 17 18:43:40.160521 2019] [evasive20:error] [pid 22589] [client 192.0.2.1:56175] client denied by server configuration: /path/index.php, referer: https://hostname/path/ + # filterOptions: {"logging": "syslog"} # failJSON: { "time": "2005-02-15T16:23:00", "match": true , "host": "192.0.2.1", "desc": "using syslog (ErrorLog syslog)" } diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index e5278eddea..9932a9ee7f 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -1321,6 +1321,101 @@ def testCheckStockCommandActions(self): # 'start', 'stop' - should be found (logged) on action start/stop, # etc. testJailsActions = ( + # nftables-multiport -- + ('j-w-nft-mp', 'nftables-multiport[name=%(__name__)s, port="http,https", protocol="tcp,udp,sctp"]', { + 'ip4': ('ip ', 'ipv4_addr', 'addr-'), 'ip6': ('ip6 ', 'ipv6_addr', 'addr6-'), + '*-start': ( + r"`nft add table inet f2b-table`", + r"`nft -- add chain inet f2b-table f2b-chain \{ type filter hook input priority -1 \; \}`", + # iterator over protocol is same for both families: + r"`for proto in $(echo 'tcp,udp,sctp' | sed 's/,/ /g'); do`", + r"`done`", + ), + 'ip4-start': ( + r"`nft add set inet f2b-table addr-set-j-w-nft-mp \{ type ipv4_addr\; \}`", + r"`nft add rule inet f2b-table f2b-chain $proto dport \{ http,https \} ip saddr @addr-set-j-w-nft-mp reject`", + ), + 'ip6-start': ( + r"`nft add set inet f2b-table addr6-set-j-w-nft-mp \{ type ipv6_addr\; \}`", + r"`nft add rule inet f2b-table f2b-chain $proto dport \{ http,https \} ip6 saddr @addr6-set-j-w-nft-mp reject`", + ), + 'flush': ( + "`(nft flush set inet f2b-table addr-set-j-w-nft-mp 2> /dev/null) || ", + "`(nft flush set inet f2b-table addr6-set-j-w-nft-mp 2> /dev/null) || ", + ), + 'stop': ( + "`(nft -a list chain inet f2b-table f2b-chain | grep -oP '@addr-set-j-w-nft-mp\s+.*\s+\Khandle\s+(\d+)$') | while read -r hdl; do`", + "`nft delete rule inet f2b-table f2b-chain $hdl; done`", + "`nft delete set inet f2b-table addr-set-j-w-nft-mp`", + "`(nft -a list chain inet f2b-table f2b-chain | grep -oP '@addr6-set-j-w-nft-mp\s+.*\s+\Khandle\s+(\d+)$') | while read -r hdl; do`", + "`nft delete rule inet f2b-table f2b-chain $hdl; done`", + "`nft delete set inet f2b-table addr6-set-j-w-nft-mp`", + ), + 'ip4-check': ( + r"`nft list chain inet f2b-table f2b-chain | grep -q '@addr-set-j-w-nft-mp[ \t]'`", + ), + 'ip6-check': ( + r"`nft list chain inet f2b-table f2b-chain | grep -q '@addr6-set-j-w-nft-mp[ \t]'`", + ), + 'ip4-ban': ( + r"`nft add element inet f2b-table addr-set-j-w-nft-mp { 192.0.2.1 }`", + ), + 'ip4-unban': ( + r"`nft delete element inet f2b-table addr-set-j-w-nft-mp { 192.0.2.1 }`", + ), + 'ip6-ban': ( + r"`nft add element inet f2b-table addr6-set-j-w-nft-mp { 2001:db8:: }`", + ), + 'ip6-unban': ( + r"`nft delete element inet f2b-table addr6-set-j-w-nft-mp { 2001:db8:: }`", + ), + }), + # nft-allports -- + ('j-w-nft-ap', 'nftables-allports[name=%(__name__)s, protocol="tcp,udp"]', { + 'ip4': ('ip ', 'ipv4_addr', 'addr-'), 'ip6': ('ip6 ', 'ipv6_addr', 'addr6-'), + '*-start': ( + r"`nft add table inet f2b-table`", + r"`nft -- add chain inet f2b-table f2b-chain \{ type filter hook input priority -1 \; \}`", + ), + 'ip4-start': ( + r"`nft add set inet f2b-table addr-set-j-w-nft-ap \{ type ipv4_addr\; \}`", + r"`nft add rule inet f2b-table f2b-chain meta l4proto \{ tcp,udp \} ip saddr @addr-set-j-w-nft-ap reject`", + ), + 'ip6-start': ( + r"`nft add set inet f2b-table addr6-set-j-w-nft-ap \{ type ipv6_addr\; \}`", + r"`nft add rule inet f2b-table f2b-chain meta l4proto \{ tcp,udp \} ip6 saddr @addr6-set-j-w-nft-ap reject`", + ), + 'flush': ( + "`(nft flush set inet f2b-table addr-set-j-w-nft-ap 2> /dev/null) || ", + "`(nft flush set inet f2b-table addr6-set-j-w-nft-ap 2> /dev/null) || ", + ), + 'stop': ( + "`(nft -a list chain inet f2b-table f2b-chain | grep -oP '@addr-set-j-w-nft-ap\s+.*\s+\Khandle\s+(\d+)$') | while read -r hdl; do`", + "`nft delete rule inet f2b-table f2b-chain $hdl; done`", + "`nft delete set inet f2b-table addr-set-j-w-nft-ap`", + "`(nft -a list chain inet f2b-table f2b-chain | grep -oP '@addr6-set-j-w-nft-ap\s+.*\s+\Khandle\s+(\d+)$') | while read -r hdl; do`", + "`nft delete rule inet f2b-table f2b-chain $hdl; done`", + "`nft delete set inet f2b-table addr6-set-j-w-nft-ap`", + ), + 'ip4-check': ( + r"""`nft list chain inet f2b-table f2b-chain | grep -q '@addr-set-j-w-nft-ap[ \t]'`""", + ), + 'ip6-check': ( + r"""`nft list chain inet f2b-table f2b-chain | grep -q '@addr6-set-j-w-nft-ap[ \t]'`""", + ), + 'ip4-ban': ( + r"`nft add element inet f2b-table addr-set-j-w-nft-ap { 192.0.2.1 }`", + ), + 'ip4-unban': ( + r"`nft delete element inet f2b-table addr-set-j-w-nft-ap { 192.0.2.1 }`", + ), + 'ip6-ban': ( + r"`nft add element inet f2b-table addr6-set-j-w-nft-ap { 2001:db8:: }`", + ), + 'ip6-unban': ( + r"`nft delete element inet f2b-table addr6-set-j-w-nft-ap { 2001:db8:: }`", + ), + }), # dummy -- ('j-dummy', 'dummy[name=%(__name__)s, init="==", target="/tmp/fail2ban.dummy"]', { 'ip4': ('family: inet4',), 'ip6': ('family: inet6',), @@ -1905,7 +2000,7 @@ def testCheckStockCommandActions(self): # test ban ip4 : self.pruneLog('# === ban-ipv4 ===') action.ban(aInfos['ipv4']) - if tests.get('ip4-start'): self.assertLogged(*tests['ip4-start'], all=True) + if tests.get('ip4-start'): self.assertLogged(*tests.get('*-start', ())+tests['ip4-start'], all=True) if tests.get('ip6-start'): self.assertNotLogged(*tests['ip6-start'], all=True) self.assertLogged(*tests.get('ip4-check',())+tests['ip4-ban'], all=True) self.assertNotLogged(*tests['ip6'], all=True) @@ -1917,7 +2012,7 @@ def testCheckStockCommandActions(self): # test ban ip6 : self.pruneLog('# === ban ipv6 ===') action.ban(aInfos['ipv6']) - if tests.get('ip6-start'): self.assertLogged(*tests['ip6-start'], all=True) + if tests.get('ip6-start'): self.assertLogged(*tests.get('*-start', ())+tests['ip6-start'], all=True) if tests.get('ip4-start'): self.assertNotLogged(*tests['ip4-start'], all=True) self.assertLogged(*tests.get('ip6-check',())+tests['ip6-ban'], all=True) self.assertNotLogged(*tests['ip4'], all=True)