diff --git a/config/action.d/firewallcmd-ipset.conf b/config/action.d/firewallcmd-ipset.conf index 69447627..ecbb3bef 100644 --- a/config/action.d/firewallcmd-ipset.conf +++ b/config/action.d/firewallcmd-ipset.conf @@ -18,7 +18,7 @@ before = firewallcmd-common.conf [Definition] -actionstart = ipset create hash:ip timeout +actionstart = ipset create hash:ip firewall-cmd --direct --add-rule filter 0 -p -m multiport --dports -m set --match-set src -j actionstop = firewall-cmd --direct --remove-rule filter 0 -p -m multiport --dports -m set --match-set src -j @@ -27,6 +27,8 @@ actionstop = firewall-cmd --direct --remove-rule filter 0 -p

timeout -exist +actionprolong = %(actionban)s + actionunban = ipset del -exist [Init] @@ -38,12 +40,6 @@ actionunban = ipset del -exist # chain = INPUT_direct -# Option: bantime -# Notes: specifies the bantime in seconds (handled internally rather than by fail2ban) -# Values: [ NUM ] Default: 600 - -bantime = 600 - ipmset = f2b- [Init?family=inet6] diff --git a/config/action.d/iptables-ipset-proto6-allports.conf b/config/action.d/iptables-ipset-proto6-allports.conf index b761ad8c..a0ede56e 100644 --- a/config/action.d/iptables-ipset-proto6-allports.conf +++ b/config/action.d/iptables-ipset-proto6-allports.conf @@ -26,7 +26,7 @@ before = iptables-common.conf # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # -actionstart = ipset create hash:ip timeout +actionstart = ipset create hash:ip -I -m set --match-set src -j # Option: actionflush @@ -51,6 +51,8 @@ actionstop = -D -m set --match-set src -j timeout -exist +actionprolong = %(actionban)s + # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the # command is executed with Fail2Ban user rights. @@ -61,12 +63,6 @@ actionunban = ipset del -exist [Init] -# Option: bantime -# Notes: specifies the bantime in seconds (handled internally rather than by fail2ban) -# Values: [ NUM ] Default: 600 -# -bantime = 600 - ipmset = f2b- familyopt = diff --git a/config/action.d/iptables-ipset-proto6.conf b/config/action.d/iptables-ipset-proto6.conf index e337eedf..b13eb711 100644 --- a/config/action.d/iptables-ipset-proto6.conf +++ b/config/action.d/iptables-ipset-proto6.conf @@ -26,7 +26,7 @@ before = iptables-common.conf # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # -actionstart = ipset create hash:ip timeout +actionstart = ipset create hash:ip -I -p -m multiport --dports -m set --match-set src -j # Option: actionflush @@ -51,6 +51,8 @@ actionstop = -D -p -m multiport --dports -m # actionban = ipset add timeout -exist +actionprolong = %(actionban)s + # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the # command is executed with Fail2Ban user rights. @@ -61,12 +63,6 @@ actionunban = ipset del -exist [Init] -# Option: bantime -# Notes: specifies the bantime in seconds (handled internally rather than by fail2ban) -# Values: [ NUM ] Default: 600 -# -bantime = 600 - ipmset = f2b- familyopt = diff --git a/config/action.d/osx-afctl.conf b/config/action.d/osx-afctl.conf index a319fc6b..a75e5723 100644 --- a/config/action.d/osx-afctl.conf +++ b/config/action.d/osx-afctl.conf @@ -12,5 +12,5 @@ actioncheck = actionban = /usr/libexec/afctl -a -t actionunban = /usr/libexec/afctl -r -[Init] -bantime = 2880 +actionprolong = %(actionunban)s && %(actionban)s + diff --git a/config/action.d/shorewall-ipset-proto6.conf b/config/action.d/shorewall-ipset-proto6.conf index 1ebcfb01..8d80460f 100644 --- a/config/action.d/shorewall-ipset-proto6.conf +++ b/config/action.d/shorewall-ipset-proto6.conf @@ -51,7 +51,7 @@ # Values: CMD # actionstart = if ! ipset -quiet -name list f2b- >/dev/null; - then ipset -quiet -exist create f2b- hash:ip timeout ; + then ipset -quiet -exist create f2b- hash:ip; fi # Option: actionstop @@ -68,6 +68,8 @@ actionstop = ipset flush f2b- # actionban = ipset add f2b- timeout -exist +actionprolong = %(actionban)s + # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the # command is executed with Fail2Ban user rights. @@ -76,10 +78,3 @@ actionban = ipset add f2b- timeout -exist # actionunban = ipset del f2b- -exist -[Init] - -# Option: bantime -# Notes: specifies the bantime in seconds (handled internally rather than by fail2ban) -# Values: [ NUM ] Default: 600 -# -bantime = 600 diff --git a/config/jail.conf b/config/jail.conf index df354bb3..4daacf63 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -202,22 +202,22 @@ banaction = iptables-multiport banaction_allports = iptables-allports # The simplest action to take: ban only -action_ = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] +action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] # ban & send an e-mail with whois report to the destemail. -action_mw = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] +action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] %(mta)s-whois[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"] # ban & send an e-mail with whois report and relevant log lines # to the destemail. -action_mwl = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] +action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] %(mta)s-whois-lines[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"] # See the IMPORTANT note in action.d/xarf-login-attack for when to use this action # # ban & send a xarf e-mail to abuse contact of IP address and include relevant log lines # to the destemail. -action_xarf = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] +action_xarf = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath=%(logpath)s, port="%(port)s"] # ban IP on CloudFlare & send an e-mail with whois report and relevant log lines diff --git a/fail2ban/client/actionreader.py b/fail2ban/client/actionreader.py index ace0b898..7ba54d31 100644 --- a/fail2ban/client/actionreader.py +++ b/fail2ban/client/actionreader.py @@ -45,6 +45,7 @@ class ActionReader(DefinitionInitConfigReader): "actioncheck": ["string", None], "actionrepair": ["string", None], "actionban": ["string", None], + "actionprolong": ["string", None], "actionunban": ["string", None], "norestored": ["string", None], } diff --git a/fail2ban/server/action.py b/fail2ban/server/action.py index d00458ba..ad0d0304 100644 --- a/fail2ban/server/action.py +++ b/fail2ban/server/action.py @@ -224,6 +224,10 @@ class ActionBase(object): """ pass + @property + def _prolongable(self): # pragma: no cover - abstract + return False + def unban(self, aInfo): # pragma: no cover - abstract """Executed when a ban expires. @@ -236,6 +240,11 @@ class ActionBase(object): pass +WRAP_CMD_PARAMS = { + 'timeout': 'str2seconds', + 'bantime': 'ignore', +} + class CommandAction(ActionBase): """A action which executes OS shell commands. @@ -306,7 +315,10 @@ class CommandAction(ActionBase): def __setattr__(self, name, value): if not name.startswith('_') and not self.__init and not callable(value): # special case for some pasrameters: - if name in ('timeout', 'bantime'): + wrp = WRAP_CMD_PARAMS.get(name) + if wrp == 'ignore': # ignore (filter) dynamic parameters + return + elif wrp == 'str2seconds': value = str(MyTime.str2seconds(value)) # parameters changed - clear properties and substitution cache: self.__properties = None @@ -434,6 +446,26 @@ class CommandAction(ActionBase): if not self._processCmd('', aInfo): raise RuntimeError("Error banning %(ip)s" % aInfo) + @property + def _prolongable(self): + return (hasattr(self, 'actionprolong') and self.actionprolong + and not str(self.actionprolong).isspace()) + + def prolong(self, aInfo): + """Executes the "actionprolong" command. + + Replaces the tags in the action command with actions properties + and ban information, and executes the resulting command. + + Parameters + ---------- + aInfo : dict + Dictionary which includes information in relation to + the ban. + """ + if not self._processCmd('', aInfo): + raise RuntimeError("Error prolonging %(ip)s" % aInfo) + def unban(self, aInfo): """Executes the "actionunban" command. @@ -498,8 +530,10 @@ class CommandAction(ActionBase): """ return self._executeOperation('', 'reloading') - @staticmethod - def escapeTag(value): + ESCAPE_CRE = re.compile(r"""[\\#&;`|*?~<>^()\[\]{}$'"\n\r]""") + + @classmethod + def escapeTag(cls, value): """Escape characters which may be used for command injection. Parameters @@ -516,12 +550,15 @@ class CommandAction(ActionBase): ----- The following characters are escaped:: - \\#&;`|*?~<>^()[]{}$'" + \\#&;`|*?~<>^()[]{}$'"\n\r """ - for c in '\\#&;`|*?~<>^()[]{}$\'"': - if c in value: - value = value.replace(c, '\\' + c) + _map2c = {'\n': 'n', '\r': 'r'} + def substChar(m): + c = m.group() + return '\\' + _map2c.get(c, c) + + value = cls.ESCAPE_CRE.sub(substChar, value) return value @classmethod @@ -780,7 +817,8 @@ class CommandAction(ActionBase): RuntimeError If command execution times out. """ - logSys.debug(realCmd) + if logSys.getEffectiveLevel() < logging.DEBUG: # pragma: no cover + logSys.log(9, realCmd) if not realCmd: logSys.debug("Nothing to do") return True diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py index f415c8ca..bae22215 100644 --- a/fail2ban/server/actions.py +++ b/fail2ban/server/actions.py @@ -298,6 +298,7 @@ class Actions(JailThread, Mapping): "fid": lambda self: self.__ticket.getID(), "failures": lambda self: self.__ticket.getAttempt(), "time": lambda self: self.__ticket.getTime(), + "bantime": lambda self: self._getBanTime(), "matches": lambda self: "\n".join(self.__ticket.getMatches()), # to bypass actions, that should not be executed for restored tickets "restored": lambda self: (1 if self.__ticket.restored else 0), @@ -325,6 +326,11 @@ class Actions(JailThread, Mapping): def copy(self): # pargma: no cover return self.__class__(self.__ticket, self.__jail, self.immutable, self.data.copy()) + def _getBanTime(self): + btime = self.__ticket.getBanTime() + if btime is None: btime = self.__jail.actions.getBanTime() + return btime + def _mi4ip(self, overalljails=False): """Gets bans merged once, a helper for lambda(s), prevents stop of executing action by any exception inside. @@ -445,6 +451,29 @@ class Actions(JailThread, Mapping): self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name) return cnt + def _prolongBan(self, ticket): + # prevent to prolong ticket that was removed in-between, + # if it in ban list - ban time already prolonged (and it stays there): + if not self.__banManager._inBanList(ticket): return + # do actions : + aInfo = None + for name, action in self._actions.iteritems(): + try: + if ticket.restored and getattr(action, 'norestored', False): + continue + if not action._prolongable: + continue + if aInfo is None: + aInfo = self.__getActionInfo(ticket) + if not aInfo.immutable: aInfo.reset() + action.prolong(aInfo) + except Exception as e: + logSys.error( + "Failed to execute ban jail '%s' action '%s' " + "info '%r': %s", + self._jail.name, name, aInfo, e, + exc_info=logSys.getEffectiveLevel()<=logging.DEBUG) + def __checkUnBan(self): """Check for IP address to unban. diff --git a/fail2ban/server/jail.py b/fail2ban/server/jail.py index 33f6db0b..0bb9f6fb 100644 --- a/fail2ban/server/jail.py +++ b/fail2ban/server/jail.py @@ -226,7 +226,7 @@ class Jail(object): if opt == 'increment': if isinstance(value, str): be[opt] = value.lower() in ("yes", "true", "ok", "1") - if be[opt] and self.database is None: + if be.get(opt) and self.database is None: logSys.warning("ban time increment is not available as long jail database is not set") if opt in ['maxtime', 'rndtime']: if not value is None: diff --git a/fail2ban/server/observer.py b/fail2ban/server/observer.py index 14233c55..174c95d5 100644 --- a/fail2ban/server/observer.py +++ b/fail2ban/server/observer.py @@ -121,9 +121,28 @@ class ObserverThread(JailThread): def add_timer(self, starttime, *event): """Add a timer event to queue will start (and wake) in 'starttime' seconds """ + # in testing we should wait (looping) for the possible time drifts: + if MyTime.myTime is not None and starttime: + # test time after short sleep: + t = threading.Timer(Utils.DEFAULT_SLEEP_INTERVAL, self._delayedEvent, + (MyTime.time() + starttime, time.time() + starttime, event) + ) + t.start() + return + # add timer event: t = threading.Timer(starttime, self.add, event) t.start() + def _delayedEvent(self, endMyTime, endTime, event): + if MyTime.time() >= endMyTime or time.time() >= endTime: + self.add_timer(0, *event) + return + # repeat after short sleep: + t = threading.Timer(Utils.DEFAULT_SLEEP_INTERVAL, self._delayedEvent, + (endMyTime, endTime, event) + ) + t.start() + def pulse_notify(self): """Notify wakeup (sets /and resets/ notify event) """ @@ -196,7 +215,8 @@ class ObserverThread(JailThread): if ev is None: break ## retrieve method by name - meth = __meth[ev[0]] + meth = ev[0] + if not callable(ev[0]): meth = __meth[meth] ## execute it with rest of event as variable arguments meth(*ev[1:]) except Exception as e: @@ -448,10 +468,10 @@ class ObserverThread(JailThread): Observer will check ip was known (bad) and possibly increase/prolong a ban time Secondary we will actualize the bans and bips (bad ip) in database """ - oldbtime = btime - ip = ticket.getIP() - logSys.debug("[%s] Observer: ban found %s, %s", jail.name, ip, btime) try: + oldbtime = btime + ip = ticket.getIP() + logSys.debug("[%s] Observer: ban found %s, %s", jail.name, ip, btime) # if not permanent, not restored and ban time was not set - check time should be increased: if btime != -1 and not ticket.restored and ticket.getBanTime() is None: btime = self.incrBanTime(jail, btime, ticket) @@ -475,6 +495,9 @@ class ObserverThread(JailThread): if btime != oldbtime: logSys.notice("[%s] Increase Ban %s (%d # %s -> %s)", jail.name, ip, ticket.getBanCount(), *logtime) + # delayed prolonging ticket via actions that expected this: + logSys.log(5, "[%s] Observer: prolong %s in %s", jail.name, ip, (btime, oldbtime)) + self.add_timer(min(10, btime - oldbtime - 5), self.prolongBan, ticket, jail) # add ticket to database, but only if was not restored (not already read from database): if jail.database is not None and not ticket.restored: # add to database always only after ban time was calculated an not yet already banned: @@ -482,6 +505,21 @@ class ObserverThread(JailThread): except Exception as e: logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG) + def prolongBan(self, ticket, jail): + """ Notify observer a ban occured for ip + + Observer will check ip was known (bad) and possibly increase/prolong a ban time + Secondary we will actualize the bans and bips (bad ip) in database + """ + try: + btime = ticket.getBanTime() + ip = ticket.getIP() + logSys.debug("[%s] Observer: prolong %s, %s", jail.name, ip, btime) + # prolong ticket via actions that expected this: + jail.actions._prolongBan(ticket) + except Exception as e: + logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG) + # Global observer initial created in server (could be later rewriten via singleton) class _Observers: def __init__(self): diff --git a/fail2ban/tests/actiontestcase.py b/fail2ban/tests/actiontestcase.py index cbd0aaca..47b266fd 100644 --- a/fail2ban/tests/actiontestcase.py +++ b/fail2ban/tests/actiontestcase.py @@ -206,15 +206,15 @@ class CommandActionTest(LogCaptureTestCase): self.assertEqual( self.__action.replaceTag("", {'matches': "some >char< should \< be[ escap}ed&\n"}), - "some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n") + "some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\\n") self.assertEqual( self.__action.replaceTag("", {'ipmatches': "some >char< should \< be[ escap}ed&\n"}), - "some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n") + "some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\\n") self.assertEqual( self.__action.replaceTag("", - {'ipjailmatches': "some >char< should \< be[ escap}ed&\n"}), - "some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n") + {'ipjailmatches': "some >char< should \< be[ escap}ed&\r\n"}), + "some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\\r\\n") # Recursive aInfo["ABC"] = "" diff --git a/fail2ban/tests/clientreadertestcase.py b/fail2ban/tests/clientreadertestcase.py index bfa68e03..b974a6f5 100644 --- a/fail2ban/tests/clientreadertestcase.py +++ b/fail2ban/tests/clientreadertestcase.py @@ -566,8 +566,6 @@ class JailsReaderTest(LogCaptureTestCase): # all must have some actionban defined self.assertTrue(actionReader._opts.get('actionban', '').strip(), msg="Action file %r is lacking actionban" % actionConfig) - self.assertIn('Init', actionReader.sections(), - msg="Action file %r is lacking [Init] section" % actionConfig) def testReadStockJailConf(self): jails = JailsReader(basedir=CONFIG_DIR, share_config=CONFIG_DIR_SHARE_CFG) # we are running tests from root project dir atm diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index 10ba0572..13df2fbc 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -43,7 +43,8 @@ from .. import protocol from ..server import server from ..server.mytime import MyTime from ..server.utils import Utils -from .utils import LogCaptureTestCase, logSys as DefLogSys, with_tmpdir, shutil, logging +from .utils import LogCaptureTestCase, logSys as DefLogSys, with_tmpdir, shutil, logging, \ + TEST_NOW, tearDownMyTime from ..helpers import getLogger @@ -80,6 +81,11 @@ fail2banclient.output = \ fail2banserver.output = \ protocol.output = _test_output +def _time_shift(shift): + # jump to the future (+shift minutes): + logSys.debug("===>>> time shift + %s min", shift) + MyTime.setTime(MyTime.time() + shift*60) + Observers = server.Observers @@ -317,6 +323,7 @@ def with_foreground_server_thread(startextra={}): # so don't kill (same process) - if success, just wait for end of worker: if phase.get('end', None): th.join() + tearDownMyTime() return wrapper return _deco_wrapper @@ -343,6 +350,7 @@ class Fail2banClientServerBase(LogCaptureTestCase): server.DEF_LOGTARGET = SRV_DEF_LOGTARGET server.DEF_LOGLEVEL = SRV_DEF_LOGLEVEL LogCaptureTestCase.tearDown(self) + tearDownMyTime() @staticmethod def _test_exit(code=0): @@ -1158,3 +1166,97 @@ class Fail2banServerTest(Fail2banClientServerBase): self.assertLogged( "Jail 'test-jail1' stopped", "Jail 'test-jail1' started", all=True) + + @with_foreground_server_thread() + def testServerObserver(self, tmp, startparams): + cfg = pjoin(tmp, "config") + test1log = pjoin(tmp, "test1.log") + + os.mkdir(pjoin(cfg, "action.d")) + def _write_action_cfg(actname="test-action1", prolong=True): + fn = pjoin(cfg, "action.d", "%s.conf" % actname) + _write_file(fn, "w", + "[DEFAULT]", + "", + "[Definition]", + "actionban = printf %%b '[%(name)s] %(actname)s: ++ ban -t : '", + "actionprolong = printf %%b '[%(name)s] %(actname)s: ++ prolong -t : '" \ + if prolong else "", + "actionunban = printf %%b '[%(name)s] %(actname)s: -- unban '", + ) + if unittest.F2B.log_level <= logging.DEBUG: # pragma: no cover + _out_file(fn) + + def _write_jail_cfg(backend="polling"): + _write_file(pjoin(cfg, "jail.conf"), "w", + "[INCLUDES]", "", + "[DEFAULT]", "", + "usedns = no", + "maxretry = 3", + "findtime = 1m", + "bantime = 5m", + "bantime.increment = true", + "datepattern = {^LN-BEG}EPOCH", + "", + "[test-jail1]", "backend = " + backend, "filter =", + "action = test-action1[name='%(__name__)s']", + " test-action2[name='%(__name__)s']", + "logpath = " + test1log, + "failregex = ^\s*failure 401|403 from :\s*.*$", + "enabled = true", + "", + ) + if unittest.F2B.log_level <= logging.DEBUG: # pragma: no cover + _out_file(pjoin(cfg, "jail.conf")) + + # create test config: + _write_action_cfg(actname="test-action1", prolong=False) + _write_action_cfg(actname="test-action2", prolong=True) + _write_jail_cfg() + + _write_file(test1log, "w") + # initial start: + self.pruneLog("[test-phase 0) time-0]") + self.execSuccess(startparams, "reload") + # generate bad ip: + _write_file(test1log, "w+", *( + (str(int(MyTime.time())) + " failure 401 from 192.0.2.11: I'm bad \"hacker\" `` $(echo test)",) * 3 + )) + # wait for ban: + self.assertLogged( + "stdout: '[test-jail1] test-action1: ++ ban 192.0.2.11 -t 300 : ", + "stdout: '[test-jail1] test-action2: ++ ban 192.0.2.11 -t 300 : ", + all=True, wait=MID_WAITTIME) + # wait for observer idle (write all tickets to db): + _observer_wait_idle() + + self.pruneLog("[test-phase 1) time+10m]") + # jump to the future (+10 minutes): + _time_shift(10) + self.assertLogged( + "stdout: '[test-jail1] test-action1: -- unban 192.0.2.11", + "stdout: '[test-jail1] test-action2: -- unban 192.0.2.11", + all=True, wait=MID_WAITTIME) + + self.pruneLog("[test-phase 2) time+10m]") + # write again (IP already bad): + _write_file(test1log, "w+", *( + (str(int(MyTime.time())) + " failure 401 from 192.0.2.11: I'm very bad \"hacker\" `` $(echo test)",) * 2 + )) + # wait for ban: + self.assertLogged( + "stdout: '[test-jail1] test-action1: ++ ban 192.0.2.11 -t 300 : ", + "stdout: '[test-jail1] test-action2: ++ ban 192.0.2.11 -t 300 : ", + all=True, wait=MID_WAITTIME) + # wait for observer idle (write all tickets to db): + _observer_wait_idle() + + self.pruneLog("[test-phase 2) time+11m]") + # jump to the future (+1 minute): + _time_shift(1) + # wait for observer idle (write all tickets to db): + _observer_wait_idle() + # wait for prolong: + self.assertLogged( + "stdout: '[test-jail1] test-action2: ++ prolong 192.0.2.11 -t 600 : ", + all=True, wait=MID_WAITTIME) diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index c98d6ee2..becf939c 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -1065,8 +1065,20 @@ class ServerConfigReaderTests(LogCaptureTestCase): logSys.debug(l) return True + def _testActionInfos(self): + if not hasattr(self, '__aInfos'): + dmyjail = DummyJail() + self.__aInfos = {} + for t, ip in (('ipv4', '192.0.2.1'), ('ipv6', '2001:DB8::')): + ticket = BanTicket(ip) + ticket.setBanTime(600) + self.__aInfos[t] = _actions.Actions.ActionInfo(ticket, dmyjail) + return self.__aInfos + def _testExecActions(self, server): jails = server._Server__jails + + aInfos = self._testActionInfos() for jail in jails: # print(jail, jails[jail]) for a in jails[jail].actions: @@ -1083,16 +1095,16 @@ class ServerConfigReaderTests(LogCaptureTestCase): action.start() # test ban ip4 : logSys.debug('# === ban-ipv4 ==='); self.pruneLog() - action.ban({'ip': IPAddr('192.0.2.1'), 'family': 'inet4'}) + action.ban(aInfos['ipv4']) # test unban ip4 : logSys.debug('# === unban ipv4 ==='); self.pruneLog() - action.unban({'ip': IPAddr('192.0.2.1'), 'family': 'inet4'}) + action.unban(aInfos['ipv4']) # test ban ip6 : logSys.debug('# === ban ipv6 ==='); self.pruneLog() - action.ban({'ip': IPAddr('2001:DB8::'), 'family': 'inet6'}) + action.ban(aInfos['ipv6']) # test unban ip6 : logSys.debug('# === unban ipv6 ==='); self.pruneLog() - action.unban({'ip': IPAddr('2001:DB8::'), 'family': 'inet6'}) + action.unban(aInfos['ipv6']) # test stop : logSys.debug('# === stop ==='); self.pruneLog() action.stop() @@ -1310,11 +1322,11 @@ class ServerConfigReaderTests(LogCaptureTestCase): ('j-w-iptables-ipset', 'iptables-ipset-proto6[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain="INPUT"]', { 'ip4': (' f2b-j-w-iptables-ipset ',), 'ip6': (' f2b-j-w-iptables-ipset6 ',), 'ip4-start': ( - "`ipset create f2b-j-w-iptables-ipset hash:ip timeout 600`", + "`ipset create f2b-j-w-iptables-ipset hash:ip`", "`iptables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset src -j REJECT --reject-with icmp-port-unreachable`", ), 'ip6-start': ( - "`ipset create f2b-j-w-iptables-ipset6 hash:ip timeout 600 family inet6`", + "`ipset create f2b-j-w-iptables-ipset6 hash:ip family inet6`", "`ip6tables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`", ), 'flush': ( @@ -1348,11 +1360,11 @@ class ServerConfigReaderTests(LogCaptureTestCase): ('j-w-iptables-ipset-ap', 'iptables-ipset-proto6-allports[name=%(__name__)s, bantime="10m", chain="INPUT"]', { 'ip4': (' f2b-j-w-iptables-ipset-ap ',), 'ip6': (' f2b-j-w-iptables-ipset-ap6 ',), 'ip4-start': ( - "`ipset create f2b-j-w-iptables-ipset-ap hash:ip timeout 600`", + "`ipset create f2b-j-w-iptables-ipset-ap hash:ip`", "`iptables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`", ), 'ip6-start': ( - "`ipset create f2b-j-w-iptables-ipset-ap6 hash:ip timeout 600 family inet6`", + "`ipset create f2b-j-w-iptables-ipset-ap6 hash:ip family inet6`", "`ip6tables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`", ), 'flush': ( @@ -1646,11 +1658,11 @@ class ServerConfigReaderTests(LogCaptureTestCase): ('j-w-fwcmd-ipset', 'firewallcmd-ipset[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain="INPUT"]', { 'ip4': (' f2b-j-w-fwcmd-ipset ',), 'ip6': (' f2b-j-w-fwcmd-ipset6 ',), 'ip4-start': ( - "`ipset create f2b-j-w-fwcmd-ipset hash:ip timeout 600`", + "`ipset create f2b-j-w-fwcmd-ipset hash:ip`", "`firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset src -j REJECT --reject-with icmp-port-unreachable`", ), 'ip6-start': ( - "`ipset create f2b-j-w-fwcmd-ipset6 hash:ip timeout 600`", + "`ipset create f2b-j-w-fwcmd-ipset6 hash:ip`", "`firewall-cmd --direct --add-rule ipv6 filter INPUT 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`", ), 'stop': ( @@ -1695,10 +1707,7 @@ class ServerConfigReaderTests(LogCaptureTestCase): jails = server._Server__jails - tickets = { - 'ip4': BanTicket('192.0.2.1'), - 'ip6': BanTicket('2001:DB8::'), - } + aInfos = self._testActionInfos() for jail, act, tests in testJailsActions: # print(jail, jails[jail]) for a in jails[jail].actions: @@ -1716,32 +1725,28 @@ class ServerConfigReaderTests(LogCaptureTestCase): self.assertLogged(*tests['start'], all=True) else: self.assertNotLogged(*tests['ip4-start']+tests['ip6-start'], all=True) - ainfo = { - 'ip4': _actions.Actions.ActionInfo(tickets['ip4'], jails[jail]), - 'ip6': _actions.Actions.ActionInfo(tickets['ip6'], jails[jail]), - } # test ban ip4 : self.pruneLog('# === ban-ipv4 ===') - action.ban(ainfo['ip4']) + action.ban(aInfos['ipv4']) if tests.get('ip4-start'): self.assertLogged(*tests['ip4-start'], all=True) if tests.get('ip6-start'): self.assertNotLogged(*tests['ip6-start'], all=True) self.assertLogged(*tests['ip4-check']+tests['ip4-ban'], all=True) self.assertNotLogged(*tests['ip6'], all=True) # test unban ip4 : self.pruneLog('# === unban ipv4 ===') - action.unban(ainfo['ip4']) + action.unban(aInfos['ipv4']) self.assertLogged(*tests['ip4-check']+tests['ip4-unban'], all=True) self.assertNotLogged(*tests['ip6'], all=True) # test ban ip6 : self.pruneLog('# === ban ipv6 ===') - action.ban(ainfo['ip6']) + action.ban(aInfos['ipv6']) if tests.get('ip6-start'): self.assertLogged(*tests['ip6-start'], all=True) if tests.get('ip4-start'): self.assertNotLogged(*tests['ip4-start'], all=True) self.assertLogged(*tests['ip6-check']+tests['ip6-ban'], all=True) self.assertNotLogged(*tests['ip4'], all=True) # test unban ip6 : self.pruneLog('# === unban ipv6 ===') - action.unban(ainfo['ip6']) + action.unban(aInfos['ipv6']) self.assertLogged(*tests['ip6-check']+tests['ip6-unban'], all=True) self.assertNotLogged(*tests['ip4'], all=True) # test flush for actions should supported this: