Merge branch 'fail2ban:master' into crowdsec

pull/3407/head
ne20002 2024-07-07 19:43:31 +02:00 committed by GitHub
commit e78b56cf83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 99 additions and 52 deletions

View File

@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy: strategy:
matrix: matrix:
python-version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12', '3.13.0-alpha.6', pypy3.10] python-version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12', '3.13.0-beta.3', pypy3.10]
fail-fast: false fail-fast: false
# Steps represent a sequence of tasks that will be executed as part of the job # Steps represent a sequence of tasks that will be executed as part of the job
steps: steps:

View File

@ -18,8 +18,16 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition
* `paths-debian.conf`: * `paths-debian.conf`:
- default banactions are `nftables` - default banactions are `nftables`
- sshd backend switched to `systemd` (gh-3292) - sshd backend switched to `systemd` (gh-3292)
* `action.d/firewallcmd-ipset.conf`:
- rename `ipsettype` to `ipsetbackend` (gh-2620), parameter `ipsettype` will be used now to the real set type (gh-3760)
* `filter.d/apache-overflows.conf` - consider AH10244: invalid URI path (gh-3778)
* `filter.d/recidive.conf` - restore possibility to set jail name in the filter, _jailname is positive now (gh-3769)
* `filter.d/sshd.conf` - adapted to conform possible new daemon name sshd-session, since OpenSSH 9.8
several log messages will be tagged with as originating from a process named "sshd-session" rather than "sshd" (gh-3782)
### New Features and Enhancements ### New Features and Enhancements
* `action.d/*-ipset.conf`:
- parameter `ipsettype` to set type of ipset, e. g. hash:ip, hash:net, etc (gh-3760)
ver. 1.1.0 (2024/04/25) - object-found--norad-59479-cospar-2024-069a--altitude-36267km ver. 1.1.0 (2024/04/25) - object-found--norad-59479-cospar-2024-069a--altitude-36267km

View File

@ -18,24 +18,24 @@ before = firewallcmd-common.conf
[Definition] [Definition]
actionstart = <ipstype_<ipsettype>/actionstart> actionstart = <ipsbackend_<ipsetbackend>/actionstart>
firewall-cmd --direct --add-rule <family> filter <chain> 0 <actiontype> -m set --match-set <ipmset> src -j <blocktype> firewall-cmd --direct --add-rule <family> filter <chain> 0 <actiontype> -m set --match-set <ipmset> src -j <blocktype>
actionflush = <ipstype_<ipsettype>/actionflush> actionflush = <ipsbackend_<ipsetbackend>/actionflush>
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 <actiontype> -m set --match-set <ipmset> src -j <blocktype> actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 <actiontype> -m set --match-set <ipmset> src -j <blocktype>
<actionflush> <actionflush>
<ipstype_<ipsettype>/actionstop> <ipsbackend_<ipsetbackend>/actionstop>
actionban = <ipstype_<ipsettype>/actionban> actionban = <ipsbackend_<ipsetbackend>/actionban>
# actionprolong = %(actionban)s # actionprolong = %(actionban)s
actionunban = <ipstype_<ipsettype>/actionunban> actionunban = <ipsbackend_<ipsetbackend>/actionunban>
[ipstype_ipset] [ipsbackend_ipset]
actionstart = ipset -exist create <ipmset> hash:ip timeout <default-ipsettime> maxelem <maxelem> <familyopt> actionstart = ipset -exist create <ipmset> <ipsettype> timeout <default-ipsettime> maxelem <maxelem> <familyopt>
actionflush = ipset flush <ipmset> actionflush = ipset flush <ipmset>
@ -45,9 +45,9 @@ actionban = ipset -exist add <ipmset> <ip> timeout <ipsettime>
actionunban = ipset -exist del <ipmset> <ip> actionunban = ipset -exist del <ipmset> <ip>
[ipstype_firewalld] [ipsbackend_firewalld]
actionstart = firewall-cmd --direct --new-ipset=<ipmset> --type=hash:ip --option=timeout=<default-ipsettime> --option=maxelem=<maxelem> <firewalld_familyopt> actionstart = firewall-cmd --direct --new-ipset=<ipmset> --type=<ipsettype> --option=timeout=<default-ipsettime> --option=maxelem=<maxelem> <firewalld_familyopt>
# TODO: there doesn't seem to be an explicit way to invoke the ipset flush function using firewall-cmd # TODO: there doesn't seem to be an explicit way to invoke the ipset flush function using firewall-cmd
actionflush = actionflush =
@ -60,6 +60,11 @@ actionunban = firewall-cmd --ipset=<ipmset> --remove-entry=<ip>
[Init] [Init]
# Option: ipsettype
# Notes: specifies type of set, see `man --pager='less -p "^SET TYPES"' ipset` for details
# Values: hash:ip, hash:net, etc... Default: hash:ip
ipsettype = hash:ip
# Option: chain # Option: chain
# Notes specifies the iptables chain to which the fail2ban rules should be # Notes specifies the iptables chain to which the fail2ban rules should be
# added # added
@ -87,11 +92,11 @@ maxelem = 65536
# banaction = %(known/banaction)s[ipsettime='<timeout-bantime>'] # banaction = %(known/banaction)s[ipsettime='<timeout-bantime>']
timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0) timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)
# Option: ipsettype # Option: ipsetbackend
# Notes.: defines type of ipset used for match-set (firewalld or ipset) # Notes.: defines the backend of ipset used for match-set (firewalld or ipset)
# Values: firewalld or ipset # Values: firewalld or ipset
# Default: ipset # Default: ipset
ipsettype = ipset ipsetbackend = ipset
# Option: actiontype # Option: actiontype
# Notes.: defines additions to the blocking rule # Notes.: defines additions to the blocking rule

View File

@ -24,7 +24,7 @@ before = iptables.conf
# Notes.: command executed on demand at the first ban (or at the start of Fail2Ban if actionstart_on_demand is set to false). # 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 # Values: CMD
# #
actionstart = ipset -exist create <ipmset> hash:ip timeout <default-ipsettime> maxelem <maxelem> <familyopt> actionstart = ipset -exist create <ipmset> <ipsettype> timeout <default-ipsettime> maxelem <maxelem> <familyopt>
<_ipt_add_rules> <_ipt_add_rules>
# Option: actionflush # Option: actionflush
@ -66,6 +66,11 @@ rule-jump = -m set --match-set <ipmset> src -j <blocktype>
[Init] [Init]
# Option: ipsettype
# Notes: specifies type of set, see `man --pager='less -p "^SET TYPES"' ipset` for details
# Values: hash:ip, hash:net, etc... Default: hash:ip
ipsettype = hash:ip
# Option: default-ipsettime # Option: default-ipsettime
# Notes: specifies default timeout in seconds (handled default ipset timeout only) # Notes: specifies default timeout in seconds (handled default ipset timeout only)
# Values: [ NUM ] Default: 0 (no timeout, managed by fail2ban by unban) # Values: [ NUM ] Default: 0 (no timeout, managed by fail2ban by unban)

View File

@ -51,7 +51,7 @@
# Values: CMD # Values: CMD
# #
actionstart = if ! ipset -quiet -name list f2b-<name> >/dev/null; actionstart = if ! ipset -quiet -name list f2b-<name> >/dev/null;
then ipset -quiet -exist create f2b-<name> hash:ip timeout <default-ipsettime> maxelem <maxelem>; then ipset -quiet -exist create f2b-<name> <ipsettype> timeout <default-ipsettime> maxelem <maxelem>;
fi fi
# Option: actionstop # Option: actionstop
@ -94,6 +94,11 @@ timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)
[Init] [Init]
# Option: ipsettype
# Notes: specifies type of set, see `man --pager='less -p "^SET TYPES"' ipset` for details
# Values: hash:ip, hash:net, etc... Default: hash:ip
ipsettype = hash:ip
# Option: maxelem # Option: maxelem
# Notes: maximal number of elements which can be stored in the ipset # Notes: maximal number of elements which can be stored in the ipset
# You may want to increase this for long-duration/high-volume jails # You may want to increase this for long-duration/high-volume jails

View File

@ -8,7 +8,7 @@ before = apache-common.conf
[Definition] [Definition]
failregex = ^%(_apache_error_client)s (?:(?:AH001[23][456]: )?Invalid (method|URI) in request\b|(?:AH00565: )?request failed: URI too long \(longer than \d+\)|request failed: erroneous characters after protocol string:|(?:AH00566: )?request failed: invalid characters in URI\b) failregex = ^%(_apache_error_client)s (?:(?:AH(?:001[23][456]|10244): )?[Ii]nvalid (method|URI)\b|(?:AH00565: )?request failed: URI too long \(longer than \d+\)|request failed: erroneous characters after protocol string:|(?:AH00566: )?request failed: invalid characters in URI\b)
ignoreregex = ignoreregex =

View File

@ -24,14 +24,15 @@ before = common.conf
_daemon = (?:fail2ban(?:-server|\.actions)\s*) _daemon = (?:fail2ban(?:-server|\.actions)\s*)
# The name of the jail that this filter is used for. In jail.conf, name the jail using # The name of the jail that this filter is used for. In jail.conf, name the jail using
# this filter 'recidive', or supply another name with `filter = recidive[_jailname="jail"]` # this filter 'recidive', or supply another name with `filter = recidive[_jailname="jail"]`,
_jailname = recidive # default all jails excepting recidive
_jailname = (?!recidive\])[^\]]*
failregex = ^%(__prefix_line)s(?:\s*fail2ban\.actions\s*%(__pid_re)s?:\s+)?NOTICE\s+\[(?!%(_jailname)s\])(?:.*)\]\s+Ban\s+<HOST>\s*$ failregex = ^%(__prefix_line)s(?:\s*fail2ban\.actions\s*%(__pid_re)s?:\s+)?NOTICE\s+\[<_jailname>\]\s+Ban\s+<HOST>
[lt_short] [lt_short]
_daemon = (?:fail2ban(?:-server|\.actions)?\s*) _daemon = (?:fail2ban(?:-server|\.actions)?\s*)
failregex = ^%(__prefix_line)s(?:\s*fail2ban(?:\.actions)?\s*%(__pid_re)s?:\s+)?(?:NOTICE\s+)?\[(?!%(_jailname)s\])(?:.*)\]\s+Ban\s+<HOST>\s*$ failregex = ^%(__prefix_line)s(?:\s*fail2ban(?:\.actions)?\s*%(__pid_re)s?:\s+)?(?:NOTICE\s+)?\[<_jailname>\]\s+Ban\s+<HOST>
[lt_journal] [lt_journal]
_daemon = <lt_short/_daemon> _daemon = <lt_short/_daemon>

View File

@ -16,7 +16,7 @@ before = common.conf
[DEFAULT] [DEFAULT]
_daemon = sshd _daemon = sshd(?:-session)?
# optional prefix (logged from several ssh versions) like "error: ", "error: PAM: " or "fatal: " # optional prefix (logged from several ssh versions) like "error: ", "error: PAM: " or "fatal: "
__pref = (?:(?:error|fatal): (?:PAM: )?)? __pref = (?:(?:error|fatal): (?:PAM: )?)?
@ -126,7 +126,7 @@ ignoreregex =
maxlines = 1 maxlines = 1
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd
# DEV Notes: # DEV Notes:
# #

View File

@ -21,8 +21,10 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2013- Yaroslav Halchenko" __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2013- Yaroslav Halchenko"
__license__ = "GPL" __license__ = "GPL"
import sys
from ..exceptions import UnknownJailException, DuplicateJailException from ..exceptions import UnknownJailException, DuplicateJailException
from ..helpers import getLogger, logging from ..helpers import getLogger, logging, PREFER_ENC
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger(__name__) logSys = getLogger(__name__)
@ -36,6 +38,11 @@ logSys = getLogger(__name__)
class Beautifier: class Beautifier:
stdoutEnc = PREFER_ENC
if sys.stdout and sys.stdout.encoding is not None:
stdoutEnc = sys.stdout.encoding
encUtf = 1 if stdoutEnc.lower() == 'utf-8' else 0
def __init__(self, cmd = None): def __init__(self, cmd = None):
self.__inputCmd = cmd self.__inputCmd = cmd
@ -104,7 +111,11 @@ class Beautifier:
jail_stat(j, " " if i == len(jstat) else " | ") jail_stat(j, " " if i == len(jstat) else " | ")
msg = "\n".join(msg) msg = "\n".join(msg)
elif inC[0:1] == ['stats'] or inC[0:1] == ['statistics']: elif inC[0:1] == ['stats'] or inC[0:1] == ['statistics']:
def _statstable(response): chrTable = [
['|', '-', '|', 'x', 'x', '-', '|', '-'], ## ascii
["\u2551", "\u2550", "\u255F", "\u256B", "\u256C", "\u2569", "\u2502", "\u2500"] ## utf-8
];
def _statstable(response, ct):
tophead = ["Jail", "Backend", "Filter", "Actions"] tophead = ["Jail", "Backend", "Filter", "Actions"]
headers = ["", "", "cur", "tot", "cur", "tot"] headers = ["", "", "cur", "tot", "cur", "tot"]
minlens = [8, 8, 3, 3, 3, 3] minlens = [8, 8, 3, 3, 3, 3]
@ -120,29 +131,31 @@ class Beautifier:
f = "%%%ds" if ralign[i] else "%%-%ds" f = "%%%ds" if ralign[i] else "%%-%ds"
rfmt.append(f % lens[i]) rfmt.append(f % lens[i])
hfmt.append(f % lens[i]) hfmt.append(f % lens[i])
rfmt = [rfmt[0], rfmt[1], "%s \u2502 %s" % (rfmt[2], rfmt[3]), "%s \u2502 %s" % (rfmt[4], rfmt[5])] rfmt = [rfmt[0], rfmt[1], "%s %s %s" % (rfmt[2], ct[6], rfmt[3]), "%s %s %s" % (rfmt[4], ct[6], rfmt[5])]
hfmt = [hfmt[0], hfmt[1], "%s \u2502 %s" % (hfmt[2], hfmt[3]), "%s \u2502 %s" % (hfmt[4], hfmt[5])] hfmt = [hfmt[0], hfmt[1], "%s %s %s" % (hfmt[2], ct[6], hfmt[3]), "%s %s %s" % (hfmt[4], ct[6], hfmt[5])]
tlens = [lens[0], lens[1], 3 + lens[2] + lens[3], 3 + lens[4] + lens[5]] tlens = [lens[0], lens[1], 3 + lens[2] + lens[3], 3 + lens[4] + lens[5]]
tfmt = [hfmt[0], hfmt[1], "%%-%ds" % (tlens[2],), "%%-%ds" % (tlens[3],)] tfmt = [hfmt[0], hfmt[1], "%%-%ds" % (tlens[2],), "%%-%ds" % (tlens[3],)]
tsep = tfmt[0:2] tsep = tfmt[0:2]
rfmt = " \u2551 ".join(rfmt) rfmt = (" "+ct[0]+" ").join(rfmt)
hfmt = " \u2551 ".join(hfmt) hfmt = (" "+ct[0]+" ").join(hfmt)
tfmt = " \u2551 ".join(tfmt) tfmt = (" "+ct[0]+" ").join(tfmt)
tsep = " \u2551 ".join(tsep) tsep = (" "+ct[0]+" ").join(tsep)
separator = ((tsep % tuple(tophead[0:2])) + " \u255F\u2500" + separator = ((tsep % tuple(tophead[0:2])) + " "+ct[2]+ct[7] +
("\u2500\u256B\u2500".join(['\u2500' * n for n in tlens[2:]])) + '\u2500') ((ct[7]+ct[3]+ct[7]).join([ct[7] * n for n in tlens[2:]])) + ct[7])
ret = [] ret = []
ret.append(tfmt % tuple(["", ""]+tophead[2:])) ret.append(" "+tfmt % tuple(["", ""]+tophead[2:]))
ret.append(separator) ret.append(" "+separator)
ret.append(hfmt % tuple(headers)) ret.append(" "+hfmt % tuple(headers))
separator = "\u2550\u256C\u2550".join(['\u2550' * n for n in tlens]) + '\u2550' separator = (ct[1]+ct[4]+ct[1]).join([ct[1] * n for n in tlens]) + ct[1]
ret.append(separator) ret.append(ct[1]+separator)
for row in rows: for row in rows:
ret.append(rfmt % tuple(row)) ret.append(" "+rfmt % tuple(row))
separator = "\u2550\u2569\u2550".join(['\u2550' * n for n in tlens]) + '\u2550' separator = (ct[1]+ct[5]+ct[1]).join([ct[1] * n for n in tlens]) + ct[1]
ret.append(separator) ret.append(ct[1]+separator)
return ret return ret
msg = "\n".join(_statstable(response)) if not response:
return "No jails found."
msg = "\n".join(_statstable(response, chrTable[self.encUtf]))
elif len(inC) < 2: elif len(inC) < 2:
pass # to few cmd args for below pass # to few cmd args for below
elif inC[1] == "syslogsocket": elif inC[1] == "syslogsocket":

View File

@ -34,6 +34,7 @@ class BeautifierTest(unittest.TestCase):
""" Call before every test case """ """ Call before every test case """
super(BeautifierTest, self).setUp() super(BeautifierTest, self).setUp()
self.b = Beautifier() self.b = Beautifier()
self.b.encUtf = 0; ## we prefer ascii in test suite (see #3750)
def tearDown(self): def tearDown(self):
""" Call after every test case """ """ Call after every test case """
@ -170,22 +171,25 @@ class BeautifierTest(unittest.TestCase):
def testStatusStats(self): def testStatusStats(self):
self.b.setInputCmd(["stats"]) self.b.setInputCmd(["stats"])
## no jails:
self.assertEqual(self.b.beautify({}), "No jails found.")
## 3 jails:
response = { response = {
"ssh": ["systemd", (3, 6), (12, 24)], "ssh": ["systemd", (3, 6), (12, 24)],
"exim4": ["pyinotify", (6, 12), (20, 20)], "exim4": ["pyinotify", (6, 12), (20, 20)],
"jail-with-long-name": ["polling", (0, 0), (0, 0)] "jail-with-long-name": ["polling", (0, 0), (0, 0)]
} }
output = ("" output = (""
+ " ? ? Filter ? Actions \n" + " | | Filter | Actions \n"
+ "Jail ? Backend ????????????????????????\n" + " Jail | Backend |-----------x-----------\n"
+ " ? ? cur ? tot ? cur ? tot\n" + " | | cur | tot | cur | tot\n"
+ "????????????????????????????????????????????????????????\n" + "---------------------x-----------x-----------x-----------\n"
+ "ssh ? systemd ? 3 ? 6 ? 12 ? 24\n" + " ssh | systemd | 3 | 6 | 12 | 24\n"
+ "exim4 ? pyinotify ? 6 ? 12 ? 20 ? 20\n" + " exim4 | pyinotify | 6 | 12 | 20 | 20\n"
+ "jail-with-long-name ? polling ? 0 ? 0 ? 0 ? 0\n" + " jail-with-long-name | polling | 0 | 0 | 0 | 0\n"
+ "????????????????????????????????????????????????????????" + "---------------------------------------------------------"
) )
response = self.b.beautify(response).encode('ascii', 'replace').decode('ascii') response = self.b.beautify(response)
self.assertEqual(response, output) self.assertEqual(response, output)

View File

@ -9,7 +9,7 @@ before = ../../../../config/filter.d/common.conf
[DEFAULT] [DEFAULT]
_daemon = sshd _daemon = sshd(?:-session)?
# optional prefix (logged from several ssh versions) like "error: ", "error: PAM: " or "fatal: " # optional prefix (logged from several ssh versions) like "error: ", "error: PAM: " or "fatal: "
__pref = (?:(?:error|fatal): (?:PAM: )?)? __pref = (?:(?:error|fatal): (?:PAM: )?)?

View File

@ -25,3 +25,6 @@
# https://issues.apache.org/bugzilla/show_bug.cgi?id=46123 # https://issues.apache.org/bugzilla/show_bug.cgi?id=46123
# failJSON: { "time": "2008-10-29T11:55:14", "match": true , "host": "127.0.0.1" } # failJSON: { "time": "2008-10-29T11:55:14", "match": true , "host": "127.0.0.1" }
[Wed Oct 29 11:55:14 2008] [error] [client 127.0.0.1] Invalid method in request \x16\x03\x01 - possible attempt to establish SSL connection when the server isn't expecting it [Wed Oct 29 11:55:14 2008] [error] [client 127.0.0.1] Invalid method in request \x16\x03\x01 - possible attempt to establish SSL connection when the server isn't expecting it
# failJSON: { "time": "2024-06-26T05:20:26", "match": true , "host": "192.0.2.39", "desc": "AH10244: invalid URI path, gh-3778" }
[Wed Jun 26 05:20:26.182799 2024] [core:error] [pid 2928] [client 192.0.2.39:37924] AH10244: invalid URI path (/cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/bin/sh)

View File

@ -20,6 +20,9 @@ Feb 25 14:34:10 belka sshd[31603]: Failed password for invalid user ROOT from aa
# failJSON: { "time": "2005-02-25T14:34:11", "match": true , "host": "aaaa:bbbb:cccc:1234::1:1" } # failJSON: { "time": "2005-02-25T14:34:11", "match": true , "host": "aaaa:bbbb:cccc:1234::1:1" }
Feb 25 14:34:11 belka sshd[31603]: Failed password for invalid user ROOT from aaaa:bbbb:cccc:1234::1:1 Feb 25 14:34:11 belka sshd[31603]: Failed password for invalid user ROOT from aaaa:bbbb:cccc:1234::1:1
# failJSON: { "time": "2005-07-03T14:59:17", "match": true , "host": "192.0.2.1", "desc": "new log with session in daemon prefix, gh-3782" }
Jul 3 14:59:17 host sshd-session[1571]: Failed password for root from 192.0.2.1 port 56502 ssh2
#3 #3
# failJSON: { "time": "2005-01-05T01:31:41", "match": true , "host": "1.2.3.4" } # failJSON: { "time": "2005-01-05T01:31:41", "match": true , "host": "1.2.3.4" }
Jan 5 01:31:41 www sshd[1643]: ROOT LOGIN REFUSED FROM 1.2.3.4 Jan 5 01:31:41 www sshd[1643]: ROOT LOGIN REFUSED FROM 1.2.3.4

View File

@ -120,7 +120,7 @@ class SetupTest(unittest.TestCase):
# suppress stdout (and stderr) if not heavydebug # suppress stdout (and stderr) if not heavydebug
supdbgout = ' >/dev/null' if unittest.F2B.log_level >= logging.DEBUG else '' # HEAVYDEBUG supdbgout = ' >/dev/null' if unittest.F2B.log_level >= logging.DEBUG else '' # HEAVYDEBUG
try: try:
self.assertEqual(os.system("%s %s install --root=%s%s" self.assertEqual(os.system("%s -W 'ignore:setup.py install is deprecated' %s install --root=%s%s"
% (sys.executable, self.setup, tmp, supdbgout)), 0) % (sys.executable, self.setup, tmp, supdbgout)), 0)
def strippath(l): def strippath(l):