[ticket] remove unneeded code - ticket will be just wrapped from FailTicket to BanTicket;

normalize increment of ban-count or time (count increased in BanManager now, some dual increments fixed in the test-cases);
introduced new action-tag `<bancount>`, that is always incremented by each ban (starting by 1), opposite to tag `<bantime>` which can be prolonged retarded (up to 10 seconds)
pull/1460/head
sebres 2017-05-17 19:54:52 +02:00
parent 157a85451a
commit 1842f30359
7 changed files with 43 additions and 61 deletions

View File

@ -34,7 +34,7 @@ try:
except ImportError: except ImportError:
OrderedDict = dict OrderedDict = dict
from .banmanager import BanManager from .banmanager import BanManager, BanTicket
from .ipdns import DNSUtils from .ipdns import DNSUtils
from .jailthread import JailThread from .jailthread import JailThread
from .action import ActionBase, CommandAction, CallingMap from .action import ActionBase, CommandAction, CallingMap
@ -299,6 +299,7 @@ class Actions(JailThread, Mapping):
"failures": lambda self: self.__ticket.getAttempt(), "failures": lambda self: self.__ticket.getAttempt(),
"time": lambda self: self.__ticket.getTime(), "time": lambda self: self.__ticket.getTime(),
"bantime": lambda self: self._getBanTime(), "bantime": lambda self: self._getBanTime(),
"bancount": lambda self: self.__ticket.getBanCount(),
"matches": lambda self: "\n".join(self.__ticket.getMatches()), "matches": lambda self: "\n".join(self.__ticket.getMatches()),
# to bypass actions, that should not be executed for restored tickets # to bypass actions, that should not be executed for restored tickets
"restored": lambda self: (1 if self.__ticket.restored else 0), "restored": lambda self: (1 if self.__ticket.restored else 0),
@ -396,15 +397,9 @@ class Actions(JailThread, Mapping):
ticket = self._jail.getFailTicket() ticket = self._jail.getFailTicket()
if not ticket: if not ticket:
break break
bTicket = BanManager.createBanTicket(ticket)
btime = ticket.getBanTime() bTicket = BanTicket.wrap(ticket)
if btime is not None: btime = ticket.getBanTime(self.__banManager.getBanTime())
bTicket.setBanTime(btime)
bTicket.setBanCount(ticket.getBanCount())
else:
btime = self.__banManager.getBanTime()
if ticket.restored:
bTicket.restored = True
ip = bTicket.getIP() ip = bTicket.getIP()
aInfo = self.__getActionInfo(bTicket) aInfo = self.__getActionInfo(bTicket)
reason = {} reason = {}

View File

@ -243,21 +243,6 @@ class BanManager:
logSys.exception(e) logSys.exception(e)
return [] return []
##
# Create a ban ticket.
#
# Create a BanTicket from a FailTicket. The timestamp of the BanTicket
# is the current time. This is a static method.
# @param ticket the FailTicket
# @return a BanTicket
@staticmethod
def createBanTicket(ticket):
# we should always use correct time to calculate correct end time (ban time is variable now,
# + possible double banning by restore from database and from log file)
# so use as lastTime always time from ticket.
return BanTicket.wrap(ticket)
## ##
# Add a ban ticket. # Add a ban ticket.
# #
@ -291,6 +276,7 @@ class BanManager:
# not yet banned - add new one: # not yet banned - add new one:
self.__banList[fid] = ticket self.__banList[fid] = ticket
self.__banTotal += 1 self.__banTotal += 1
ticket.incrBanCount()
# correct next unban time: # correct next unban time:
if self.__nextUnbanTime > eob: if self.__nextUnbanTime > eob:
self.__nextUnbanTime = eob self.__nextUnbanTime = eob

View File

@ -377,6 +377,7 @@ class ObserverThread(JailThread):
db = jail.database db = jail.database
if db is not None: if db is not None:
for banCount, timeOfBan, lastBanTime in db.getBan(ip, jail): for banCount, timeOfBan, lastBanTime in db.getBan(ip, jail):
banCount = max(banCount, ticket.getBanCount())
retryCount = ((1 << (banCount if banCount < 20 else 20))/2 + 1) retryCount = ((1 << (banCount if banCount < 20 else 20))/2 + 1)
# if lastBanTime == -1 or timeOfBan + lastBanTime * 2 > MyTime.time(): # if lastBanTime == -1 or timeOfBan + lastBanTime * 2 > MyTime.time():
# retryCount = maxRetry # retryCount = maxRetry
@ -396,8 +397,8 @@ class ObserverThread(JailThread):
(', Ban' if retryCount >= maxRetry else '')) (', Ban' if retryCount >= maxRetry else ''))
# retryCount-1, because a ticket was already once incremented by filter self # retryCount-1, because a ticket was already once incremented by filter self
retryCount = failManager.addFailure(ticket, retryCount - 1, True) retryCount = failManager.addFailure(ticket, retryCount - 1, True)
ticket.setBanCount(banCount)
# after observe we have increased count >= maxretry ... # after observe we have increased attempt count, compare it >= maxretry ...
if retryCount >= maxRetry: if retryCount >= maxRetry:
# perform the banning of the IP now (again) # perform the banning of the IP now (again)
# [todo]: this code part will be used multiple times - optimize it later. # [todo]: this code part will be used multiple times - optimize it later.
@ -442,12 +443,14 @@ class ObserverThread(JailThread):
for banCount, timeOfBan, lastBanTime in \ for banCount, timeOfBan, lastBanTime in \
jail.database.getBan(ip, jail, overalljails=be.get('overalljails', False)) \ jail.database.getBan(ip, jail, overalljails=be.get('overalljails', False)) \
: :
# increment count in ticket (if still not increased from banmanager, test-cases?):
if banCount >= ticket.getBanCount():
ticket.setBanCount(banCount+1)
logSys.debug('IP %s was already banned: %s #, %s', ip, banCount, timeOfBan); logSys.debug('IP %s was already banned: %s #, %s', ip, banCount, timeOfBan);
ticket.setBanCount(banCount);
# calculate new ban time # calculate new ban time
if banCount > 0: if banCount > 0:
banTime = be['evformula'](self.BanTimeIncr(banTime, banCount)) banTime = be['evformula'](self.BanTimeIncr(banTime, banCount))
ticket.setBanTime(banTime); ticket.setBanTime(banTime)
# check current ticket time to prevent increasing for twice read tickets (restored from log file besides database after restart) # check current ticket time to prevent increasing for twice read tickets (restored from log file besides database after restart)
if ticket.getTime() > timeOfBan: if ticket.getTime() > timeOfBan:
logSys.info('[%s] IP %s is bad: %s # last %s - incr %s to %s' % (jail.name, ip, banCount, logSys.info('[%s] IP %s is bad: %s # last %s - incr %s to %s' % (jail.name, ip, banCount,
@ -466,12 +469,14 @@ class ObserverThread(JailThread):
Observer will check ip was known (bad) and possibly increase/prolong a ban time 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 Secondary we will actualize the bans and bips (bad ip) in database
""" """
if ticket.restored: # pragma: no cover (normally not resored tickets only)
return
try: try:
oldbtime = btime oldbtime = btime
ip = ticket.getIP() ip = ticket.getIP()
logSys.debug("[%s] Observer: ban found %s, %s", jail.name, ip, btime) 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 not permanent and ban time was not set - check time should be increased:
if btime != -1 and not ticket.restored and ticket.getBanTime() is None: if btime != -1 and ticket.getBanTime() is None:
btime = self.incrBanTime(jail, btime, ticket) btime = self.incrBanTime(jail, btime, ticket)
# if we should prolong ban time: # if we should prolong ban time:
if btime == -1 or btime > oldbtime: if btime == -1 or btime > oldbtime:
@ -487,15 +492,13 @@ class ObserverThread(JailThread):
return False return False
else: else:
logtime = ('permanent', 'infinite') logtime = ('permanent', 'infinite')
# increment count:
ticket.incrBanCount()
# if ban time was prolonged - log again with new ban time: # if ban time was prolonged - log again with new ban time:
if btime != oldbtime: if btime != oldbtime:
logSys.notice("[%s] Increase Ban %s (%d # %s -> %s)", jail.name, logSys.notice("[%s] Increase Ban %s (%d # %s -> %s)", jail.name,
ip, ticket.getBanCount(), *logtime) ip, ticket.getBanCount(), *logtime)
# delayed prolonging ticket via actions that expected this: # delayed prolonging ticket via actions that expected this (not later than 10 sec):
logSys.log(5, "[%s] Observer: prolong %s in %s", jail.name, ip, (btime, oldbtime)) 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) self.add_timer(min(10, max(0, btime - oldbtime - 5)), self.prolongBan, ticket, jail)
# add ticket to database, but only if was not restored (not already read from database): # 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: 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: # add to database always only after ban time was calculated an not yet already banned:

View File

@ -106,16 +106,17 @@ class Ticket(object):
return self._time return self._time
def setBanTime(self, value): def setBanTime(self, value):
self._banTime = value; self._banTime = value
def getBanTime(self, defaultBT=None): def getBanTime(self, defaultBT=None):
return (self._banTime if self._banTime is not None else defaultBT) return (self._banTime if self._banTime is not None else defaultBT)
def setBanCount(self, value): def setBanCount(self, value, always=False):
self._banCount = value; if always or value > self._banCount:
self._banCount = value
def incrBanCount(self, value = 1): def incrBanCount(self, value=1):
self._banCount += value; self._banCount += value
def getBanCount(self): def getBanCount(self):
return self._banCount; return self._banCount;

View File

@ -100,21 +100,22 @@ class AddFailure(unittest.TestCase):
def testBanTimeIncr(self): def testBanTimeIncr(self):
ticket = BanTicket(self.__ticket.getIP(), self.__ticket.getTime()) ticket = BanTicket(self.__ticket.getIP(), self.__ticket.getTime())
## increase twice and at end permanent: ## increase twice and at end permanent, check time/count increase:
c = 0
for i in (1000, 2000, -1): for i in (1000, 2000, -1):
self.__banManager.addBanTicket(self.__ticket) self.__banManager.addBanTicket(self.__ticket); c += 1
ticket.setBanTime(i) ticket.setBanTime(i)
self.assertFalse(self.__banManager.addBanTicket(ticket)) self.assertFalse(self.__banManager.addBanTicket(ticket)); # no incr of c (already banned)
self.assertEqual(str(self.__banManager.getTicketByID(ticket.getIP())), self.assertEqual(str(self.__banManager.getTicketByID(ticket.getIP())),
"BanTicket: ip=%s time=%s bantime=%s bancount=0 #attempts=0 matches=[]" % (ticket.getIP(), ticket.getTime(), i)) "BanTicket: ip=%s time=%s bantime=%s bancount=%s #attempts=0 matches=[]" % (ticket.getIP(), ticket.getTime(), i, c))
## after permanent, it should remain permanent ban time (-1): ## after permanent, it should remain permanent ban time (-1):
self.__banManager.addBanTicket(self.__ticket) self.__banManager.addBanTicket(self.__ticket); c += 1
ticket.setBanTime(-1) ticket.setBanTime(-1)
self.assertFalse(self.__banManager.addBanTicket(ticket)) self.assertFalse(self.__banManager.addBanTicket(ticket)); # no incr of c (already banned)
ticket.setBanTime(1000) ticket.setBanTime(1000)
self.assertFalse(self.__banManager.addBanTicket(ticket)) self.assertFalse(self.__banManager.addBanTicket(ticket)); # no incr of c (already banned)
self.assertEqual(str(self.__banManager.getTicketByID(ticket.getIP())), self.assertEqual(str(self.__banManager.getTicketByID(ticket.getIP())),
"BanTicket: ip=%s time=%s bantime=%s bancount=0 #attempts=0 matches=[]" % (ticket.getIP(), ticket.getTime(), -1)) "BanTicket: ip=%s time=%s bantime=%s bancount=%s #attempts=0 matches=[]" % (ticket.getIP(), ticket.getTime(), -1, c))
def testUnban(self): def testUnban(self):
btime = self.__banManager.getBanTime() btime = self.__banManager.getBanTime()

View File

@ -1195,8 +1195,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
"[DEFAULT]", "[DEFAULT]",
"", "",
"[Definition]", "[Definition]",
"actionban = printf %%s \"[%(name)s] %(actname)s: ++ ban <ip> -t <bantime> : <F-MSG>\"", \ "actionban = printf %%s \"[%(name)s] %(actname)s: ++ ban <ip> -c <bancount> -t <bantime> : <F-MSG>\"", \
"actionprolong = printf %%s \"[%(name)s] %(actname)s: ++ prolong <ip> -t <bantime> : <F-MSG>\"" \ "actionprolong = printf %%s \"[%(name)s] %(actname)s: ++ prolong <ip> -c <bancount> -t <bantime> : <F-MSG>\"" \
if prolong else "", if prolong else "",
"actionunban = printf %%b '[%(name)s] %(actname)s: -- unban <ip>'", "actionunban = printf %%b '[%(name)s] %(actname)s: -- unban <ip>'",
) )
@ -1241,8 +1241,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
# wait for ban: # wait for ban:
_observer_wait_idle() _observer_wait_idle()
self.assertLogged( self.assertLogged(
"stdout: '[test-jail1] test-action1: ++ ban 192.0.2.11 -t 300 : ", "stdout: '[test-jail1] test-action1: ++ ban 192.0.2.11 -c 1 -t 300 : ",
"stdout: '[test-jail1] test-action2: ++ ban 192.0.2.11 -t 300 : ", "stdout: '[test-jail1] test-action2: ++ ban 192.0.2.11 -c 1 -t 300 : ",
all=True, wait=MID_WAITTIME) all=True, wait=MID_WAITTIME)
# wait for observer idle (write all tickets to db): # wait for observer idle (write all tickets to db):
_observer_wait_idle() _observer_wait_idle()
@ -1269,8 +1269,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
)) ))
# wait for ban: # wait for ban:
self.assertLogged( self.assertLogged(
"stdout: '[test-jail1] test-action1: ++ ban 192.0.2.11 -t 300 : ", "stdout: '[test-jail1] test-action1: ++ ban 192.0.2.11 -c 2 -t 300 : ",
"stdout: '[test-jail1] test-action2: ++ ban 192.0.2.11 -t 300 : ", "stdout: '[test-jail1] test-action2: ++ ban 192.0.2.11 -c 2 -t 300 : ",
all=True, wait=MID_WAITTIME) all=True, wait=MID_WAITTIME)
# unblock observer here and wait it is done: # unblock observer here and wait it is done:
wakeObs = True wakeObs = True
@ -1283,5 +1283,5 @@ class Fail2banServerTest(Fail2banClientServerBase):
_observer_wait_idle() _observer_wait_idle()
# wait for prolong: # wait for prolong:
self.assertLogged( self.assertLogged(
"stdout: '[test-jail1] test-action2: ++ prolong 192.0.2.11 -t 600 : ", "stdout: '[test-jail1] test-action2: ++ prolong 192.0.2.11 -c 2 -t 600 : ",
all=True, wait=MID_WAITTIME) all=True, wait=MID_WAITTIME)

View File

@ -31,9 +31,8 @@ import tempfile
import time import time
from ..server.mytime import MyTime from ..server.mytime import MyTime
from ..server.ticket import FailTicket from ..server.ticket import FailTicket, BanTicket
from ..server.failmanager import FailManager from ..server.failmanager import FailManager
from ..server.banmanager import BanManager
from ..server.observer import Observers, ObserverThread from ..server.observer import Observers, ObserverThread
from ..server.utils import Utils from ..server.utils import Utils
from .utils import LogCaptureTestCase from .utils import LogCaptureTestCase
@ -246,7 +245,6 @@ class BanTimeIncrDB(unittest.TestCase):
# incr time and ban a ticket again : # incr time and ban a ticket again :
ticket.setTime(stime + 15) ticket.setTime(stime + 15)
self.assertEqual(self.incrBanTime(ticket, 10), 20) self.assertEqual(self.incrBanTime(ticket, 10), 20)
ticket.incrBanCount()
self.db.addBan(jail, ticket) self.db.addBan(jail, ticket)
# get a ticket already banned in this jail: # get a ticket already banned in this jail:
self.assertEqual( self.assertEqual(
@ -292,7 +290,6 @@ class BanTimeIncrDB(unittest.TestCase):
ticket.setTime(stime + lastBanTime + 5) ticket.setTime(stime + lastBanTime + 5)
banTime = self.incrBanTime(ticket, 10) banTime = self.incrBanTime(ticket, 10)
self.assertEqual(banTime, lastBanTime * 2) self.assertEqual(banTime, lastBanTime * 2)
ticket.incrBanCount()
self.db.addBan(jail, ticket) self.db.addBan(jail, ticket)
lastBanTime = banTime lastBanTime = banTime
# increase again, but the last multiplier reached (time not increased): # increase again, but the last multiplier reached (time not increased):
@ -300,7 +297,6 @@ class BanTimeIncrDB(unittest.TestCase):
banTime = self.incrBanTime(ticket, 10) banTime = self.incrBanTime(ticket, 10)
self.assertNotEqual(banTime, lastBanTime * 2) self.assertNotEqual(banTime, lastBanTime * 2)
self.assertEqual(banTime, lastBanTime) self.assertEqual(banTime, lastBanTime)
ticket.incrBanCount()
self.db.addBan(jail, ticket) self.db.addBan(jail, ticket)
lastBanTime = banTime lastBanTime = banTime
# add two tickets from yesterday: one unbanned (bantime already out-dated): # add two tickets from yesterday: one unbanned (bantime already out-dated):
@ -500,7 +496,7 @@ class BanTimeIncrDB(unittest.TestCase):
# wrap FailTicket to BanTicket: # wrap FailTicket to BanTicket:
failticket2 = ticket2 failticket2 = ticket2
ticket2 = BanManager.createBanTicket(failticket2) ticket2 = BanTicket.wrap(failticket2)
self.assertEqual(ticket2, failticket2) self.assertEqual(ticket2, failticket2)
# add this ticket to ban (use observer only without ban manager): # add this ticket to ban (use observer only without ban manager):
obs.add('banFound', ticket2, jail, 10) obs.add('banFound', ticket2, jail, 10)