mirror of https://github.com/fail2ban/fail2ban
automatic reban (repeat banning action) after repair/restore sane environment, if already logged ticket causes new failures (part of #980, closes #1680);
introduces banning epoch for actions and tickets (to distinguish or recognize removed set of tickets)pull/2588/head
parent
1a9bc1905d
commit
f001f8de2a
|
@ -217,6 +217,7 @@ class ActionBase(object):
|
||||||
"start",
|
"start",
|
||||||
"stop",
|
"stop",
|
||||||
"ban",
|
"ban",
|
||||||
|
"reban",
|
||||||
"unban",
|
"unban",
|
||||||
)
|
)
|
||||||
for method in required:
|
for method in required:
|
||||||
|
@ -250,6 +251,17 @@ class ActionBase(object):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def reban(self, aInfo): # pragma: no cover - abstract
|
||||||
|
"""Executed when a ban occurs.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
aInfo : dict
|
||||||
|
Dictionary which includes information in relation to
|
||||||
|
the ban.
|
||||||
|
"""
|
||||||
|
return self.ban(aInfo)
|
||||||
|
|
||||||
def unban(self, aInfo): # pragma: no cover - abstract
|
def unban(self, aInfo): # pragma: no cover - abstract
|
||||||
"""Executed when a ban expires.
|
"""Executed when a ban expires.
|
||||||
|
|
||||||
|
@ -281,6 +293,7 @@ class CommandAction(ActionBase):
|
||||||
----------
|
----------
|
||||||
actionban
|
actionban
|
||||||
actioncheck
|
actioncheck
|
||||||
|
actionreban
|
||||||
actionreload
|
actionreload
|
||||||
actionrepair
|
actionrepair
|
||||||
actionstart
|
actionstart
|
||||||
|
@ -301,6 +314,7 @@ class CommandAction(ActionBase):
|
||||||
self.actionstart = ''
|
self.actionstart = ''
|
||||||
## Command executed when ticket gets banned.
|
## Command executed when ticket gets banned.
|
||||||
self.actionban = ''
|
self.actionban = ''
|
||||||
|
self.actionreban = ''
|
||||||
## Command executed when ticket gets removed.
|
## Command executed when ticket gets removed.
|
||||||
self.actionunban = ''
|
self.actionunban = ''
|
||||||
## Command executed in order to check requirements.
|
## Command executed in order to check requirements.
|
||||||
|
@ -504,8 +518,8 @@ class CommandAction(ActionBase):
|
||||||
ret = self._executeOperation('<actionstart>', 'starting', family=family, afterExec=_started)
|
ret = self._executeOperation('<actionstart>', 'starting', family=family, afterExec=_started)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def ban(self, aInfo):
|
def ban(self, aInfo, cmd='<actionban>'):
|
||||||
"""Executes the "actionban" command.
|
"""Executes the given command ("actionban" or "actionreban").
|
||||||
|
|
||||||
Replaces the tags in the action command with actions properties
|
Replaces the tags in the action command with actions properties
|
||||||
and ban information, and executes the resulting command.
|
and ban information, and executes the resulting command.
|
||||||
|
@ -522,7 +536,7 @@ class CommandAction(ActionBase):
|
||||||
if not self.__started.get(family):
|
if not self.__started.get(family):
|
||||||
self._start(family, forceStart=True)
|
self._start(family, forceStart=True)
|
||||||
# ban:
|
# ban:
|
||||||
if not self._processCmd('<actionban>', aInfo):
|
if not self._processCmd(cmd, aInfo):
|
||||||
raise RuntimeError("Error banning %(ip)s" % aInfo)
|
raise RuntimeError("Error banning %(ip)s" % aInfo)
|
||||||
self.__started[family] = self.__started.get(family, 0) | 3; # started and contains items
|
self.__started[family] = self.__started.get(family, 0) | 3; # started and contains items
|
||||||
|
|
||||||
|
@ -543,6 +557,21 @@ class CommandAction(ActionBase):
|
||||||
if not self._processCmd('<actionunban>', aInfo):
|
if not self._processCmd('<actionunban>', aInfo):
|
||||||
raise RuntimeError("Error unbanning %(ip)s" % aInfo)
|
raise RuntimeError("Error unbanning %(ip)s" % aInfo)
|
||||||
|
|
||||||
|
def reban(self, aInfo):
|
||||||
|
"""Executes the "actionreban" command if available, otherwise simply repeat "actionban".
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
# re-ban:
|
||||||
|
return self.ban(aInfo, '<actionreban>' if self.actionreban else '<actionban>')
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
"""Executes the "actionflush" command.
|
"""Executes the "actionflush" command.
|
||||||
|
|
||||||
|
@ -812,6 +841,17 @@ class CommandAction(ActionBase):
|
||||||
realCmd = Utils.buildShellCmd(realCmd, varsDict)
|
realCmd = Utils.buildShellCmd(realCmd, varsDict)
|
||||||
return realCmd
|
return realCmd
|
||||||
|
|
||||||
|
@property
|
||||||
|
def banEpoch(self):
|
||||||
|
return getattr(self, '_banEpoch', 0)
|
||||||
|
def invalidateBanEpoch(self):
|
||||||
|
"""Increments ban epoch of jail and this action, so already banned tickets would cause
|
||||||
|
a re-ban for all tickets with previous epoch."""
|
||||||
|
if self._jail is not None:
|
||||||
|
self._banEpoch = self._jail.actions.banEpoch = self._jail.actions.banEpoch + 1
|
||||||
|
else:
|
||||||
|
self._banEpoch = self.banEpoch + 1
|
||||||
|
|
||||||
def _invariantCheck(self, family=None, beforeRepair=None, forceStart=True):
|
def _invariantCheck(self, family=None, beforeRepair=None, forceStart=True):
|
||||||
"""Executes a substituted `actioncheck` command.
|
"""Executes a substituted `actioncheck` command.
|
||||||
"""
|
"""
|
||||||
|
@ -826,6 +866,8 @@ class CommandAction(ActionBase):
|
||||||
return -1
|
return -1
|
||||||
self._logSys.error(
|
self._logSys.error(
|
||||||
"Invariant check failed. Trying to restore a sane environment")
|
"Invariant check failed. Trying to restore a sane environment")
|
||||||
|
# increment ban epoch of jail and this action (allows re-ban on already banned):
|
||||||
|
self.invalidateBanEpoch()
|
||||||
# try to find repair command, if exists - exec it:
|
# try to find repair command, if exists - exec it:
|
||||||
repairCmd = self._getOperation('<actionrepair>', family)
|
repairCmd = self._getOperation('<actionrepair>', family)
|
||||||
if repairCmd:
|
if repairCmd:
|
||||||
|
|
|
@ -81,6 +81,8 @@ class Actions(JailThread, Mapping):
|
||||||
self._actions = OrderedDict()
|
self._actions = OrderedDict()
|
||||||
## The ban manager.
|
## The ban manager.
|
||||||
self.__banManager = BanManager()
|
self.__banManager = BanManager()
|
||||||
|
self.banEpoch = 0
|
||||||
|
self.__lastConsistencyCheckTM = 0
|
||||||
## Precedence of ban (over unban), so max number of tickets banned (to call an unban check):
|
## Precedence of ban (over unban), so max number of tickets banned (to call an unban check):
|
||||||
self.banPrecedence = 10
|
self.banPrecedence = 10
|
||||||
## Max count of outdated tickets to unban per each __checkUnBan operation:
|
## Max count of outdated tickets to unban per each __checkUnBan operation:
|
||||||
|
@ -439,6 +441,7 @@ class Actions(JailThread, Mapping):
|
||||||
cnt = 0
|
cnt = 0
|
||||||
if not tickets:
|
if not tickets:
|
||||||
tickets = self.__getFailTickets(self.banPrecedence)
|
tickets = self.__getFailTickets(self.banPrecedence)
|
||||||
|
rebanacts = None
|
||||||
for ticket in tickets:
|
for ticket in tickets:
|
||||||
bTicket = BanManager.createBanTicket(ticket)
|
bTicket = BanManager.createBanTicket(ticket)
|
||||||
ip = bTicket.getIP()
|
ip = bTicket.getIP()
|
||||||
|
@ -461,6 +464,8 @@ class Actions(JailThread, Mapping):
|
||||||
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
# after all actions are processed set banned flag:
|
# after all actions are processed set banned flag:
|
||||||
bTicket.banned = True
|
bTicket.banned = True
|
||||||
|
if self.banEpoch: # be sure tickets always have the same ban epoch (default 0):
|
||||||
|
bTicket.banEpoch = self.banEpoch
|
||||||
else:
|
else:
|
||||||
bTicket = reason['ticket']
|
bTicket = reason['ticket']
|
||||||
# if already banned (otherwise still process some action)
|
# if already banned (otherwise still process some action)
|
||||||
|
@ -475,11 +480,61 @@ class Actions(JailThread, Mapping):
|
||||||
else logging.NOTICE if diftm < 60 \
|
else logging.NOTICE if diftm < 60 \
|
||||||
else logging.WARNING
|
else logging.WARNING
|
||||||
logSys.log(ll, "[%s] %s already banned", self._jail.name, ip)
|
logSys.log(ll, "[%s] %s already banned", self._jail.name, ip)
|
||||||
|
# if long time after ban - do consistency check (something is wrong here):
|
||||||
|
if bTicket.banEpoch == self.banEpoch and diftm > 3:
|
||||||
|
# avoid too often checks:
|
||||||
|
if not rebanacts and MyTime.time() > self.__lastConsistencyCheckTM + 3:
|
||||||
|
for action in self._actions.itervalues():
|
||||||
|
action.consistencyCheck()
|
||||||
|
self.__lastConsistencyCheckTM = MyTime.time()
|
||||||
|
# check epoch in order to reban it:
|
||||||
|
if bTicket.banEpoch < self.banEpoch:
|
||||||
|
if not rebanacts: rebanacts = dict(
|
||||||
|
(name, action) for name, action in self._actions.iteritems()
|
||||||
|
if action.banEpoch > bTicket.banEpoch)
|
||||||
|
cnt += self.__reBan(bTicket, actions=rebanacts)
|
||||||
|
else:
|
||||||
|
# pragma: no cover - unexpected: ticket is not banned for some reasons - reban using all actions:
|
||||||
|
cnt += self.__reBan(bTicket)
|
||||||
if cnt:
|
if cnt:
|
||||||
logSys.debug("Banned %s / %s, %s ticket(s) in %r", cnt,
|
logSys.debug("Banned %s / %s, %s ticket(s) in %r", cnt,
|
||||||
self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name)
|
self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name)
|
||||||
return cnt
|
return cnt
|
||||||
|
|
||||||
|
def __reBan(self, ticket, actions=None, log=True):
|
||||||
|
"""Repeat bans for the ticket.
|
||||||
|
|
||||||
|
Executes the actions in order to reban the host given in the
|
||||||
|
ticket.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
ticket : Ticket
|
||||||
|
Ticket to reban
|
||||||
|
"""
|
||||||
|
actions = actions or self._actions
|
||||||
|
ip = ticket.getIP()
|
||||||
|
aInfo = self.__getActionInfo(ticket)
|
||||||
|
if log:
|
||||||
|
logSys.notice("[%s] Reban %s%s", self._jail.name, aInfo["ip"], (', action %r' % actions.keys()[0] if len(actions) == 1 else ''))
|
||||||
|
for name, action in actions.iteritems():
|
||||||
|
try:
|
||||||
|
logSys.debug("[%s] action %r: reban %s", self._jail.name, name, ip)
|
||||||
|
if not aInfo.immutable: aInfo.reset()
|
||||||
|
action.reban(aInfo)
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error(
|
||||||
|
"Failed to execute reban jail '%s' action '%s' "
|
||||||
|
"info '%r': %s",
|
||||||
|
self._jail.name, name, aInfo, e,
|
||||||
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
|
return 0
|
||||||
|
# after all actions are processed set banned flag:
|
||||||
|
ticket.banned = True
|
||||||
|
if self.banEpoch: # be sure tickets always have the same ban epoch (default 0):
|
||||||
|
ticket.banEpoch = self.banEpoch
|
||||||
|
return 1
|
||||||
|
|
||||||
def __checkUnBan(self, maxCount=None):
|
def __checkUnBan(self, maxCount=None):
|
||||||
"""Check for IP address to unban.
|
"""Check for IP address to unban.
|
||||||
|
|
||||||
|
@ -522,7 +577,7 @@ class Actions(JailThread, Mapping):
|
||||||
logSys.error("Failed to flush bans in jail '%s' action '%s': %s",
|
logSys.error("Failed to flush bans in jail '%s' action '%s': %s",
|
||||||
self._jail.name, name, e,
|
self._jail.name, name, e,
|
||||||
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
logSys.info("No flush occured, do consistency check")
|
logSys.info("No flush occurred, do consistency check")
|
||||||
if hasattr(action, 'consistencyCheck'):
|
if hasattr(action, 'consistencyCheck'):
|
||||||
def _beforeRepair():
|
def _beforeRepair():
|
||||||
if stop and not getattr(action, 'actionrepair_on_unban', None): # don't need repair on stop
|
if stop and not getattr(action, 'actionrepair_on_unban', None): # don't need repair on stop
|
||||||
|
@ -569,8 +624,6 @@ class Actions(JailThread, Mapping):
|
||||||
logSys.notice("[%s] Unban %s", self._jail.name, aInfo["ip"])
|
logSys.notice("[%s] Unban %s", self._jail.name, aInfo["ip"])
|
||||||
for name, action in unbactions.iteritems():
|
for name, action in unbactions.iteritems():
|
||||||
try:
|
try:
|
||||||
if ticket.restored and getattr(action, 'norestored', False):
|
|
||||||
continue
|
|
||||||
logSys.debug("[%s] action %r: unban %s", self._jail.name, name, ip)
|
logSys.debug("[%s] action %r: unban %s", self._jail.name, name, ip)
|
||||||
if not aInfo.immutable: aInfo.reset()
|
if not aInfo.immutable: aInfo.reset()
|
||||||
action.unban(aInfo)
|
action.unban(aInfo)
|
||||||
|
|
|
@ -206,6 +206,13 @@ class Ticket(object):
|
||||||
# return single value of data:
|
# return single value of data:
|
||||||
return self._data.get(key, default)
|
return self._data.get(key, default)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def banEpoch(self):
|
||||||
|
return getattr(self, '_banEpoch', 0)
|
||||||
|
@banEpoch.setter
|
||||||
|
def banEpoch(self, value):
|
||||||
|
self._banEpoch = value
|
||||||
|
|
||||||
|
|
||||||
class FailTicket(Ticket):
|
class FailTicket(Ticket):
|
||||||
|
|
||||||
|
|
|
@ -28,11 +28,10 @@ import time
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from ..server.actions import Actions
|
|
||||||
from ..server.ticket import FailTicket
|
from ..server.ticket import FailTicket
|
||||||
from ..server.utils import Utils
|
from ..server.utils import Utils
|
||||||
from .dummyjail import DummyJail
|
from .dummyjail import DummyJail
|
||||||
from .utils import LogCaptureTestCase, with_alt_time, MyTime
|
from .utils import LogCaptureTestCase, with_alt_time, with_tmpdir, MyTime
|
||||||
|
|
||||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||||
|
|
||||||
|
@ -43,7 +42,7 @@ class ExecuteActions(LogCaptureTestCase):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
super(ExecuteActions, self).setUp()
|
super(ExecuteActions, self).setUp()
|
||||||
self.__jail = DummyJail()
|
self.__jail = DummyJail()
|
||||||
self.__actions = Actions(self.__jail)
|
self.__actions = self.__jail.actions
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(ExecuteActions, self).tearDown()
|
super(ExecuteActions, self).tearDown()
|
||||||
|
@ -52,8 +51,8 @@ class ExecuteActions(LogCaptureTestCase):
|
||||||
self.__actions.add('ip')
|
self.__actions.add('ip')
|
||||||
act = self.__actions['ip']
|
act = self.__actions['ip']
|
||||||
act.actionstart = 'echo ip start'+o.get('start', '')
|
act.actionstart = 'echo ip start'+o.get('start', '')
|
||||||
act.actionban = 'echo ip ban <ip>'
|
act.actionban = 'echo ip ban <ip>'+o.get('ban', '')
|
||||||
act.actionunban = 'echo ip unban <ip>'
|
act.actionunban = 'echo ip unban <ip>'+o.get('unban', '')
|
||||||
act.actioncheck = 'echo ip check'+o.get('check', '')
|
act.actioncheck = 'echo ip check'+o.get('check', '')
|
||||||
act.actionflush = 'echo ip flush'+o.get('flush', '')
|
act.actionflush = 'echo ip flush'+o.get('flush', '')
|
||||||
act.actionstop = 'echo ip stop'+o.get('stop', '')
|
act.actionstop = 'echo ip stop'+o.get('stop', '')
|
||||||
|
@ -239,7 +238,7 @@ class ExecuteActions(LogCaptureTestCase):
|
||||||
"stdout: %r" % 'ip flush inet4',
|
"stdout: %r" % 'ip flush inet4',
|
||||||
"stdout: %r" % 'ip flush inet6',
|
"stdout: %r" % 'ip flush inet6',
|
||||||
'Failed to flush bans',
|
'Failed to flush bans',
|
||||||
'No flush occured, do consistency check',
|
'No flush occurred, do consistency check',
|
||||||
'Invariant check failed. Trying to restore a sane environment',
|
'Invariant check failed. Trying to restore a sane environment',
|
||||||
"stdout: %r" % 'ip stop', # same for both families
|
"stdout: %r" % 'ip stop', # same for both families
|
||||||
'Failed to flush bans',
|
'Failed to flush bans',
|
||||||
|
@ -259,7 +258,7 @@ class ExecuteActions(LogCaptureTestCase):
|
||||||
self.pruneLog('[test-phase 3] failed flush in consistent env')
|
self.pruneLog('[test-phase 3] failed flush in consistent env')
|
||||||
self.__actions._Actions__flushBan()
|
self.__actions._Actions__flushBan()
|
||||||
self.assertLogged('Failed to flush bans',
|
self.assertLogged('Failed to flush bans',
|
||||||
'No flush occured, do consistency check',
|
'No flush occurred, do consistency check',
|
||||||
"stdout: %r" % 'ip flush inet6',
|
"stdout: %r" % 'ip flush inet6',
|
||||||
"stdout: %r" % 'ip check inet6',
|
"stdout: %r" % 'ip check inet6',
|
||||||
all=True, wait=True)
|
all=True, wait=True)
|
||||||
|
@ -341,7 +340,7 @@ class ExecuteActions(LogCaptureTestCase):
|
||||||
"stdout: %r" % 'ip flush inet4',
|
"stdout: %r" % 'ip flush inet4',
|
||||||
"stdout: %r" % 'ip flush inet6',
|
"stdout: %r" % 'ip flush inet6',
|
||||||
'Failed to flush bans',
|
'Failed to flush bans',
|
||||||
'No flush occured, do consistency check',
|
'No flush occurred, do consistency check',
|
||||||
'Invariant check failed. Trying to restore a sane environment',
|
'Invariant check failed. Trying to restore a sane environment',
|
||||||
"stdout: %r" % 'ip stop inet6',
|
"stdout: %r" % 'ip stop inet6',
|
||||||
'Failed to flush bans in jail',
|
'Failed to flush bans in jail',
|
||||||
|
@ -368,7 +367,7 @@ class ExecuteActions(LogCaptureTestCase):
|
||||||
act['actioncheck?family=inet6'] = act.actioncheck
|
act['actioncheck?family=inet6'] = act.actioncheck
|
||||||
self.__actions._Actions__flushBan()
|
self.__actions._Actions__flushBan()
|
||||||
self.assertLogged('Failed to flush bans',
|
self.assertLogged('Failed to flush bans',
|
||||||
'No flush occured, do consistency check',
|
'No flush occurred, do consistency check',
|
||||||
"stdout: %r" % 'ip flush inet6',
|
"stdout: %r" % 'ip flush inet6',
|
||||||
"stdout: %r" % 'ip check inet6',
|
"stdout: %r" % 'ip check inet6',
|
||||||
all=True, wait=True)
|
all=True, wait=True)
|
||||||
|
@ -396,3 +395,103 @@ class ExecuteActions(LogCaptureTestCase):
|
||||||
"stdout: %r" % 'ip flush inet4',
|
"stdout: %r" % 'ip flush inet4',
|
||||||
'Unban tickets each individualy',
|
'Unban tickets each individualy',
|
||||||
all=True)
|
all=True)
|
||||||
|
|
||||||
|
@with_alt_time
|
||||||
|
@with_tmpdir
|
||||||
|
def testActionsRebanBrokenAfterRepair(self, tmp):
|
||||||
|
act = self.defaultAction({
|
||||||
|
'start':' <family>; touch "<FN>"',
|
||||||
|
'check':' <family>; test -f "<FN>"',
|
||||||
|
'flush':' <family>; echo -n "" > "<FN>"',
|
||||||
|
'stop': ' <family>; rm -f "<FN>"',
|
||||||
|
'ban': ' <family>; echo "<ip> <family>" >> "<FN>"',
|
||||||
|
})
|
||||||
|
act['FN'] = tmp+'/<family>'
|
||||||
|
act.actionstart_on_demand = True
|
||||||
|
act.actionrepair = 'echo ip repair <family>; touch "<FN>"'
|
||||||
|
act.actionreban = 'echo ip reban <ip> <family>; echo "<ip> <family> -- rebanned" >> "<FN>"'
|
||||||
|
self.pruneLog('[test-phase 0] initial ban')
|
||||||
|
self.assertEqual(self.__actions.addBannedIP(['192.0.2.1', '2001:db8::1']), 2)
|
||||||
|
self.assertLogged('Ban 192.0.2.1', 'Ban 2001:db8::1',
|
||||||
|
"stdout: %r" % 'ip start inet4',
|
||||||
|
"stdout: %r" % 'ip ban 192.0.2.1 inet4',
|
||||||
|
"stdout: %r" % 'ip start inet6',
|
||||||
|
"stdout: %r" % 'ip ban 2001:db8::1 inet6',
|
||||||
|
all=True)
|
||||||
|
|
||||||
|
self.pruneLog('[test-phase 1] check ban')
|
||||||
|
self.dumpFile(tmp+'/inet4')
|
||||||
|
self.assertLogged('192.0.2.1 inet4')
|
||||||
|
self.assertNotLogged('2001:db8::1 inet6')
|
||||||
|
self.pruneLog()
|
||||||
|
self.dumpFile(tmp+'/inet6')
|
||||||
|
self.assertLogged('2001:db8::1 inet6')
|
||||||
|
self.assertNotLogged('192.0.2.1 inet4')
|
||||||
|
|
||||||
|
# simulate 3 seconds past:
|
||||||
|
MyTime.setTime(MyTime.time() + 4)
|
||||||
|
# already banned produces events:
|
||||||
|
self.pruneLog('[test-phase 2] check already banned')
|
||||||
|
self.assertEqual(self.__actions.addBannedIP(['192.0.2.1', '2001:db8::1', '2001:db8::2']), 1)
|
||||||
|
self.assertLogged(
|
||||||
|
'192.0.2.1 already banned', '2001:db8::1 already banned', 'Ban 2001:db8::2',
|
||||||
|
"stdout: %r" % 'ip check inet4', # both checks occurred
|
||||||
|
"stdout: %r" % 'ip check inet6',
|
||||||
|
all=True)
|
||||||
|
self.dumpFile(tmp+'/inet4')
|
||||||
|
self.dumpFile(tmp+'/inet6')
|
||||||
|
# no reban should occur:
|
||||||
|
self.assertNotLogged('Reban 192.0.2.1', 'Reban 2001:db8::1',
|
||||||
|
"stdout: %r" % 'ip ban 192.0.2.1 inet4',
|
||||||
|
"stdout: %r" % 'ip reban 192.0.2.1 inet4',
|
||||||
|
"stdout: %r" % 'ip ban 2001:db8::1 inet6',
|
||||||
|
"stdout: %r" % 'ip reban 2001:db8::1 inet6',
|
||||||
|
'192.0.2.1 inet4 -- repaired',
|
||||||
|
'2001:db8::1 inet6 -- repaired',
|
||||||
|
all=True)
|
||||||
|
|
||||||
|
# simulate 3 seconds past:
|
||||||
|
MyTime.setTime(MyTime.time() + 4)
|
||||||
|
# break env (remove both files, so check would fail):
|
||||||
|
os.remove(tmp+'/inet4')
|
||||||
|
os.remove(tmp+'/inet6')
|
||||||
|
# test again already banned (it shall cause reban now):
|
||||||
|
self.pruneLog('[test-phase 3a] check reban after sane env repaired')
|
||||||
|
self.assertEqual(self.__actions.addBannedIP(['192.0.2.1', '2001:db8::1']), 2)
|
||||||
|
self.assertLogged(
|
||||||
|
"Invariant check failed. Trying to restore a sane environment",
|
||||||
|
"stdout: %r" % 'ip repair inet4', # both repairs occurred
|
||||||
|
"stdout: %r" % 'ip repair inet6',
|
||||||
|
"Reban 192.0.2.1, action 'ip'", "Reban 2001:db8::1, action 'ip'", # both rebans also
|
||||||
|
"stdout: %r" % 'ip reban 192.0.2.1 inet4',
|
||||||
|
"stdout: %r" % 'ip reban 2001:db8::1 inet6',
|
||||||
|
all=True)
|
||||||
|
|
||||||
|
# now last IP (2001:db8::2) - no repair, but still old epoch of ticket, so it gets rebanned:
|
||||||
|
self.pruneLog('[test-phase 3a] check reban by epoch mismatch (without repair)')
|
||||||
|
self.assertEqual(self.__actions.addBannedIP('2001:db8::2'), 1)
|
||||||
|
self.assertLogged(
|
||||||
|
"Reban 2001:db8::2, action 'ip'",
|
||||||
|
"stdout: %r" % 'ip reban 2001:db8::2 inet6',
|
||||||
|
all=True)
|
||||||
|
self.assertNotLogged(
|
||||||
|
"Invariant check failed. Trying to restore a sane environment",
|
||||||
|
"stdout: %r" % 'ip repair inet4', # both repairs occurred
|
||||||
|
"stdout: %r" % 'ip repair inet6',
|
||||||
|
"Reban 192.0.2.1, action 'ip'", "Reban 2001:db8::1, action 'ip'", # both rebans also
|
||||||
|
"stdout: %r" % 'ip reban 192.0.2.1 inet4',
|
||||||
|
"stdout: %r" % 'ip reban 2001:db8::1 inet6',
|
||||||
|
all=True)
|
||||||
|
|
||||||
|
# and bans present in files:
|
||||||
|
self.pruneLog('[test-phase 4] check reban')
|
||||||
|
self.dumpFile(tmp+'/inet4')
|
||||||
|
self.assertLogged('192.0.2.1 inet4 -- rebanned')
|
||||||
|
self.assertNotLogged('2001:db8::1 inet6 -- rebanned')
|
||||||
|
self.pruneLog()
|
||||||
|
self.dumpFile(tmp+'/inet6')
|
||||||
|
self.assertLogged(
|
||||||
|
'2001:db8::1 inet6 -- rebanned',
|
||||||
|
'2001:db8::2 inet6 -- rebanned', all=True)
|
||||||
|
self.assertNotLogged('192.0.2.1 inet4 -- rebanned')
|
||||||
|
|
||||||
|
|
|
@ -113,16 +113,7 @@ fail2banclient.input_command = _test_input_command
|
||||||
fail2bancmdline.PRODUCTION = \
|
fail2bancmdline.PRODUCTION = \
|
||||||
fail2banserver.PRODUCTION = False
|
fail2banserver.PRODUCTION = False
|
||||||
|
|
||||||
|
_out_file = LogCaptureTestCase.dumpFile
|
||||||
def _out_file(fn, handle=logSys.debug):
|
|
||||||
"""Helper which outputs content of the file at HEAVYDEBUG loglevels"""
|
|
||||||
if (handle != logSys.debug or logSys.getEffectiveLevel() <= logging.DEBUG):
|
|
||||||
handle('---- ' + fn + ' ----')
|
|
||||||
for line in fileinput.input(fn):
|
|
||||||
line = line.rstrip('\n')
|
|
||||||
handle(line)
|
|
||||||
handle('-'*30)
|
|
||||||
|
|
||||||
|
|
||||||
def _write_file(fn, mode, *lines):
|
def _write_file(fn, mode, *lines):
|
||||||
f = open(fn, mode)
|
f = open(fn, mode)
|
||||||
|
|
|
@ -752,7 +752,7 @@ class Transmitter(TransmitterBase):
|
||||||
self.assertSortedEqual(
|
self.assertSortedEqual(
|
||||||
self.transm.proceed(["get", self.jailName, "actionmethods",
|
self.transm.proceed(["get", self.jailName, "actionmethods",
|
||||||
action])[1],
|
action])[1],
|
||||||
['ban', 'start', 'stop', 'testmethod', 'unban'])
|
['ban', 'reban', 'start', 'stop', 'testmethod', 'unban'])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(["set", self.jailName, "action", action,
|
self.transm.proceed(["set", self.jailName, "action", action,
|
||||||
"testmethod", '{"text": "world!"}']),
|
"testmethod", '{"text": "world!"}']),
|
||||||
|
|
|
@ -22,6 +22,7 @@ __author__ = "Yaroslav Halchenko"
|
||||||
__copyright__ = "Copyright (c) 2013 Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2013 Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
|
import fileinput
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import optparse
|
import optparse
|
||||||
|
@ -741,11 +742,8 @@ class LogCaptureTestCase(unittest.TestCase):
|
||||||
self._dirty |= 2 # records changed
|
self._dirty |= 2 # records changed
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
||||||
# For extended testing of what gets output into logging
|
# For extended testing of what gets output into logging
|
||||||
# system, we will redirect it to a string
|
# system, we will redirect it to a string
|
||||||
logSys = getLogger("fail2ban")
|
|
||||||
|
|
||||||
# Keep old settings
|
# Keep old settings
|
||||||
self._old_level = logSys.level
|
self._old_level = logSys.level
|
||||||
self._old_handlers = logSys.handlers
|
self._old_handlers = logSys.handlers
|
||||||
|
@ -762,7 +760,6 @@ class LogCaptureTestCase(unittest.TestCase):
|
||||||
"""Call after every test case."""
|
"""Call after every test case."""
|
||||||
# print "O: >>%s<<" % self._log.getvalue()
|
# print "O: >>%s<<" % self._log.getvalue()
|
||||||
self.pruneLog()
|
self.pruneLog()
|
||||||
logSys = getLogger("fail2ban")
|
|
||||||
logSys.handlers = self._old_handlers
|
logSys.handlers = self._old_handlers
|
||||||
logSys.level = self._old_level
|
logSys.level = self._old_level
|
||||||
super(LogCaptureTestCase, self).tearDown()
|
super(LogCaptureTestCase, self).tearDown()
|
||||||
|
@ -845,5 +842,15 @@ class LogCaptureTestCase(unittest.TestCase):
|
||||||
def getLog(self):
|
def getLog(self):
|
||||||
return self._log.getvalue()
|
return self._log.getvalue()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def dumpFile(fn, handle=logSys.debug):
|
||||||
|
"""Helper which outputs content of the file at HEAVYDEBUG loglevels"""
|
||||||
|
if (handle != logSys.debug or logSys.getEffectiveLevel() <= logging.DEBUG):
|
||||||
|
handle('---- ' + fn + ' ----')
|
||||||
|
for line in fileinput.input(fn):
|
||||||
|
line = line.rstrip('\n')
|
||||||
|
handle(line)
|
||||||
|
handle('-'*30)
|
||||||
|
|
||||||
|
|
||||||
pid_exists = Utils.pid_exists
|
pid_exists = Utils.pid_exists
|
||||||
|
|
Loading…
Reference in New Issue