mirror of https://github.com/fail2ban/fail2ban
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 #10pull/2337/merge
parent
08393f9d82
commit
725354c793
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue