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