From ce8cc5d26112f192c6ecef82cf5b0e76a8cd37e5 Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 11 Sep 2025 16:44:06 +0200 Subject: [PATCH 1/5] test illustrating the issue with blocktype="DROP" for IPv6 chain (supplying init parameter to action doesn't overwrite the value in conditional section) --- fail2ban/tests/servertestcase.py | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index e68dd3f5..052bac62 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -1676,6 +1676,55 @@ class ServerConfigReaderTests(LogCaptureTestCase): r"`ipset -exist del f2b-j-w-iptables-ipset-ap6 2001:db8::`", ), }), + # iptables-ipset-allports-drop -- + ('j-w-ipt-ipset-ap-drp', 'iptables-ipset[name=%(__name__)s, type="allports", blocktype="DROP"]', { + 'ip4': (' f2b-j-w-ipt-ipset-ap-drp ',), 'ip6': (' f2b-j-w-ipt-ipset-ap-drp6 ',), + '*-start-stop-check': ( + # iterator over protocol is same for both families: + "`for chain in $(echo 'INPUT' | sed 's/,/ /g'); do for proto in $(echo 'tcp' | sed 's/,/ /g'); do`", + "`done; done`", + ), + 'ip4-start': ( + "`ipset -exist create f2b-j-w-ipt-ipset-ap-drp hash:ip timeout 0 maxelem 65536 `", + "`{ iptables -w -C $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-drp src -j DROP >/dev/null 2>&1; } || " + "{ iptables -w -I $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-drp src -j DROP; }", + ), + 'ip6-start': ( + "`ipset -exist create f2b-j-w-ipt-ipset-ap-drp6 hash:ip timeout 0 maxelem 65536 family inet6`", + "`{ ip6tables -w -C $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-drp6 src -j DROP >/dev/null 2>&1; } || " + "{ ip6tables -w -I $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-drp6 src -j DROP; }", + ), + 'flush': ( + "`ipset flush f2b-j-w-ipt-ipset-ap-drp`", + "`ipset flush f2b-j-w-ipt-ipset-ap-drp6`", + ), + 'stop': ( + "`iptables -w -D $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-drp src -j DROP`", + "`ipset flush f2b-j-w-ipt-ipset-ap-drp`", + "`ipset destroy f2b-j-w-ipt-ipset-ap-drp 2>/dev/null || { sleep 1; ipset destroy f2b-j-w-ipt-ipset-ap-drp; }`", + "`ip6tables -w -D $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-drp6 src -j DROP`", + "`ipset flush f2b-j-w-ipt-ipset-ap-drp6`", + "`ipset destroy f2b-j-w-ipt-ipset-ap-drp6 2>/dev/null || { sleep 1; ipset destroy f2b-j-w-ipt-ipset-ap-drp6; }`", + ), + 'ip4-check': ( + r"""`iptables -w -C $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-drp src -j DROP`""", + ), + 'ip6-check': ( + r"""`ip6tables -w -C $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-drp6 src -j DROP`""", + ), + 'ip4-ban': ( + r"`ipset -exist add f2b-j-w-ipt-ipset-ap-drp 192.0.2.1 timeout 0`", + ), + 'ip4-unban': ( + r"`ipset -exist del f2b-j-w-ipt-ipset-ap-drp 192.0.2.1`", + ), + 'ip6-ban': ( + r"`ipset -exist add f2b-j-w-ipt-ipset-ap-drp6 2001:db8:: timeout 0`", + ), + 'ip6-unban': ( + r"`ipset -exist del f2b-j-w-ipt-ipset-ap-drp6 2001:db8::`", + ), + }), # iptables (oneport) -- ('j-w-iptables', 'iptables[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain=""]', { 'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'), From 3fd3454146fa223340ad3e1589806936cee0469b Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 11 Sep 2025 19:39:06 +0200 Subject: [PATCH 2/5] if parameter supplied to the config, overwrite also conditional init options (from `init?...` section) --- fail2ban/client/configreader.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fail2ban/client/configreader.py b/fail2ban/client/configreader.py index 630ef32f..686b762e 100644 --- a/fail2ban/client/configreader.py +++ b/fail2ban/client/configreader.py @@ -354,6 +354,11 @@ class DefinitionInitConfigReader(ConfigReader): if v is None: v = getopt(opt) self._initOpts['known/'+opt] = v if opt not in self._initOpts: + # overwrite also conditional init options (from init?... section): + cond = SafeConfigParserWithIncludes.CONDITIONAL_RE.match(opt) + if cond: + optc, cond = cond.groups() + v = pOpts.get(optc, v) if v is None: v = getopt(opt) self._initOpts[opt] = v if all and self.has_section("Definition"): From 5beee494a38bbb5b2fb08467f60e10d026baa403 Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 11 Sep 2025 23:11:45 +0200 Subject: [PATCH 3/5] allow to overwrite conditional parameters only direct from jail, for example `banaction = iptables-ipset[blocktype="...", blocktype?family=inet6="..."]` --- fail2ban/helpers.py | 6 ++-- fail2ban/tests/servertestcase.py | 53 +++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/fail2ban/helpers.py b/fail2ban/helpers.py index 220753a7..00aca138 100644 --- a/fail2ban/helpers.py +++ b/fail2ban/helpers.py @@ -323,15 +323,17 @@ def _merge_copy_dicts(x, y): # regex, to extract list of options: OPTION_CRE = re.compile(r"^([^\[]+)(?:\[(.*)\])?\s*$", re.DOTALL) +# regex, matching option name (inclusive conditional option, like n?family=inet6): +OPTION_NAME_CRE = r'[\w\-_\.]+(?:\?[\w\-_\.]+=[\w\-_\.]+)?' # regex, to iterate over single option in option list, syntax: # `action = act[p1="...", p2='...', p3=...]`, where the p3=... not contains `,` or ']' # since v0.10 separator extended with `]\s*[` for support of multiple option groups, syntax # `action = act[p1=...][p2=...]` OPTION_EXTRACT_CRE = re.compile( - r'\s*([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,\]]*))(?:,|\]\s*\[|$|(?P.+))|,?\s*$|(?P.+)', re.DOTALL) + r'\s*('+OPTION_NAME_CRE+r')=(?:"([^"]*)"|\'([^\']*)\'|([^,\]]*))(?:,|\]\s*\[|$|(?P.+))|,?\s*$|(?P.+)', re.DOTALL) # split by new-line considering possible new-lines within options [...]: OPTION_SPLIT_CRE = re.compile( - r'(?:[^\[\s]+(?:\s*\[\s*(?:[\w\-_\.]+=(?:"[^"]*"|\'[^\']*\'|[^,\]]*)\s*(?:,|\]\s*\[)?\s*)*\])?\s*|\S+)(?=\n\s*|\s+|$)', re.DOTALL) + r'(?:[^\[\s]+(?:\s*\[\s*(?:'+OPTION_NAME_CRE+r'=(?:"[^"]*"|\'[^\']*\'|[^,\]]*)\s*(?:,|\]\s*\[)?\s*)*\])?\s*|\S+)(?=\n\s*|\s+|$)', re.DOTALL) def extractOptions(option): match = OPTION_CRE.match(option) diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index 052bac62..040e9c03 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -1676,7 +1676,7 @@ class ServerConfigReaderTests(LogCaptureTestCase): r"`ipset -exist del f2b-j-w-iptables-ipset-ap6 2001:db8::`", ), }), - # iptables-ipset-allports-drop -- + # iptables-ipset (allports + drop) -- ('j-w-ipt-ipset-ap-drp', 'iptables-ipset[name=%(__name__)s, type="allports", blocktype="DROP"]', { 'ip4': (' f2b-j-w-ipt-ipset-ap-drp ',), 'ip6': (' f2b-j-w-ipt-ipset-ap-drp6 ',), '*-start-stop-check': ( @@ -1725,6 +1725,57 @@ class ServerConfigReaderTests(LogCaptureTestCase): r"`ipset -exist del f2b-j-w-ipt-ipset-ap-drp6 2001:db8::`", ), }), + # iptables-ipset (allports + REJECT with icmp?6? host-unreachable) -- + ('j-w-ipt-ipset-ap-rwhu', 'iptables-ipset[name=%(__name__)s, type="allports", ' + +'blocktype="REJECT --reject-with icmp-host-unreachable", ' + +'blocktype?family=inet6="REJECT --reject-with icmp6-host-unreachable"]', { + 'ip4': (' f2b-j-w-ipt-ipset-ap-rwhu ',), 'ip6': (' f2b-j-w-ipt-ipset-ap-rwhu6 ',), + '*-start-stop-check': ( + # iterator over protocol is same for both families: + "`for chain in $(echo 'INPUT' | sed 's/,/ /g'); do for proto in $(echo 'tcp' | sed 's/,/ /g'); do`", + "`done; done`", + ), + 'ip4-start': ( + "`ipset -exist create f2b-j-w-ipt-ipset-ap-rwhu hash:ip timeout 0 maxelem 65536 `", + "`{ iptables -w -C $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-rwhu src -j REJECT --reject-with icmp-host-unreachable >/dev/null 2>&1; } || " + "{ iptables -w -I $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-rwhu src -j REJECT --reject-with icmp-host-unreachable; }", + ), + 'ip6-start': ( + "`ipset -exist create f2b-j-w-ipt-ipset-ap-rwhu6 hash:ip timeout 0 maxelem 65536 family inet6`", + "`{ ip6tables -w -C $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-rwhu6 src -j REJECT --reject-with icmp6-host-unreachable >/dev/null 2>&1; } || " + "{ ip6tables -w -I $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-rwhu6 src -j REJECT --reject-with icmp6-host-unreachable; }", + ), + 'flush': ( + "`ipset flush f2b-j-w-ipt-ipset-ap-rwhu`", + "`ipset flush f2b-j-w-ipt-ipset-ap-rwhu6`", + ), + 'stop': ( + "`iptables -w -D $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-rwhu src -j REJECT --reject-with icmp-host-unreachable`", + "`ipset flush f2b-j-w-ipt-ipset-ap-rwhu`", + "`ipset destroy f2b-j-w-ipt-ipset-ap-rwhu 2>/dev/null || { sleep 1; ipset destroy f2b-j-w-ipt-ipset-ap-rwhu; }`", + "`ip6tables -w -D $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-rwhu6 src -j REJECT --reject-with icmp6-host-unreachable`", + "`ipset flush f2b-j-w-ipt-ipset-ap-rwhu6`", + "`ipset destroy f2b-j-w-ipt-ipset-ap-rwhu6 2>/dev/null || { sleep 1; ipset destroy f2b-j-w-ipt-ipset-ap-rwhu6; }`", + ), + 'ip4-check': ( + r"""`iptables -w -C $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-rwhu src -j REJECT --reject-with icmp-host-unreachable`""", + ), + 'ip6-check': ( + r"""`ip6tables -w -C $chain -p $proto -m set --match-set f2b-j-w-ipt-ipset-ap-rwhu6 src -j REJECT --reject-with icmp6-host-unreachable`""", + ), + 'ip4-ban': ( + r"`ipset -exist add f2b-j-w-ipt-ipset-ap-rwhu 192.0.2.1 timeout 0`", + ), + 'ip4-unban': ( + r"`ipset -exist del f2b-j-w-ipt-ipset-ap-rwhu 192.0.2.1`", + ), + 'ip6-ban': ( + r"`ipset -exist add f2b-j-w-ipt-ipset-ap-rwhu6 2001:db8:: timeout 0`", + ), + 'ip6-unban': ( + r"`ipset -exist del f2b-j-w-ipt-ipset-ap-rwhu6 2001:db8::`", + ), + }), # iptables (oneport) -- ('j-w-iptables', 'iptables[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain=""]', { 'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'), From 070d49e09cfbc0090310151989b5e532466f2cc1 Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Wed, 24 Sep 2025 18:18:38 +0200 Subject: [PATCH 4/5] `man/jail.conf.5` - update docu --- man/jail.conf.5 | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/man/jail.conf.5 b/man/jail.conf.5 index 4f65f30a..6a6ac849 100644 --- a/man/jail.conf.5 +++ b/man/jail.conf.5 @@ -245,8 +245,23 @@ Arguments can be passed to actions to override the default values from the [Init [name=value,name2=value,name3="values,values"] .RE -Values can also be quoted (required when value includes a ","). More that one action can be specified (in separate lines). +Values can also be quoted (required when value includes a "," or space). More that one action can be specified (in separate lines). +.br +The action specific arguments can also affect conditional parameters, so for instance to submit different values to different chains +firstly pass the argument affecting all chains, e.g. \fIblocktype\fR, then for IPv6 chain, e. g. \fIblocktype?family=inet6\fR. +Examples: +.RS +.nf + +# pass blocktype to DROP for all chains: +banaction_allports = iptables-ipset[type=allports, blocktype=DROP] +# pass different blocktype for IPv4 and IPv6 chains: +banaction = iptables-ipset[type=multiport, blocktype="REJECT --reject-with icmp-host-unreachable", blocktype?family=inet6="REJECT --reject-with icmp6-host-unreachable"] + +.fi .RE +.RE + .TP .B ignoreself boolean value (default true) indicates the banning of own IP addresses should be prevented From d0b94c147edfa20d22b9283c59d6645f845194f1 Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Wed, 24 Sep 2025 18:22:06 +0200 Subject: [PATCH 5/5] Update ChangeLog --- ChangeLog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog b/ChangeLog index 3879d226..4c4cce23 100644 --- a/ChangeLog +++ b/ChangeLog @@ -24,6 +24,10 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition in systemd module (see https://github.com/systemd/python-systemd/issues/143) * fixes `systemd` causing "too many open files" error for a lot of journal files and large amount of systemd jails (see new parameter `rotated` below, gh-3391); +* passing of arguments from jails to action or filter will affect conditional section too (gh-4069), + e. g. setting `blocktype="DROP"` via jail for action would now apply for IPv4 and IPv6 chains, + to submit different `blocktype` for IPv4 and IPv6 from jail, one can pass them like in this example: + `banaction = iptables-ipset[blocktype="...", blocktype?family=inet6="..."]` * `jail.conf`: - default banactions need to be specified in `paths-*.conf` (maintainer level) now - since stock fail2ban includes `paths-debian.conf` by default, banactions are `nftables`