mirror of https://github.com/fail2ban/fail2ban
[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
parent
157a85451a
commit
1842f30359
|
@ -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 = {}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue