mirror of https://github.com/fail2ban/fail2ban
Merge pull request #2402 from sebres/maxentries-mem-saving
maxmatches: memory saving optionspull/2247/head
commit
d67e42efa2
|
@ -70,6 +70,8 @@ filter = flt[logtype=short]
|
|||
* `filter.d/traefik-auth.conf`: used to ban hosts, that were failed through traefik
|
||||
|
||||
### Enhancements
|
||||
* introduced new options: `dbmaxmatches` (fail2ban.conf) and `maxmatches` (jail.conf) to contol
|
||||
how many matches per ticket fail2ban can hold in memory and store in database (gh-2402, gh-2118);
|
||||
* fail2ban.conf: introduced new section `[Thread]` and option `stacksize` to configure default size
|
||||
of the stack for threads running in fail2ban (gh-2356), it could be set in `fail2ban.local` to
|
||||
avoid runtime error "can't start new thread" (see gh-969);
|
||||
|
|
3
MANIFEST
3
MANIFEST
|
@ -145,6 +145,7 @@ config/filter.d/sshd.conf
|
|||
config/filter.d/stunnel.conf
|
||||
config/filter.d/suhosin.conf
|
||||
config/filter.d/tine20.conf
|
||||
config/filter.d/traefik-auth.conf
|
||||
config/filter.d/uwimap-auth.conf
|
||||
config/filter.d/vsftpd.conf
|
||||
config/filter.d/webmin-auth.conf
|
||||
|
@ -333,9 +334,11 @@ fail2ban/tests/files/logs/solid-pop3d
|
|||
fail2ban/tests/files/logs/squid
|
||||
fail2ban/tests/files/logs/squirrelmail
|
||||
fail2ban/tests/files/logs/sshd
|
||||
fail2ban/tests/files/logs/sshd-journal
|
||||
fail2ban/tests/files/logs/stunnel
|
||||
fail2ban/tests/files/logs/suhosin
|
||||
fail2ban/tests/files/logs/tine20
|
||||
fail2ban/tests/files/logs/traefik-auth
|
||||
fail2ban/tests/files/logs/uwimap-auth
|
||||
fail2ban/tests/files/logs/vsftpd
|
||||
fail2ban/tests/files/logs/webmin-auth
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
# Changes: in most of the cases you should not modify this
|
||||
# file, but provide customizations in fail2ban.local file, e.g.:
|
||||
#
|
||||
# [Definition]
|
||||
# [DEFAULT]
|
||||
# loglevel = DEBUG
|
||||
#
|
||||
|
||||
[Definition]
|
||||
[DEFAULT]
|
||||
|
||||
# Option: loglevel
|
||||
# Notes.: Set the log level output.
|
||||
|
@ -68,6 +68,15 @@ dbfile = /var/lib/fail2ban/fail2ban.sqlite3
|
|||
# Values: [ SECONDS ] Default: 86400 (24hours)
|
||||
dbpurgeage = 1d
|
||||
|
||||
# Options: dbmaxmatches
|
||||
# Notes.: Number of matches stored in database per ticket (resolvable via
|
||||
# tags <ipmatches>/<ipjailmatches> in actions)
|
||||
# Values: [ INT ] Default: 10
|
||||
dbmaxmatches = 10
|
||||
|
||||
[Definition]
|
||||
|
||||
|
||||
[Thread]
|
||||
|
||||
# Options: stacksize
|
||||
|
|
|
@ -69,6 +69,9 @@ findtime = 10m
|
|||
# "maxretry" is the number of failures before a host get banned.
|
||||
maxretry = 5
|
||||
|
||||
# "maxmatches" is the number of matches stored in ticket (resolvable via tag <matches> in actions).
|
||||
maxmatches = %(maxretry)s
|
||||
|
||||
# "backend" specifies the backend used to get files modification.
|
||||
# Available options are "pyinotify", "gamin", "polling", "systemd" and "auto".
|
||||
# This option can be overridden in each jail as well.
|
||||
|
|
|
@ -54,6 +54,7 @@ class Fail2banReader(ConfigReader):
|
|||
["string", "logtarget", "STDERR"],
|
||||
["string", "syslogsocket", "auto"],
|
||||
["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"],
|
||||
["int", "dbmaxmatches", None],
|
||||
["string", "dbpurgeage", "1d"]]
|
||||
self.__opts = ConfigReader.getOptions(self, "Definition", opts)
|
||||
if updateMainOpt:
|
||||
|
@ -73,7 +74,7 @@ class Fail2banReader(ConfigReader):
|
|||
# Also dbfile should be set before all other database options.
|
||||
# So adding order indices into items, to be stripped after sorting, upon return
|
||||
order = {"thread":0, "syslogsocket":11, "loglevel":12, "logtarget":13,
|
||||
"dbfile":50, "dbpurgeage":51}
|
||||
"dbfile":50, "dbmaxmatches":51, "dbpurgeage":51}
|
||||
stream = list()
|
||||
for opt in self.__opts:
|
||||
if opt in order:
|
||||
|
|
|
@ -93,6 +93,7 @@ class JailReader(ConfigReader):
|
|||
opts = [["bool", "enabled", False],
|
||||
["string", "backend", "auto"],
|
||||
["int", "maxretry", None],
|
||||
["int", "maxmatches", None],
|
||||
["string", "findtime", None],
|
||||
["string", "bantime", None],
|
||||
["string", "usedns", None], # be sure usedns is before all regex(s) in stream
|
||||
|
|
|
@ -72,6 +72,8 @@ protocol = [
|
|||
['', "DATABASE", ""],
|
||||
["set dbfile <FILE>", "set the location of fail2ban persistent datastore. Set to \"None\" to disable"],
|
||||
["get dbfile", "get the location of fail2ban persistent datastore"],
|
||||
["set dbmaxmatches <INT>", "sets the max number of matches stored in database per ticket"],
|
||||
["get dbmaxmatches", "gets the max number of matches stored in database per ticket"],
|
||||
["set dbpurgeage <SECONDS>", "sets the max age in <SECONDS> that history of bans will be kept"],
|
||||
["get dbpurgeage", "gets the max age in seconds that history of bans will be kept"],
|
||||
['', "JAIL CONTROL", ""],
|
||||
|
@ -103,6 +105,7 @@ protocol = [
|
|||
["set <JAIL> banip <IP> ... <IP>", "manually Ban <IP> for <JAIL>"],
|
||||
["set <JAIL> unbanip [--report-absent] <IP> ... <IP>", "manually Unban <IP> in <JAIL>"],
|
||||
["set <JAIL> maxretry <RETRY>", "sets the number of failures <RETRY> before banning the host for <JAIL>"],
|
||||
["set <JAIL> maxmatches <INT>", "sets the max number of matches stored in memory per ticket in <JAIL>"],
|
||||
["set <JAIL> maxlines <LINES>", "sets the number of <LINES> to buffer for regex search for <JAIL>"],
|
||||
["set <JAIL> addaction <ACT>[ <PYTHONFILE> <JSONKWARGS>]", "adds a new action named <ACT> for <JAIL>. Optionally for a Python based action, a <PYTHONFILE> and <JSONKWARGS> can be specified, else will be a Command Action"],
|
||||
["set <JAIL> delaction <ACT>", "removes the action <ACT> from <JAIL>"],
|
||||
|
@ -130,6 +133,7 @@ protocol = [
|
|||
["get <JAIL> datepattern", "gets the patern used to match date/times for <JAIL>"],
|
||||
["get <JAIL> usedns", "gets the usedns setting for <JAIL>"],
|
||||
["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"],
|
||||
["get <JAIL> maxmatches", "gets the max number of matches stored in memory per ticket in <JAIL>"],
|
||||
["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"],
|
||||
["get <JAIL> actions", "gets a list of actions for <JAIL>"],
|
||||
["", "COMMAND ACTION INFORMATION",""],
|
||||
|
|
|
@ -177,7 +177,7 @@ class Fail2BanDb(object):
|
|||
|
||||
|
||||
def __init__(self, filename, purgeAge=24*60*60):
|
||||
self.maxEntries = 50
|
||||
self.maxMatches = 10
|
||||
self._lock = RLock()
|
||||
self._dbFilename = filename
|
||||
self._purgeAge = purgeAge
|
||||
|
@ -541,8 +541,13 @@ class Fail2BanDb(object):
|
|||
#TODO: Implement data parts once arbitrary match keys completed
|
||||
data = ticket.getData()
|
||||
matches = data.get('matches')
|
||||
if matches and len(matches) > self.maxEntries:
|
||||
data['matches'] = matches[-self.maxEntries:]
|
||||
if self.maxMatches:
|
||||
if matches and len(matches) > self.maxMatches:
|
||||
data = data.copy()
|
||||
data['matches'] = matches[-self.maxMatches:]
|
||||
elif matches:
|
||||
data = data.copy()
|
||||
del data['matches']
|
||||
cur.execute(
|
||||
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
|
||||
(jail.name, ip, int(round(ticket.getTime())), data))
|
||||
|
@ -667,7 +672,7 @@ class Fail2BanDb(object):
|
|||
tickdata = {}
|
||||
m = data.get('matches', [])
|
||||
# pre-insert "maxadd" enries (because tickets are ordered desc by time)
|
||||
maxadd = self.maxEntries - len(matches)
|
||||
maxadd = self.maxMatches - len(matches)
|
||||
if maxadd > 0:
|
||||
if len(m) <= maxadd:
|
||||
matches = m + matches
|
||||
|
@ -702,10 +707,12 @@ class Fail2BanDb(object):
|
|||
queryArgs.append(fromtime - forbantime)
|
||||
if ip is None:
|
||||
query += " GROUP BY ip ORDER BY ip, timeofban DESC"
|
||||
else:
|
||||
query += " ORDER BY timeofban DESC LIMIT 1"
|
||||
cur = self._db.cursor()
|
||||
return cur.execute(query, queryArgs)
|
||||
|
||||
def getCurrentBans(self, jail = None, ip = None, forbantime=None, fromtime=None):
|
||||
def getCurrentBans(self, jail = None, ip = None, forbantime=None, fromtime=None, maxmatches=None):
|
||||
tickets = []
|
||||
ticket = None
|
||||
|
||||
|
@ -717,10 +724,20 @@ class Fail2BanDb(object):
|
|||
for banip, timeofban, data in results:
|
||||
# logSys.debug('restore ticket %r, %r, %r', banip, timeofban, data)
|
||||
ticket = FailTicket(banip, timeofban, data=data)
|
||||
# filter matches if expected (current count > as maxmatches specified):
|
||||
if maxmatches is None:
|
||||
maxmatches = self.maxMatches
|
||||
if maxmatches:
|
||||
matches = ticket.getMatches()
|
||||
if matches and len(matches) > maxmatches:
|
||||
ticket.setMatches(matches[-maxmatches:])
|
||||
else:
|
||||
ticket.setMatches(None)
|
||||
# logSys.debug('restored ticket: %r', ticket)
|
||||
if ip is not None: return ticket
|
||||
tickets.append(ticket)
|
||||
|
||||
return tickets if ip is None else ticket
|
||||
return tickets
|
||||
|
||||
@commitandrollback
|
||||
def purge(self, cur):
|
||||
|
|
|
@ -43,7 +43,7 @@ class FailManager:
|
|||
self.__maxRetry = 3
|
||||
self.__maxTime = 600
|
||||
self.__failTotal = 0
|
||||
self.maxEntries = 50
|
||||
self.maxMatches = 50
|
||||
self.__bgSvc = BgService()
|
||||
|
||||
def setFailTotal(self, value):
|
||||
|
@ -87,7 +87,7 @@ class FailManager:
|
|||
attempt = 1
|
||||
else:
|
||||
# will be incremented / extended (be sure we have at least +1 attempt):
|
||||
matches = ticket.getMatches()
|
||||
matches = ticket.getMatches() if self.maxMatches else None
|
||||
attempt = ticket.getAttempt()
|
||||
if attempt <= 0:
|
||||
attempt += 1
|
||||
|
@ -97,10 +97,13 @@ class FailManager:
|
|||
fData.setLastReset(unixTime)
|
||||
fData.setRetry(0)
|
||||
fData.inc(matches, attempt, count)
|
||||
# truncate to maxEntries:
|
||||
matches = fData.getMatches()
|
||||
if len(matches) > self.maxEntries:
|
||||
fData.setMatches(matches[-self.maxEntries:])
|
||||
# truncate to maxMatches:
|
||||
if self.maxMatches:
|
||||
matches = fData.getMatches()
|
||||
if len(matches) > self.maxMatches:
|
||||
fData.setMatches(matches[-self.maxMatches:])
|
||||
else:
|
||||
fData.setMatches(None)
|
||||
except KeyError:
|
||||
# if already FailTicket - add it direct, otherwise create (using copy all ticket data):
|
||||
if isinstance(ticket, FailTicket):
|
||||
|
|
|
@ -213,7 +213,8 @@ class Jail(object):
|
|||
try:
|
||||
if self.database is not None:
|
||||
forbantime = self.actions.getBanTime()
|
||||
for ticket in self.database.getCurrentBans(jail=self, forbantime=forbantime):
|
||||
for ticket in self.database.getCurrentBans(jail=self,
|
||||
forbantime=forbantime, maxmatches=self.filter.failManager.maxMatches):
|
||||
#logSys.debug('restored ticket: %s', ticket)
|
||||
if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True):
|
||||
# mark ticked was restored from database - does not put it again into db:
|
||||
|
|
|
@ -443,6 +443,12 @@ class Server:
|
|||
def getUseDns(self, name):
|
||||
return self.__jails[name].filter.getUseDns()
|
||||
|
||||
def setMaxMatches(self, name, value):
|
||||
self.__jails[name].filter.failManager.maxMatches = value
|
||||
|
||||
def getMaxMatches(self, name):
|
||||
return self.__jails[name].filter.failManager.maxMatches
|
||||
|
||||
def setMaxRetry(self, name, value):
|
||||
self.__jails[name].filter.setMaxRetry(value)
|
||||
|
||||
|
|
|
@ -135,7 +135,13 @@ class Ticket(object):
|
|||
return self._data['failures']
|
||||
|
||||
def setMatches(self, matches):
|
||||
self._data['matches'] = matches or []
|
||||
if matches:
|
||||
self._data['matches'] = matches
|
||||
else:
|
||||
try:
|
||||
del self._data['matches']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def getMatches(self):
|
||||
return [(line if not isinstance(line, (list, tuple)) else "".join(line)) \
|
||||
|
|
|
@ -183,10 +183,19 @@ class Transmitter:
|
|||
else:
|
||||
if self.__quiet: return
|
||||
return db.filename
|
||||
elif name == "dbmaxmatches":
|
||||
db = self.__server.getDatabase()
|
||||
if db is None:
|
||||
logSys.log(logging.MSG, "dbmaxmatches setting was not in effect since no db yet")
|
||||
return None
|
||||
else:
|
||||
db.maxMatches = int(command[1])
|
||||
if self.__quiet: return
|
||||
return db.maxMatches
|
||||
elif name == "dbpurgeage":
|
||||
db = self.__server.getDatabase()
|
||||
if db is None:
|
||||
logSys.warning("dbpurgeage setting was not in effect since no db yet")
|
||||
logSys.log(logging.MSG, "dbpurgeage setting was not in effect since no db yet")
|
||||
return None
|
||||
else:
|
||||
db.purgeage = command[1]
|
||||
|
@ -310,6 +319,11 @@ class Transmitter:
|
|||
self.__server.setLogTimeZone(name, value)
|
||||
if self.__quiet: return
|
||||
return self.__server.getLogTimeZone(name)
|
||||
elif command[1] == "maxmatches":
|
||||
value = command[2]
|
||||
self.__server.setMaxMatches(name, int(value))
|
||||
if self.__quiet: return
|
||||
return self.__server.getMaxMatches(name)
|
||||
elif command[1] == "maxretry":
|
||||
value = command[2]
|
||||
self.__server.setMaxRetry(name, int(value))
|
||||
|
@ -398,6 +412,12 @@ class Transmitter:
|
|||
return None
|
||||
else:
|
||||
return db.filename
|
||||
elif name == "dbmaxmatches":
|
||||
db = self.__server.getDatabase()
|
||||
if db is None:
|
||||
return None
|
||||
else:
|
||||
return db.maxMatches
|
||||
elif name == "dbpurgeage":
|
||||
db = self.__server.getDatabase()
|
||||
if db is None:
|
||||
|
@ -433,6 +453,8 @@ class Transmitter:
|
|||
return self.__server.getDatePattern(name)
|
||||
elif command[1] == "logtimezone":
|
||||
return self.__server.getLogTimeZone(name)
|
||||
elif command[1] == "maxmatches":
|
||||
return self.__server.getMaxMatches(name)
|
||||
elif command[1] == "maxretry":
|
||||
return self.__server.getMaxRetry(name)
|
||||
elif command[1] == "maxlines":
|
||||
|
|
|
@ -881,18 +881,20 @@ class JailsReaderTest(LogCaptureTestCase):
|
|||
self.assertTrue(
|
||||
find_set('syslogsocket') < find_set('loglevel') < find_set('logtarget')
|
||||
)
|
||||
# then dbfile should be before dbpurgeage
|
||||
# then dbfile should be before dbmaxmatches and dbpurgeage
|
||||
self.assertTrue(find_set('dbpurgeage') > find_set('dbfile'))
|
||||
self.assertTrue(find_set('dbmaxmatches') > find_set('dbfile'))
|
||||
|
||||
# and there is logging information left to be passed into the
|
||||
# server
|
||||
self.assertSortedEqual(commands,
|
||||
[['set', 'dbfile',
|
||||
'/var/lib/fail2ban/fail2ban.sqlite3'],
|
||||
['set', 'dbpurgeage', '1d'],
|
||||
['set', 'loglevel', "INFO"],
|
||||
['set', 'logtarget', '/var/log/fail2ban.log'],
|
||||
['set', 'syslogsocket', 'auto']])
|
||||
self.assertSortedEqual(commands,[
|
||||
['set', 'syslogsocket', 'auto'],
|
||||
['set', 'loglevel', "INFO"],
|
||||
['set', 'logtarget', '/var/log/fail2ban.log'],
|
||||
['set', 'dbfile', '/var/lib/fail2ban/fail2ban.sqlite3'],
|
||||
['set', 'dbmaxmatches', 10],
|
||||
['set', 'dbpurgeage', '1d'],
|
||||
])
|
||||
|
||||
# and if we force change configurator's fail2ban's baseDir
|
||||
# there should be an error message (test visually ;) --
|
||||
|
|
|
@ -331,9 +331,9 @@ class DatabaseTest(LogCaptureTestCase):
|
|||
# be returned
|
||||
self.assertEqual(len(self.db.getBans(jail=self.jail,bantime=-1)), 2)
|
||||
|
||||
def testGetBansMerged_MaxEntries(self):
|
||||
def testGetBansMerged_MaxMatches(self):
|
||||
self.testAddJail()
|
||||
maxEntries = 2
|
||||
maxMatches = 2
|
||||
failures = [
|
||||
{"matches": ["abc\n"], "user": set(['test'])},
|
||||
{"matches": ["123\n"], "user": set(['test'])},
|
||||
|
@ -349,29 +349,44 @@ class DatabaseTest(LogCaptureTestCase):
|
|||
ticket.setAttempt(1)
|
||||
self.db.addBan(self.jail, ticket)
|
||||
# should retrieve 2 matches only, but count of all attempts:
|
||||
self.db.maxEntries = maxEntries;
|
||||
self.db.maxMatches = maxMatches;
|
||||
ticket = self.db.getBansMerged("127.0.0.1")
|
||||
self.assertEqual(ticket.getIP(), "127.0.0.1")
|
||||
self.assertEqual(ticket.getAttempt(), len(failures))
|
||||
self.assertEqual(len(ticket.getMatches()), maxEntries)
|
||||
self.assertEqual(ticket.getMatches(), matches2find[-maxEntries:])
|
||||
self.assertEqual(len(ticket.getMatches()), maxMatches)
|
||||
self.assertEqual(ticket.getMatches(), matches2find[-maxMatches:])
|
||||
# add more failures at once:
|
||||
ticket = FailTicket("127.0.0.1", MyTime.time() - 10, matches2find,
|
||||
data={"user": set(['test', 'root'])})
|
||||
ticket.setAttempt(len(failures))
|
||||
self.db.addBan(self.jail, ticket)
|
||||
# should retrieve 2 matches only, but count of all attempts:
|
||||
self.db.maxEntries = maxEntries;
|
||||
ticket = self.db.getBansMerged("127.0.0.1")
|
||||
self.assertEqual(ticket.getAttempt(), 2 * len(failures))
|
||||
self.assertEqual(len(ticket.getMatches()), maxEntries)
|
||||
self.assertEqual(ticket.getMatches(), matches2find[-maxEntries:])
|
||||
self.assertEqual(len(ticket.getMatches()), maxMatches)
|
||||
self.assertEqual(ticket.getMatches(), matches2find[-maxMatches:])
|
||||
# also using getCurrentBans:
|
||||
ticket = self.db.getCurrentBans(self.jail, "127.0.0.1", fromtime=MyTime.time()-100)
|
||||
self.assertTrue(ticket is not None)
|
||||
self.assertEqual(ticket.getAttempt(), len(failures))
|
||||
self.assertEqual(len(ticket.getMatches()), maxEntries)
|
||||
self.assertEqual(ticket.getMatches(), matches2find[-maxEntries:])
|
||||
self.assertEqual(len(ticket.getMatches()), maxMatches)
|
||||
self.assertEqual(ticket.getMatches(), matches2find[-maxMatches:])
|
||||
# maxmatches of jail < dbmaxmatches (so read 1 match and 0 matches):
|
||||
ticket = self.db.getCurrentBans(self.jail, "127.0.0.1", fromtime=MyTime.time()-100,
|
||||
maxmatches=1)
|
||||
self.assertEqual(len(ticket.getMatches()), 1)
|
||||
self.assertEqual(ticket.getMatches(), failures[3]['matches'])
|
||||
ticket = self.db.getCurrentBans(self.jail, "127.0.0.1", fromtime=MyTime.time()-100,
|
||||
maxmatches=0)
|
||||
self.assertEqual(len(ticket.getMatches()), 0)
|
||||
# dbmaxmatches = 0, should retrieve 0 matches by last ban:
|
||||
ticket.setMatches(["1","2","3"])
|
||||
self.db.maxMatches = 0;
|
||||
self.db.addBan(self.jail, ticket)
|
||||
ticket = self.db.getCurrentBans(self.jail, "127.0.0.1", fromtime=MyTime.time()-100)
|
||||
self.assertTrue(ticket is not None)
|
||||
self.assertEqual(ticket.getAttempt(), len(failures))
|
||||
self.assertEqual(len(ticket.getMatches()), 0)
|
||||
|
||||
def testGetBansMerged(self):
|
||||
self.testAddJail()
|
||||
|
|
|
@ -179,6 +179,7 @@ def _start_params(tmp, use_stock=False, use_stock_cfg=None,
|
|||
"pidfile = " + pjoin(tmp, "f2b.pid"),
|
||||
"backend = polling",
|
||||
"dbfile = " + db,
|
||||
"dbmaxmatches = 100",
|
||||
"dbpurgeage = 1d",
|
||||
"",
|
||||
)
|
||||
|
|
|
@ -69,9 +69,9 @@ class AddFailure(unittest.TestCase):
|
|||
self.assertEqual(self.__failManager.getFailTotal(), 0)
|
||||
self.__failManager.setFailTotal(13)
|
||||
|
||||
def testFailManagerAdd_MaxEntries(self):
|
||||
maxEntries = 2
|
||||
self.__failManager.maxEntries = maxEntries
|
||||
def testFailManagerAdd_MaxMatches(self):
|
||||
maxMatches = 2
|
||||
self.__failManager.maxMatches = maxMatches
|
||||
failures = ["abc\n", "123\n", "ABC\n", "1234\n"]
|
||||
# add failures sequential:
|
||||
i = 80
|
||||
|
@ -86,8 +86,8 @@ class AddFailure(unittest.TestCase):
|
|||
ticket = manFailList["127.0.0.1"]
|
||||
# should retrieve 2 matches only, but count of all attempts (4):
|
||||
self.assertEqual(ticket.getAttempt(), len(failures))
|
||||
self.assertEqual(len(ticket.getMatches()), maxEntries)
|
||||
self.assertEqual(ticket.getMatches(), failures[len(failures) - maxEntries:])
|
||||
self.assertEqual(len(ticket.getMatches()), maxMatches)
|
||||
self.assertEqual(ticket.getMatches(), failures[len(failures) - maxMatches:])
|
||||
# add more failures at once:
|
||||
ticket = FailTicket("127.0.0.1", 1000002000 - 10, failures)
|
||||
ticket.setAttempt(len(failures))
|
||||
|
@ -98,8 +98,8 @@ class AddFailure(unittest.TestCase):
|
|||
ticket = manFailList["127.0.0.1"]
|
||||
# should retrieve 2 matches only, but count of all attempts (8):
|
||||
self.assertEqual(ticket.getAttempt(), 2 * len(failures))
|
||||
self.assertEqual(len(ticket.getMatches()), maxEntries)
|
||||
self.assertEqual(ticket.getMatches(), failures[len(failures) - maxEntries:])
|
||||
self.assertEqual(len(ticket.getMatches()), maxMatches)
|
||||
self.assertEqual(ticket.getMatches(), failures[len(failures) - maxMatches:])
|
||||
# add self ticket again:
|
||||
self.__failManager.addFailure(ticket)
|
||||
#
|
||||
|
@ -108,8 +108,16 @@ class AddFailure(unittest.TestCase):
|
|||
ticket = manFailList["127.0.0.1"]
|
||||
# same matches, but +1 attempt (9)
|
||||
self.assertEqual(ticket.getAttempt(), 2 * len(failures) + 1)
|
||||
self.assertEqual(len(ticket.getMatches()), maxEntries)
|
||||
self.assertEqual(ticket.getMatches(), failures[len(failures) - maxEntries:])
|
||||
self.assertEqual(len(ticket.getMatches()), maxMatches)
|
||||
self.assertEqual(ticket.getMatches(), failures[len(failures) - maxMatches:])
|
||||
# no matches by maxMatches == 0 :
|
||||
self.__failManager.maxMatches = 0
|
||||
self.__failManager.addFailure(ticket)
|
||||
manFailList = self.__failManager._FailManager__failList
|
||||
ticket = manFailList["127.0.0.1"]
|
||||
self.assertEqual(len(ticket.getMatches()), 0)
|
||||
# test set matches None to None:
|
||||
ticket.setMatches(None)
|
||||
|
||||
def testFailManagerMaxTime(self):
|
||||
self._addDefItems()
|
||||
|
|
|
@ -197,6 +197,8 @@ class Transmitter(TransmitterBase):
|
|||
self.setGetTest("dbfile", tmpFilename)
|
||||
# the same file name (again no jails / not changed):
|
||||
self.setGetTest("dbfile", tmpFilename)
|
||||
self.setGetTest("dbmaxmatches", "100", 100)
|
||||
self.setGetTestNOK("dbmaxmatches", "LIZARD")
|
||||
self.setGetTest("dbpurgeage", "600", 600)
|
||||
self.setGetTestNOK("dbpurgeage", "LIZARD")
|
||||
# the same file name (again with jails / not changed):
|
||||
|
@ -211,6 +213,12 @@ class Transmitter(TransmitterBase):
|
|||
self.assertEqual(self.transm.proceed(
|
||||
["get", "dbfile"]),
|
||||
(0, None))
|
||||
self.assertEqual(self.transm.proceed(
|
||||
["set", "dbmaxmatches", "100"]),
|
||||
(0, None))
|
||||
self.assertEqual(self.transm.proceed(
|
||||
["get", "dbmaxmatches"]),
|
||||
(0, None))
|
||||
self.assertEqual(self.transm.proceed(
|
||||
["set", "dbpurgeage", "500"]),
|
||||
(0, None))
|
||||
|
@ -374,6 +382,12 @@ class Transmitter(TransmitterBase):
|
|||
self.assertLogged("Ban 192.0.2.2", wait=True)
|
||||
self.assertNotLogged("Ban 192.0.2.1")
|
||||
|
||||
def testJailMaxMatches(self):
|
||||
self.setGetTest("maxmatches", "5", 5, jail=self.jailName)
|
||||
self.setGetTest("maxmatches", "2", 2, jail=self.jailName)
|
||||
self.setGetTest("maxmatches", "-2", -2, jail=self.jailName)
|
||||
self.setGetTestNOK("maxmatches", "Duck", jail=self.jailName)
|
||||
|
||||
def testJailMaxRetry(self):
|
||||
self.setGetTest("maxretry", "5", 5, jail=self.jailName)
|
||||
self.setGetTest("maxretry", "2", 2, jail=self.jailName)
|
||||
|
|
|
@ -168,6 +168,14 @@ persistent datastore. Set to
|
|||
get the location of fail2ban
|
||||
persistent datastore
|
||||
.TP
|
||||
\fBset dbmaxmatches <INT>\fR
|
||||
sets the max number of matches
|
||||
stored in database per ticket
|
||||
.TP
|
||||
\fBget dbmaxmatches\fR
|
||||
gets the max number of matches
|
||||
stored in database per ticket
|
||||
.TP
|
||||
\fBset dbpurgeage <SECONDS>\fR
|
||||
sets the max age in <SECONDS> that
|
||||
history of bans will be kept
|
||||
|
@ -286,6 +294,11 @@ sets the number of failures
|
|||
<RETRY> before banning the host
|
||||
for <JAIL>
|
||||
.TP
|
||||
\fBset <JAIL> maxmatches <INT>\fR
|
||||
sets the max number of matches
|
||||
stored in memory per ticket in
|
||||
<JAIL>
|
||||
.TP
|
||||
\fBset <JAIL> maxlines <LINES>\fR
|
||||
sets the number of <LINES> to
|
||||
buffer for regex search for <JAIL>
|
||||
|
@ -393,6 +406,11 @@ gets the usedns setting for <JAIL>
|
|||
gets the number of failures
|
||||
allowed for <JAIL>
|
||||
.TP
|
||||
\fBget <JAIL> maxmatches\fR
|
||||
gets the max number of matches
|
||||
stored in memory per ticket in
|
||||
<JAIL>
|
||||
.TP
|
||||
\fBget <JAIL> maxlines\fR
|
||||
gets the number of lines to buffer
|
||||
for <JAIL>
|
||||
|
|
|
@ -156,6 +156,11 @@ Database filename. Default: /var/lib/fail2ban/fail2ban.sqlite3
|
|||
.br
|
||||
This defines where the persistent data for fail2ban is stored. This persistent data allows bans to be reinstated and continue reading log files from the last read position when fail2ban is restarted. A value of \fINone\fR disables this feature.
|
||||
.TP
|
||||
.B dbmaxmatches
|
||||
Max number of matches stored in database per ticket. Default: 10
|
||||
.br
|
||||
This option sets the max number of matched log-lines could be stored per ticket in the database. This also affects values resolvable via tags \fB<ipmatches>\fR and \fB<ipjailmatches>\fR in actions.
|
||||
.TP
|
||||
.B dbpurgeage
|
||||
Database purge age in seconds. Default: 86400 (24hours)
|
||||
.br
|
||||
|
@ -276,6 +281,9 @@ regex (Python \fBreg\fRular \fBex\fRpression) to be added to the filter's failre
|
|||
.TP
|
||||
.B ignoreregex
|
||||
regex which, if the log line matches, would cause Fail2Ban not consider that line. This line will be ignored even if it matches a failregex of the jail or any of its filters.
|
||||
.TP
|
||||
.B maxmatches
|
||||
max number of matched log-lines the jail would hold in memory per ticket. By default it is the same value as \fBmaxretry\fR of jail (or default). This option also affects values resolvable via tag \fB<matches>\fR in actions.
|
||||
|
||||
.SS Backends
|
||||
Available options are listed below.
|
||||
|
|
Loading…
Reference in New Issue