action info extended with new members for jail info (usable as tags in command actions):

`jail.found`, `jail.found_total` - current and total found failures
  `jail.banned`, `jail.banned_total` - current and total bans
closes #10
pull/2337/merge
sebres 2021-03-20 22:33:31 +01:00
parent 08393f9d82
commit 725354c793
4 changed files with 48 additions and 31 deletions

View File

@ -84,7 +84,7 @@ class Actions(JailThread, Mapping):
self._jail = jail self._jail = jail
self._actions = OrderedDict() self._actions = OrderedDict()
## The ban manager. ## The ban manager.
self.__banManager = BanManager() self.banManager = BanManager()
self.banEpoch = 0 self.banEpoch = 0
self.__lastConsistencyCheckTM = 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):
@ -203,7 +203,7 @@ class Actions(JailThread, Mapping):
def setBanTime(self, value): def setBanTime(self, value):
value = MyTime.str2seconds(value) value = MyTime.str2seconds(value)
self.__banManager.setBanTime(value) self.banManager.setBanTime(value)
logSys.info(" banTime: %s" % value) logSys.info(" banTime: %s" % value)
## ##
@ -212,10 +212,10 @@ class Actions(JailThread, Mapping):
# @return the time # @return the time
def getBanTime(self): def getBanTime(self):
return self.__banManager.getBanTime() return self.banManager.getBanTime()
def getBanned(self, ids): def getBanned(self, ids):
lst = self.__banManager.getBanList() lst = self.banManager.getBanList()
if not ids: if not ids:
return lst return lst
if len(ids) == 1: if len(ids) == 1:
@ -230,7 +230,7 @@ class Actions(JailThread, Mapping):
list list
The list of banned IP addresses. The list of banned IP addresses.
""" """
return self.__banManager.getBanList(ordered=True, withTime=withTime) return self.banManager.getBanList(ordered=True, withTime=withTime)
def addBannedIP(self, ip): def addBannedIP(self, ip):
"""Ban an IP or list of IPs.""" """Ban an IP or list of IPs."""
@ -282,7 +282,7 @@ class Actions(JailThread, Mapping):
if db and self._jail.database is not None: if db and self._jail.database is not None:
self._jail.database.delBan(self._jail, ip) self._jail.database.delBan(self._jail, ip)
# Find the ticket with the IP. # Find the ticket with the IP.
ticket = self.__banManager.getTicketByID(ip) ticket = self.banManager.getTicketByID(ip)
if ticket is not None: if ticket is not None:
# Unban the IP. # Unban the IP.
self.__unBan(ticket) self.__unBan(ticket)
@ -291,7 +291,7 @@ class Actions(JailThread, Mapping):
if not isinstance(ip, IPAddr): if not isinstance(ip, IPAddr):
ipa = IPAddr(ip) ipa = IPAddr(ip)
if not ipa.isSingle: # subnet (mask/cidr) or raw (may be dns/hostname): if not ipa.isSingle: # subnet (mask/cidr) or raw (may be dns/hostname):
ips = filter(ipa.contains, self.__banManager.getBanList()) ips = filter(ipa.contains, self.banManager.getBanList())
if ips: if ips:
return self.removeBannedIP(ips, db, ifexists) return self.removeBannedIP(ips, db, ifexists)
# not found: # not found:
@ -350,7 +350,7 @@ class Actions(JailThread, Mapping):
continue continue
# wait for ban (stop if gets inactive, pending ban or unban): # wait for ban (stop if gets inactive, pending ban or unban):
bancnt = 0 bancnt = 0
wt = min(self.sleeptime, self.__banManager._nextUnbanTime - MyTime.time()) wt = min(self.sleeptime, self.banManager._nextUnbanTime - MyTime.time())
logSys.log(5, "Actions: wait for pending tickets %s (default %s)", wt, self.sleeptime) logSys.log(5, "Actions: wait for pending tickets %s (default %s)", wt, self.sleeptime)
if Utils.wait_for(lambda: not self.active or self._jail.hasFailTickets, wt): if Utils.wait_for(lambda: not self.active or self._jail.hasFailTickets, wt):
bancnt = self.__checkBan() bancnt = self.__checkBan()
@ -397,7 +397,12 @@ class Actions(JailThread, Mapping):
"ipfailures": lambda self: self._mi4ip(True).getAttempt(), "ipfailures": lambda self: self._mi4ip(True).getAttempt(),
"ipjailfailures": lambda self: self._mi4ip().getAttempt(), "ipjailfailures": lambda self: self._mi4ip().getAttempt(),
# raw ticket info: # raw ticket info:
"raw-ticket": lambda self: repr(self.__ticket) "raw-ticket": lambda self: repr(self.__ticket),
# jail info:
"jail.banned": lambda self: self.__jail.actions.banManager.size(),
"jail.banned_total": lambda self: self.__jail.actions.banManager.getBanTotal(),
"jail.found": lambda self: self.__jail.filter.failManager.size(),
"jail.found_total": lambda self: self.__jail.filter.failManager.getFailTotal()
} }
__slots__ = CallingMap.__slots__ + ('__ticket', '__jail', '__mi4ip') __slots__ = CallingMap.__slots__ + ('__ticket', '__jail', '__mi4ip')
@ -494,11 +499,11 @@ class Actions(JailThread, Mapping):
for ticket in tickets: for ticket in tickets:
bTicket = BanTicket.wrap(ticket) bTicket = BanTicket.wrap(ticket)
btime = ticket.getBanTime(self.__banManager.getBanTime()) btime = ticket.getBanTime(self.banManager.getBanTime())
ip = bTicket.getIP() ip = bTicket.getIP()
aInfo = self._getActionInfo(bTicket) aInfo = self._getActionInfo(bTicket)
reason = {} reason = {}
if self.__banManager.addBanTicket(bTicket, reason=reason): if self.banManager.addBanTicket(bTicket, reason=reason):
cnt += 1 cnt += 1
# report ticket to observer, to check time should be increased and hereafter observer writes ban to database (asynchronous) # report ticket to observer, to check time should be increased and hereafter observer writes ban to database (asynchronous)
if Observers.Main is not None and not bTicket.restored: if Observers.Main is not None and not bTicket.restored:
@ -557,7 +562,7 @@ class Actions(JailThread, Mapping):
# and increase ticket time if "bantime.increment" set) # and increase ticket time if "bantime.increment" set)
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): def __reBan(self, ticket, actions=None, log=True):
@ -597,7 +602,7 @@ class Actions(JailThread, Mapping):
def _prolongBan(self, ticket): def _prolongBan(self, ticket):
# prevent to prolong ticket that was removed in-between, # prevent to prolong ticket that was removed in-between,
# if it in ban list - ban time already prolonged (and it stays there): # if it in ban list - ban time already prolonged (and it stays there):
if not self.__banManager._inBanList(ticket): return if not self.banManager._inBanList(ticket): return
# do actions : # do actions :
aInfo = None aInfo = None
for name, action in self._actions.iteritems(): for name, action in self._actions.iteritems():
@ -622,13 +627,13 @@ class Actions(JailThread, Mapping):
Unban IP addresses which are outdated. Unban IP addresses which are outdated.
""" """
lst = self.__banManager.unBanList(MyTime.time(), maxCount) lst = self.banManager.unBanList(MyTime.time(), maxCount)
for ticket in lst: for ticket in lst:
self.__unBan(ticket) self.__unBan(ticket)
cnt = len(lst) cnt = len(lst)
if cnt: if cnt:
logSys.debug("Unbanned %s, %s ticket(s) in %r", logSys.debug("Unbanned %s, %s ticket(s) in %r",
cnt, self.__banManager.size(), self._jail.name) cnt, self.banManager.size(), self._jail.name)
return cnt return cnt
def __flushBan(self, db=False, actions=None, stop=False): def __flushBan(self, db=False, actions=None, stop=False):
@ -642,10 +647,10 @@ class Actions(JailThread, Mapping):
log = True log = True
if actions is None: if actions is None:
logSys.debug(" Flush ban list") logSys.debug(" Flush ban list")
lst = self.__banManager.flushBanList() lst = self.banManager.flushBanList()
else: else:
log = False # don't log "[jail] Unban ..." if removing actions only. log = False # don't log "[jail] Unban ..." if removing actions only.
lst = iter(self.__banManager) lst = iter(self.banManager)
cnt = 0 cnt = 0
# first we'll execute flush for actions supporting this operation: # first we'll execute flush for actions supporting this operation:
unbactions = {} unbactions = {}
@ -682,7 +687,7 @@ class Actions(JailThread, Mapping):
self.__unBan(ticket, actions=actions, log=log) self.__unBan(ticket, actions=actions, log=log)
cnt += 1 cnt += 1
logSys.debug(" Unbanned %s, %s ticket(s) in %r", logSys.debug(" Unbanned %s, %s ticket(s) in %r",
cnt, self.__banManager.size(), self._jail.name) cnt, self.banManager.size(), self._jail.name)
return cnt return cnt
def __unBan(self, ticket, actions=None, log=True): def __unBan(self, ticket, actions=None, log=True):
@ -725,18 +730,18 @@ class Actions(JailThread, Mapping):
logSys.warning("Unsupported extended jail status flavor %r. Supported: %s" % (flavor, supported_flavors)) logSys.warning("Unsupported extended jail status flavor %r. Supported: %s" % (flavor, supported_flavors))
# Always print this information (basic) # Always print this information (basic)
if flavor != "short": if flavor != "short":
banned = self.__banManager.getBanList() banned = self.banManager.getBanList()
cnt = len(banned) cnt = len(banned)
else: else:
cnt = self.__banManager.size() cnt = self.banManager.size()
ret = [("Currently banned", cnt), ret = [("Currently banned", cnt),
("Total banned", self.__banManager.getBanTotal())] ("Total banned", self.banManager.getBanTotal())]
if flavor != "short": if flavor != "short":
ret += [("Banned IP list", banned)] ret += [("Banned IP list", banned)]
if flavor == "cymru": if flavor == "cymru":
cymru_info = self.__banManager.getBanListExtendedCymruInfo() cymru_info = self.banManager.getBanListExtendedCymruInfo()
ret += \ ret += \
[("Banned ASN list", self.__banManager.geBanListExtendedASN(cymru_info)), [("Banned ASN list", self.banManager.geBanListExtendedASN(cymru_info)),
("Banned Country list", self.__banManager.geBanListExtendedCountry(cymru_info)), ("Banned Country list", self.banManager.geBanListExtendedCountry(cymru_info)),
("Banned RIR list", self.__banManager.geBanListExtendedRIR(cymru_info))] ("Banned RIR list", self.banManager.geBanListExtendedRIR(cymru_info))]
return ret return ret

View File

@ -29,7 +29,7 @@ import tempfile
import sqlite3 import sqlite3
import shutil import shutil
from ..server.filter import FileContainer from ..server.filter import FileContainer, Filter
from ..server.mytime import MyTime from ..server.mytime import MyTime
from ..server.ticket import FailTicket from ..server.ticket import FailTicket
from ..server.actions import Actions, Utils from ..server.actions import Actions, Utils
@ -544,17 +544,21 @@ class DatabaseTest(LogCaptureTestCase):
self.testAddJail() # Jail required self.testAddJail() # Jail required
self.jail.database = self.db self.jail.database = self.db
self.db.addJail(self.jail) self.db.addJail(self.jail)
actions = Actions(self.jail) actions = self.jail.actions
actions.add( actions.add(
"action_checkainfo", "action_checkainfo",
os.path.join(TEST_FILES_DIR, "action.d/action_checkainfo.py"), os.path.join(TEST_FILES_DIR, "action.d/action_checkainfo.py"),
{}) {})
actions.banManager.setBanTotal(20)
self.jail._Jail__filter = flt = Filter(self.jail)
flt.failManager.setFailTotal(50)
ticket = FailTicket("1.2.3.4") ticket = FailTicket("1.2.3.4")
ticket.setAttempt(5) ticket.setAttempt(5)
ticket.setMatches(['test', 'test']) ticket.setMatches(['test', 'test'])
self.jail.putFailTicket(ticket) self.jail.putFailTicket(ticket)
actions._Actions__checkBan() actions._Actions__checkBan()
self.assertLogged("ban ainfo %s, %s, %s, %s" % (True, True, True, True)) self.assertLogged("ban ainfo %s, %s, %s, %s" % (True, True, True, True))
self.assertLogged("jail info %d, %d, %d, %d" % (1, 21, 0, 50))
def testDelAndAddJail(self): def testDelAndAddJail(self):
self.testAddJail() # Add jail self.testAddJail() # Add jail

View File

@ -1410,8 +1410,9 @@ class Fail2banServerTest(Fail2banClientServerBase):
'jails': ( 'jails': (
# default: # default:
'''test_action = dummy[actionstart_on_demand=1, init="start: %(__name__)s", target="%(tmp)s/test.txt", '''test_action = dummy[actionstart_on_demand=1, init="start: %(__name__)s", target="%(tmp)s/test.txt",
actionban='<known/actionban>; actionban='<known/actionban>; echo "found: <jail.found> / <jail.found_total>, banned: <jail.banned> / <jail.banned_total>"
echo "<matches>"; printf "=====\\n%%b\\n=====\\n\\n" "<matches>" >> <target>']''', echo "<matches>"; printf "=====\\n%%b\\n=====\\n\\n" "<matches>" >> <target>',
actionstop='<known/actionstop>; echo "stats <name> - found: <jail.found_total>, banned: <jail.banned_total>"']''',
# jail sendmail-auth: # jail sendmail-auth:
'[sendmail-auth]', '[sendmail-auth]',
'backend = polling', 'backend = polling',
@ -1456,7 +1457,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
_write_file(lgfn, "w+", *smaut_msg) _write_file(lgfn, "w+", *smaut_msg)
# wait and check it caused banned (and dump in the test-file): # wait and check it caused banned (and dump in the test-file):
self.assertLogged( self.assertLogged(
"[sendmail-auth] Ban 192.0.2.1", "1 ticket(s) in 'sendmail-auth'", all=True, wait=MID_WAITTIME) "[sendmail-auth] Ban 192.0.2.1", "stdout: 'found: 0 / 3, banned: 1 / 1'",
"1 ticket(s) in 'sendmail-auth'", all=True, wait=MID_WAITTIME)
_out_file(tofn) _out_file(tofn)
td = _read_file(tofn) td = _read_file(tofn)
# check matches (maxmatches = 2, so only 2 & 3 available): # check matches (maxmatches = 2, so only 2 & 3 available):
@ -1470,7 +1472,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
_write_file(lgfn, "w+", *smrej_msg) _write_file(lgfn, "w+", *smrej_msg)
# wait and check it caused banned (and dump in the test-file): # wait and check it caused banned (and dump in the test-file):
self.assertLogged( self.assertLogged(
"[sendmail-reject] Ban 192.0.2.2", "1 ticket(s) in 'sendmail-reject'", all=True, wait=MID_WAITTIME) "[sendmail-reject] Ban 192.0.2.2", "stdout: 'found: 0 / 3, banned: 1 / 1'",
"1 ticket(s) in 'sendmail-reject'", all=True, wait=MID_WAITTIME)
_out_file(tofn) _out_file(tofn)
td = _read_file(tofn) td = _read_file(tofn)
# check matches (no maxmatches, so all matched messages are available): # check matches (no maxmatches, so all matched messages are available):
@ -1484,6 +1487,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
# wait a bit: # wait a bit:
self.assertLogged( self.assertLogged(
"Reload finished.", "Reload finished.",
"stdout: 'stats sendmail-auth - found: 3, banned: 1'",
"stdout: 'stats sendmail-reject - found: 3, banned: 1'",
"[sendmail-auth] Restore Ban 192.0.2.1", "1 ticket(s) in 'sendmail-auth'", all=True, wait=MID_WAITTIME) "[sendmail-auth] Restore Ban 192.0.2.1", "1 ticket(s) in 'sendmail-auth'", all=True, wait=MID_WAITTIME)
# check matches again - (dbmaxmatches = 1), so it should be only last match after restart: # check matches again - (dbmaxmatches = 1), so it should be only last match after restart:
td = _read_file(tofn) td = _read_file(tofn)

View File

@ -8,6 +8,9 @@ class TestAction(ActionBase):
self._logSys.info("ban ainfo %s, %s, %s, %s", self._logSys.info("ban ainfo %s, %s, %s, %s",
aInfo["ipmatches"] != '', aInfo["ipjailmatches"] != '', aInfo["ipfailures"] > 0, aInfo["ipjailfailures"] > 0 aInfo["ipmatches"] != '', aInfo["ipjailmatches"] != '', aInfo["ipfailures"] > 0, aInfo["ipjailfailures"] > 0
) )
self._logSys.info("jail info %d, %d, %d, %d",
aInfo["jail.banned"], aInfo["jail.banned_total"], aInfo["jail.found"], aInfo["jail.found_total"]
)
def unban(self, aInfo): def unban(self, aInfo):
pass pass