Merge pull request #2315 from psvi/ban_list

New command `fail2ban-client get <JAIL> banip`
pull/2319/merge
Sergey G. Brester 6 years ago committed by GitHub
commit 53684af0e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -56,6 +56,7 @@ ver. 0.11.0-dev-0 (20??/??/??) - development nightly edition
end of ban) of the ticket with ban-time of jail (as maximum), for all tickets with ban-time greater
(or persistent); not affected if ban-time of the jail is unchanged between stop/start.
* added new setup-option `--without-tests` to skip building and installing of tests files (gh-2287).
* added new command `fail2ban-client get <JAIL> banip ?sep-char|--with-time?` to get the banned ip addresses (gh-1916).
ver. 0.10.4-dev-1 (20??/??/??) - development edition

@ -180,6 +180,12 @@ class Beautifier:
msg = "The jail %s action %s has the following " \
"methods:\n" % (inC[1], inC[3])
msg += ", ".join(response)
elif inC[2] == "banip" and inC[0] == "get":
if isinstance(response, list):
sep = " " if len(inC) <= 3 else inC[3]
if sep == "--with-time":
sep = "\n"
msg = sep.join(response)
except Exception:
logSys.warning("Beautifier error. Please report the error")
logSys.error("Beautify %r with %r failed", response, self.__inputCmd,

@ -128,6 +128,7 @@ protocol = [
["get <JAIL> bantime", "gets the time a host is banned for <JAIL>"],
["get <JAIL> datepattern", "gets the patern used to match date/times for <JAIL>"],
["get <JAIL> usedns", "gets the usedns setting for <JAIL>"],
["get <JAIL> banip [<SEP>|--with-time]", "gets the list of of banned IP addresses for <JAIL>. Optionally the separator character ('<SEP>', default is space) or the option '--with-time' (printing the times of ban) may be specified. The IPs are ordered by end of ban."],
["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"],
["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"],
["get <JAIL> actions", "gets a list of actions for <JAIL>"],

@ -204,6 +204,16 @@ class Actions(JailThread, Mapping):
def getBanTime(self):
return self.__banManager.getBanTime()
def getBanList(self, withTime=False):
"""Returns the list of banned IP addresses.
Returns
-------
list
The list of banned IP addresses.
"""
return self.__banManager.getBanList(ordered=True, withTime=withTime)
def removeBannedIP(self, ip=None, db=True, ifexists=False):
"""Removes banned IP calling actions' unban method

@ -102,9 +102,22 @@ class BanManager:
#
# @return IP list
def getBanList(self):
def getBanList(self, ordered=False, withTime=False):
with self.__lock:
if not ordered:
return self.__banList.keys()
lst = []
for ticket in self.__banList.itervalues():
eob = ticket.getEndOfBanTime(self.__banTime)
lst.append((ticket,eob))
lst.sort(key=lambda t: t[1])
t2s = MyTime.time2str
if withTime:
return ['%s \t%s + %d = %s' % (
t[0].getID(),
t2s(t[0].getTime()), t[0].getBanTime(self.__banTime), t2s(t[1])
) for t in lst]
return [t[0].getID() for t in lst]
##
# Returns a iterator to ban list (used in reload, so idle).

@ -602,7 +602,7 @@ class Filter(JailThread):
if self._inIgnoreIPList(ip, tick):
continue
logSys.info(
"[%s] Found %s - %s", self.jailName, ip, datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S")
"[%s] Found %s - %s", self.jailName, ip, MyTime.time2str(unixTime)
)
self.failManager.addFailure(tick)
# report to observer - failure was found, for possibly increasing of it retry counter (asynchronous)
@ -1092,7 +1092,7 @@ class FileFilter(Filter):
fs = container.getFileSize()
if logSys.getEffectiveLevel() <= logging.DEBUG:
logSys.debug("Seek to find time %s (%s), file size %s", date,
datetime.datetime.fromtimestamp(date).strftime("%Y-%m-%d %H:%M:%S"), fs)
MyTime.time2str(date), fs)
minp = container.getPos()
maxp = fs
tryPos = minp
@ -1171,7 +1171,7 @@ class FileFilter(Filter):
container.setPos(foundPos)
if logSys.getEffectiveLevel() <= logging.DEBUG:
logSys.debug("Position %s from %s, found time %s (%s) within %s seeks", lastPos, fs, foundTime,
(datetime.datetime.fromtimestamp(foundTime).strftime("%Y-%m-%d %H:%M:%S") if foundTime is not None else ''), cntr)
(MyTime.time2str(foundTime) if foundTime is not None else ''), cntr)
def status(self, flavor="basic"):
"""Status of Filter plus files being monitored.

@ -114,6 +114,16 @@ class MyTime:
else:
return time.localtime(MyTime.myTime)
@staticmethod
def time2str(unixTime, format="%Y-%m-%d %H:%M:%S"):
"""Convert time to a string representing as date and time using given format.
Default format is ISO 8601, YYYY-MM-DD HH:MM:SS without microseconds.
@return ISO-capable string representation of given unixTime
"""
return datetime.datetime.fromtimestamp(
unixTime).replace(microsecond=0).strftime(format)
## precreate/precompile primitives used in str2seconds:
## preparing expression:

@ -393,7 +393,7 @@ class ObserverThread(JailThread):
return
# retry counter was increased - add it again:
logSys.info("[%s] Found %s, bad - %s, %s # -> %s%s", jail.name, ip,
datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S"), banCount, retryCount,
MyTime.time2str(unixTime), banCount, retryCount,
(', Ban' if retryCount >= maxRetry else ''))
# retryCount-1, because a ticket was already once incremented by filter self
retryCount = failManager.addFailure(ticket, retryCount - 1, True)
@ -454,7 +454,7 @@ class ObserverThread(JailThread):
# check current ticket time to prevent increasing for twice read tickets (restored from log file besides database after restart)
if ticket.getTime() > timeOfBan:
logSys.info('[%s] IP %s is bad: %s # last %s - incr %s to %s' % (jail.name, ip, banCount,
datetime.datetime.fromtimestamp(timeOfBan).strftime("%Y-%m-%d %H:%M:%S"),
MyTime.time2str(timeOfBan),
datetime.timedelta(seconds=int(orgBanTime)), datetime.timedelta(seconds=int(banTime))));
else:
ticket.restored = True
@ -485,7 +485,7 @@ class ObserverThread(JailThread):
if btime != -1:
bendtime = ticket.getTime() + btime
logtime = (datetime.timedelta(seconds=int(btime)),
datetime.datetime.fromtimestamp(bendtime).strftime("%Y-%m-%d %H:%M:%S"))
MyTime.time2str(bendtime))
# check ban is not too old :
if bendtime < MyTime.time():
logSys.debug('Ignore old bantime %s', logtime[1])

@ -510,6 +510,21 @@ class Server:
def getBanTime(self, name):
return self.__jails[name].actions.getBanTime()
def getBanList(self, name, withTime=False):
"""Returns the list of banned IP addresses for a jail.
Parameters
----------
name : str
The name of a jail.
Returns
-------
list
The list of banned IP addresses.
"""
return self.__jails[name].actions.getBanList(withTime)
def setBanTimeExtra(self, name, opt, value):
self.__jails[name].setBanTimeExtra(opt, value)

@ -390,6 +390,9 @@ class Transmitter:
# Action
elif command[1] == "bantime":
return self.__server.getBanTime(name)
elif command[1] == "banip":
return self.__server.getBanList(name,
withTime=len(command) > 2 and command[2] == "--with-time")
elif command[1].startswith("bantime."):
opt = command[1][len("bantime."):]
return self.__server.getBanTimeExtra(name, opt)

@ -1064,6 +1064,17 @@ class Fail2banServerTest(Fail2banClientServerBase):
"stdout: '[test-jail2] test-action3: ++ ban 192.0.2.22",
"stdout: '[test-jail2] test-action3: ++ ban 192.0.2.22 ", all=True, wait=MID_WAITTIME)
# get banned ips:
_observer_wait_idle()
self.pruneLog("[test-phase 2d.1]")
self.execCmd(SUCCESS, startparams, "get", "test-jail2", "banip", "\n")
self.assertLogged(
"192.0.2.4", "192.0.2.8", "192.0.2.21", "192.0.2.22", all=True, wait=MID_WAITTIME)
self.pruneLog("[test-phase 2d.2]")
self.execCmd(SUCCESS, startparams, "get", "test-jail1", "banip")
self.assertLogged(
"192.0.2.1", "192.0.2.2", "192.0.2.3", "192.0.2.4", "192.0.2.8", all=True, wait=MID_WAITTIME)
# restart jail with unban all:
self.pruneLog("[test-phase 2e]")
self.execCmd(SUCCESS, startparams,
@ -1397,6 +1408,11 @@ class Fail2banServerTest(Fail2banClientServerBase):
"stdout: '[test-jail1] test-action1: ++ ban 192.0.2.11 -c 2 -t 300 : ",
"stdout: '[test-jail1] test-action2: ++ ban 192.0.2.11 -c 2 -t 300 : ",
all=True, wait=MID_WAITTIME)
# get banned ips with time:
self.pruneLog("[test-phase 2) time+10m - get-ips]")
self.execCmd(SUCCESS, startparams, "get", "test-jail1", "banip", "--with-time")
self.assertLogged(
"192.0.2.11", "+ 300 =", all=True, wait=MID_WAITTIME)
# unblock observer here and wait it is done:
wakeObs = True
_observer_wait_idle()
@ -1411,6 +1427,13 @@ class Fail2banServerTest(Fail2banClientServerBase):
"stdout: '[test-jail1] test-action2: ++ prolong 192.0.2.11 -c 2 -t 600 : ",
all=True, wait=MID_WAITTIME)
# get banned ips with time:
_observer_wait_idle()
self.pruneLog("[test-phase 2) time+11m - get-ips]")
self.execCmd(SUCCESS, startparams, "get", "test-jail1", "banip", "--with-time")
self.assertLogged(
"192.0.2.11", "+ 600 =", all=True, wait=MID_WAITTIME)
# test multiple start/stop of the server (threaded in foreground) --
if False: # pragma: no cover
@with_foreground_server_thread()

@ -94,7 +94,7 @@ class _tmSerial():
@staticmethod
def _tm(time):
# ## strftime it too slow for large time serializer :
# return datetime.datetime.fromtimestamp(time).strftime("%Y-%m-%d %H:%M:%S")
# return MyTime.time2str(time)
c = _tmSerial
sec = (time % 60)
if c._last_s == time - sec:
@ -306,7 +306,7 @@ class BasicFilter(unittest.TestCase):
unittest.F2B.SkipIfFast()
## test function "_tm" works correct (returns the same as slow strftime):
for i in xrange(1417512352, (1417512352 // 3600 + 3) * 3600):
tm = datetime.datetime.fromtimestamp(i).strftime("%Y-%m-%d %H:%M:%S")
tm = MyTime.time2str(i)
if _tm(i) != tm: # pragma: no cover - never reachable
self.assertEqual((_tm(i), i), (tm, i))

@ -347,6 +347,46 @@ class Transmitter(TransmitterBase):
self.transm.proceed(
["set", self.jailName, "unbanip", "192.168.1.1"])[0],1)
def testJailBanList(self):
jail = "TestJailBanList"
self.server.addJail(jail, FAST_BACKEND)
self.server.startJail(jail)
# Helper to process set banip/set unbanip commands and compare the list of
# banned IP addresses with outList.
def _getBanListTest(jail, banip=None, unbanip=None, outList=[]):
# Ban IP address
if banip is not None:
self.assertEqual(
self.transm.proceed(["set", jail, "banip", banip]),
(0, banip))
self.assertLogged("Ban %s" % banip, wait=True) # Give chance to ban
# Unban IP address
if unbanip is not None:
self.assertEqual(
self.transm.proceed(["set", jail, "unbanip", unbanip]),
(0, unbanip))
self.assertLogged("Unban %s" % unbanip, wait=True) # Give chance to unban
# Compare the list of banned IP addresses with outList
self.assertSortedEqual(
self.transm.proceed(["get", jail, "banip"]),
(0, outList))
_getBanListTest(jail,
outList=[])
_getBanListTest(jail, banip="127.0.0.1",
outList=["127.0.0.1"])
_getBanListTest(jail, banip="192.168.0.1",
outList=["127.0.0.1", "192.168.0.1"])
_getBanListTest(jail, banip="192.168.1.10",
outList=["127.0.0.1", "192.168.0.1", "192.168.1.10"])
_getBanListTest(jail, unbanip="127.0.0.1",
outList=["192.168.0.1", "192.168.1.10"])
_getBanListTest(jail, unbanip="192.168.1.10",
outList=["192.168.0.1"])
_getBanListTest(jail, unbanip="192.168.0.1",
outList=[])
def testJailMaxRetry(self):
self.setGetTest("maxretry", "5", 5, jail=self.jailName)
self.setGetTest("maxretry", "2", 2, jail=self.jailName)

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-CLIENT "1" "October 2018" "fail2ban-client v0.11.0.dev3" "User Commands"
.TH FAIL2BAN-CLIENT "1" "January 2019" "fail2ban-client v0.11.0.dev3" "User Commands"
.SH NAME
fail2ban-client \- configure and control the server
.SH SYNOPSIS
@ -386,6 +386,15 @@ date/times for <JAIL>
\fBget <JAIL> usedns\fR
gets the usedns setting for <JAIL>
.TP
\fBget <JAIL> banip [<SEP>|\-\-with\-time]\fR
gets the list of of banned IP
addresses for <JAIL>. Optionally
the separator character ('<SEP>',
default is space) or the option
\&'\-\-with\-time' (printing the times
of ban) may be specified. The IPs
are ordered by end of ban.
.TP
\fBget <JAIL> maxretry\fR
gets the number of failures
allowed for <JAIL>

Loading…
Cancel
Save