From 3d477d229d169a3dedcb7144bb24f4ea08d93c02 Mon Sep 17 00:00:00 2001 From: SP Date: Thu, 3 Jan 2019 22:47:24 +0300 Subject: [PATCH 1/7] ENH: added new command `fail2ban-client get banip` to get the banned ip addresses (gh-1916) --- ChangeLog | 1 + fail2ban/client/beautifier.py | 2 ++ fail2ban/server/actions.py | 10 +++++++ fail2ban/server/server.py | 15 ++++++++++ fail2ban/server/transmitter.py | 2 ++ fail2ban/tests/servertestcase.py | 50 ++++++++++++++++++++++++++++++++ man/fail2ban-client.1 | 4 +++ 7 files changed, 84 insertions(+) diff --git a/ChangeLog b/ChangeLog index b9efbc10..8f12b49a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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 banip` to get the banned ip addresses (gh-1916). ver. 0.10.4-dev-1 (20??/??/??) - development edition diff --git a/fail2ban/client/beautifier.py b/fail2ban/client/beautifier.py index 4d9e549f..607c0ade 100644 --- a/fail2ban/client/beautifier.py +++ b/fail2ban/client/beautifier.py @@ -180,6 +180,8 @@ 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": + msg = " ".join(response) except Exception: logSys.warning("Beautifier error. Please report the error") logSys.error("Beautify %r with %r failed", response, self.__inputCmd, diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py index 3d862275..11a905be 100644 --- a/fail2ban/server/actions.py +++ b/fail2ban/server/actions.py @@ -204,6 +204,16 @@ class Actions(JailThread, Mapping): def getBanTime(self): return self.__banManager.getBanTime() + def getBanList(self): + """Returns the list of banned IP addresses. + + Returns + ------- + list + The list of banned IP addresses. + """ + return self.__banManager.getBanList() + def removeBannedIP(self, ip=None, db=True, ifexists=False): """Removes banned IP calling actions' unban method diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index dfbbd5d7..5370808c 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -510,6 +510,21 @@ class Server: def getBanTime(self, name): return self.__jails[name].actions.getBanTime() + def getBanList(self, name): + """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() + def setBanTimeExtra(self, name, opt, value): self.__jails[name].setBanTimeExtra(opt, value) diff --git a/fail2ban/server/transmitter.py b/fail2ban/server/transmitter.py index c24408c4..e3e05eb6 100644 --- a/fail2ban/server/transmitter.py +++ b/fail2ban/server/transmitter.py @@ -390,6 +390,8 @@ class Transmitter: # Action elif command[1] == "bantime": return self.__server.getBanTime(name) + elif command[1] == "banip": + return self.__server.getBanList(name) elif command[1].startswith("bantime."): opt = command[1][len("bantime."):] return self.__server.getBanTimeExtra(name, opt) diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index 8b616abc..14905b76 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -103,6 +103,34 @@ class TransmitterBase(LogCaptureTestCase): # if we expected to get it set without problem, check new value self.assertEqual(v(self.transm.proceed(getCmd)), v((0, outValue))) + def getBanListTest(self, jail, banip=None, unbanip=None, outList=None): + """Process set banip/set unbanip commands and compare the list of + banned IP addresses with outList.""" + def v(value): + """Prepare value for comparison.""" + if value[1] is None: + tmp = [] + else: + tmp = map(str, value[1]) + return (value[0], sorted(tmp)) + + # Ban IP address + if banip is not None: + self.assertEqual( + self.transm.proceed(["set", jail, "banip", banip]), + (0, banip)) + time.sleep(Utils.DEFAULT_SLEEP_TIME) # Give chance to ban + # Unban IP address + if unbanip is not None: + self.assertEqual( + self.transm.proceed(["set", jail, "unbanip", unbanip]), + (0, unbanip)) + time.sleep(Utils.DEFAULT_SLEEP_TIME) # Give chance to unban + # Compare the list of banned IP addresses with outList + self.assertEqual( + v(self.transm.proceed(["get", jail, "banip"])), + v((0, outList))) + def setGetTestNOK(self, cmd, inValue, jail=None): setCmd = ["set", cmd, inValue] getCmd = ["get", cmd] @@ -347,6 +375,28 @@ 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) + + self.getBanListTest(jail) + self.getBanListTest( + jail, banip="127.0.0.1", outList=["127.0.0.1"]) + self.getBanListTest( + jail, banip="192.168.0.1", + outList=["127.0.0.1", "192.168.0.1"]) + self.getBanListTest( + jail, banip="192.168.1.10", + outList=["127.0.0.1", "192.168.0.1", "192.168.1.10"]) + self.getBanListTest( + jail, unbanip="127.0.0.1", + outList=["192.168.0.1", "192.168.1.10"]) + self.getBanListTest( + jail, unbanip="192.168.1.10", outList=["192.168.0.1"]) + self.getBanListTest(jail, unbanip="192.168.0.1", outList=[]) + self.getBanListTest(jail) + def testJailMaxRetry(self): self.setGetTest("maxretry", "5", 5, jail=self.jailName) self.setGetTest("maxretry", "2", 2, jail=self.jailName) diff --git a/man/fail2ban-client.1 b/man/fail2ban-client.1 index d1226d56..81883ce2 100644 --- a/man/fail2ban-client.1 +++ b/man/fail2ban-client.1 @@ -379,6 +379,10 @@ will look back for failures for gets the time a host is banned for .TP +\fBget banip\fR +gets the list of banned IP +addresses for +.TP \fBget datepattern\fR gets the patern used to match date/times for From 7f5f7017dbaf9410149d262e4ec15fdc23da60f8 Mon Sep 17 00:00:00 2001 From: SP Date: Fri, 4 Jan 2019 17:06:47 +0300 Subject: [PATCH 2/7] ENH: added new test cases for `fail2ban-client get banip` command (gh-1916) --- fail2ban/tests/clientbeautifiertestcase.py | 7 +++++ fail2ban/tests/fail2banclienttestcase.py | 30 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/fail2ban/tests/clientbeautifiertestcase.py b/fail2ban/tests/clientbeautifiertestcase.py index 79a0ff54..863da7f8 100644 --- a/fail2ban/tests/clientbeautifiertestcase.py +++ b/fail2ban/tests/clientbeautifiertestcase.py @@ -261,3 +261,10 @@ class BeautifierTest(unittest.TestCase): output = "Sorry but the command is invalid" self.assertEqual(self.b.beautifyError(IndexError()), output) + + def testJailBanList(self): + self.b.setInputCmd(["get", "ssh", "banip"]) + response = ["192.168.0.1", "192.168.1.10"] + output = "192.168.0.1 192.168.1.10" + self.assertEqual(self.b.beautify(response), output) + self.assertEqual(self.b.beautify([]), "") diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index c120128b..b8fb6d0c 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -1227,6 +1227,36 @@ class Fail2banServerTest(Fail2banClientServerBase): "Jail 'test-jail1' stopped", "Jail 'test-jail1' started", all=True, wait=MID_WAITTIME) + # test the list of banned IP addresses, step 0: prepare + self.pruneLog("[test-phase 9a]") + self.execCmd(SUCCESS, startparams, "reload", "--unban", "test-jail1") + self.assertLogged( + "Jail 'test-jail1' reloaded", wait=MID_WAITTIME) + # test the list of banned IP addresses, step 1: ban IP addresses + self.pruneLog("[test-phase 9b]") + self.execCmd(SUCCESS, startparams, + "set", "test-jail1", "banip", "192.168.0.1") + self.assertLogged("[test-jail1] Ban 192.168.0.1", wait=MID_WAITTIME) + self.execCmd(SUCCESS, startparams, + "set", "test-jail1", "banip", "192.168.1.10") + self.assertLogged("[test-jail1] Ban 192.168.1.10", wait=MID_WAITTIME) + self.execCmd(SUCCESS, startparams, "get", "test-jail1", "banip") + self.assertLogged( + "192.168.1.10 192.168.0.1", + "192.168.0.1 192.168.1.10", wait=MID_WAITTIME) + # test the list of banned IP addresses, step 2: unban IP addresses + self.pruneLog("[test-phase 9c]") + self.execCmd(SUCCESS, startparams, + "set", "test-jail1", "unbanip", "192.168.0.1") + self.assertLogged("[test-jail1] Unban 192.168.0.1", wait=MID_WAITTIME) + self.execCmd(SUCCESS, startparams, + "set", "test-jail1", "unbanip", "192.168.1.10") + self.assertLogged("[test-jail1] Unban 192.168.1.10", wait=MID_WAITTIME) + self.execCmd(SUCCESS, startparams, "get", "test-jail1", "banip") + self.assertNotLogged( + "192.168.1.10 192.168.0.1", + "192.168.0.1 192.168.1.10", wait=MID_WAITTIME) + # test action.d/nginx-block-map.conf -- @unittest.F2B.skip_if_cfg_missing(action="nginx-block-map") @with_foreground_server_thread(startextra={ From df97fd33cfb475f2833024eceaba7de7ca1a8f56 Mon Sep 17 00:00:00 2001 From: sebres Date: Sun, 6 Jan 2019 22:31:23 +0100 Subject: [PATCH 3/7] ip-list is sorted now (by end of ban) per default; extended with new option `--with-time` to provide more pretty and informative result (separated by new-line, including time strings: time of ban + ban-time = end of ban): 192.0.2.1 2019-01-06 22:24:48 + 300 = 2019-01-06 22:29:48 192.0.2.2 2019-01-06 22:24:48 + 600 = 2019-01-06 22:34:48 also it is possible now to provide separator-character as extra-parameter after `get banip ?sep-char?` (default is space). removed unneeded test-cases (test code-base minimization) and unexpected manually changed files. --- ChangeLog | 2 +- fail2ban/client/beautifier.py | 6 ++- fail2ban/server/actions.py | 4 +- fail2ban/server/banmanager.py | 17 ++++++- fail2ban/server/mytime.py | 10 ++++ fail2ban/server/server.py | 4 +- fail2ban/server/transmitter.py | 3 +- fail2ban/tests/clientbeautifiertestcase.py | 7 --- fail2ban/tests/fail2banclienttestcase.py | 53 ++++++++++------------ man/fail2ban-client.1 | 4 -- 10 files changed, 60 insertions(+), 50 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8f12b49a..d18876e2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -56,7 +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 banip` to get the banned ip addresses (gh-1916). +* added new command `fail2ban-client get banip ?--with-time|sep-char?` to get the banned ip addresses (gh-1916). ver. 0.10.4-dev-1 (20??/??/??) - development edition diff --git a/fail2ban/client/beautifier.py b/fail2ban/client/beautifier.py index 607c0ade..97cd38b2 100644 --- a/fail2ban/client/beautifier.py +++ b/fail2ban/client/beautifier.py @@ -181,7 +181,11 @@ class Beautifier: "methods:\n" % (inC[1], inC[3]) msg += ", ".join(response) elif inC[2] == "banip" and inC[0] == "get": - msg = " ".join(response) + 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, diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py index 11a905be..3a92dcda 100644 --- a/fail2ban/server/actions.py +++ b/fail2ban/server/actions.py @@ -204,7 +204,7 @@ class Actions(JailThread, Mapping): def getBanTime(self): return self.__banManager.getBanTime() - def getBanList(self): + def getBanList(self, withTime=False): """Returns the list of banned IP addresses. Returns @@ -212,7 +212,7 @@ class Actions(JailThread, Mapping): list The list of banned IP addresses. """ - return self.__banManager.getBanList() + return self.__banManager.getBanList(ordered=True, withTime=withTime) def removeBannedIP(self, ip=None, db=True, ifexists=False): """Removes banned IP calling actions' unban method diff --git a/fail2ban/server/banmanager.py b/fail2ban/server/banmanager.py index 1340fb52..ffbcf766 100644 --- a/fail2ban/server/banmanager.py +++ b/fail2ban/server/banmanager.py @@ -102,9 +102,22 @@ class BanManager: # # @return IP list - def getBanList(self): + def getBanList(self, ordered=False, withTime=False): with self.__lock: - return self.__banList.keys() + 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). diff --git a/fail2ban/server/mytime.py b/fail2ban/server/mytime.py index 49199887..e20e9690 100644 --- a/fail2ban/server/mytime.py +++ b/fail2ban/server/mytime.py @@ -113,6 +113,16 @@ class MyTime: return time.localtime(x) else: return time.localtime(MyTime.myTime) + + @staticmethod + def time2str(unixTime): + """Convert time to a string representing as date and time in ISO 8601 + format, 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("%Y-%m-%d %H:%M:%S") ## precreate/precompile primitives used in str2seconds: diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index 5370808c..9cc17b5b 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -510,7 +510,7 @@ class Server: def getBanTime(self, name): return self.__jails[name].actions.getBanTime() - def getBanList(self, name): + def getBanList(self, name, withTime=False): """Returns the list of banned IP addresses for a jail. Parameters @@ -523,7 +523,7 @@ class Server: list The list of banned IP addresses. """ - return self.__jails[name].actions.getBanList() + return self.__jails[name].actions.getBanList(withTime) def setBanTimeExtra(self, name, opt, value): self.__jails[name].setBanTimeExtra(opt, value) diff --git a/fail2ban/server/transmitter.py b/fail2ban/server/transmitter.py index e3e05eb6..0c0cfba8 100644 --- a/fail2ban/server/transmitter.py +++ b/fail2ban/server/transmitter.py @@ -391,7 +391,8 @@ class Transmitter: elif command[1] == "bantime": return self.__server.getBanTime(name) elif command[1] == "banip": - return self.__server.getBanList(name) + 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) diff --git a/fail2ban/tests/clientbeautifiertestcase.py b/fail2ban/tests/clientbeautifiertestcase.py index 863da7f8..79a0ff54 100644 --- a/fail2ban/tests/clientbeautifiertestcase.py +++ b/fail2ban/tests/clientbeautifiertestcase.py @@ -261,10 +261,3 @@ class BeautifierTest(unittest.TestCase): output = "Sorry but the command is invalid" self.assertEqual(self.b.beautifyError(IndexError()), output) - - def testJailBanList(self): - self.b.setInputCmd(["get", "ssh", "banip"]) - response = ["192.168.0.1", "192.168.1.10"] - output = "192.168.0.1 192.168.1.10" - self.assertEqual(self.b.beautify(response), output) - self.assertEqual(self.b.beautify([]), "") diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index b8fb6d0c..4480c71c 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -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, @@ -1227,36 +1238,6 @@ class Fail2banServerTest(Fail2banClientServerBase): "Jail 'test-jail1' stopped", "Jail 'test-jail1' started", all=True, wait=MID_WAITTIME) - # test the list of banned IP addresses, step 0: prepare - self.pruneLog("[test-phase 9a]") - self.execCmd(SUCCESS, startparams, "reload", "--unban", "test-jail1") - self.assertLogged( - "Jail 'test-jail1' reloaded", wait=MID_WAITTIME) - # test the list of banned IP addresses, step 1: ban IP addresses - self.pruneLog("[test-phase 9b]") - self.execCmd(SUCCESS, startparams, - "set", "test-jail1", "banip", "192.168.0.1") - self.assertLogged("[test-jail1] Ban 192.168.0.1", wait=MID_WAITTIME) - self.execCmd(SUCCESS, startparams, - "set", "test-jail1", "banip", "192.168.1.10") - self.assertLogged("[test-jail1] Ban 192.168.1.10", wait=MID_WAITTIME) - self.execCmd(SUCCESS, startparams, "get", "test-jail1", "banip") - self.assertLogged( - "192.168.1.10 192.168.0.1", - "192.168.0.1 192.168.1.10", wait=MID_WAITTIME) - # test the list of banned IP addresses, step 2: unban IP addresses - self.pruneLog("[test-phase 9c]") - self.execCmd(SUCCESS, startparams, - "set", "test-jail1", "unbanip", "192.168.0.1") - self.assertLogged("[test-jail1] Unban 192.168.0.1", wait=MID_WAITTIME) - self.execCmd(SUCCESS, startparams, - "set", "test-jail1", "unbanip", "192.168.1.10") - self.assertLogged("[test-jail1] Unban 192.168.1.10", wait=MID_WAITTIME) - self.execCmd(SUCCESS, startparams, "get", "test-jail1", "banip") - self.assertNotLogged( - "192.168.1.10 192.168.0.1", - "192.168.0.1 192.168.1.10", wait=MID_WAITTIME) - # test action.d/nginx-block-map.conf -- @unittest.F2B.skip_if_cfg_missing(action="nginx-block-map") @with_foreground_server_thread(startextra={ @@ -1427,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() @@ -1441,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() diff --git a/man/fail2ban-client.1 b/man/fail2ban-client.1 index 81883ce2..d1226d56 100644 --- a/man/fail2ban-client.1 +++ b/man/fail2ban-client.1 @@ -379,10 +379,6 @@ will look back for failures for gets the time a host is banned for .TP -\fBget banip\fR -gets the list of banned IP -addresses for -.TP \fBget datepattern\fR gets the patern used to match date/times for From f959f58e15c46fdb6f33fc30e08ce6b7316c3c83 Mon Sep 17 00:00:00 2001 From: sebres Date: Sun, 6 Jan 2019 22:45:48 +0100 Subject: [PATCH 4/7] extend protocol (command-line) and regenerate man's --- ChangeLog | 2 +- fail2ban/protocol.py | 1 + man/fail2ban-client.1 | 11 ++++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index d18876e2..20a37d4a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -56,7 +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 banip ?--with-time|sep-char?` to get the banned ip addresses (gh-1916). +* added new command `fail2ban-client get banip ?sep-char|--with-time?` to get the banned ip addresses (gh-1916). ver. 0.10.4-dev-1 (20??/??/??) - development edition diff --git a/fail2ban/protocol.py b/fail2ban/protocol.py index b21ab848..f9ec5b71 100644 --- a/fail2ban/protocol.py +++ b/fail2ban/protocol.py @@ -128,6 +128,7 @@ protocol = [ ["get bantime", "gets the time a host is banned for "], ["get datepattern", "gets the patern used to match date/times for "], ["get usedns", "gets the usedns setting for "], +["get banip [|--with-time]", "gets the list of of banned IP addresses for . Optionally the separator character ('', 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 maxretry", "gets the number of failures allowed for "], ["get maxlines", "gets the number of lines to buffer for "], ["get actions", "gets a list of actions for "], diff --git a/man/fail2ban-client.1 b/man/fail2ban-client.1 index d1226d56..af2af054 100644 --- a/man/fail2ban-client.1 +++ b/man/fail2ban-client.1 @@ -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 \fBget usedns\fR gets the usedns setting for .TP +\fBget banip [|\-\-with\-time]\fR +gets the list of of banned IP +addresses for . Optionally +the separator character ('', +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 maxretry\fR gets the number of failures allowed for From 4b934c784d5329f884d1005338a9d84705f87839 Mon Sep 17 00:00:00 2001 From: sebres Date: Sun, 6 Jan 2019 23:33:28 +0100 Subject: [PATCH 5/7] normalized time to string calls. --- fail2ban/server/filter.py | 6 +++--- fail2ban/server/mytime.py | 8 ++++---- fail2ban/server/observer.py | 6 +++--- fail2ban/tests/filtertestcase.py | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py index 9265a58b..001b35b7 100644 --- a/fail2ban/server/filter.py +++ b/fail2ban/server/filter.py @@ -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. diff --git a/fail2ban/server/mytime.py b/fail2ban/server/mytime.py index e20e9690..98b69bd4 100644 --- a/fail2ban/server/mytime.py +++ b/fail2ban/server/mytime.py @@ -115,14 +115,14 @@ class MyTime: return time.localtime(MyTime.myTime) @staticmethod - def time2str(unixTime): - """Convert time to a string representing as date and time in ISO 8601 - format, YYYY-MM-DD HH:MM:SS without microseconds. + 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("%Y-%m-%d %H:%M:%S") + unixTime).replace(microsecond=0).strftime(format) ## precreate/precompile primitives used in str2seconds: diff --git a/fail2ban/server/observer.py b/fail2ban/server/observer.py index c3fa7d54..ffeeec71 100644 --- a/fail2ban/server/observer.py +++ b/fail2ban/server/observer.py @@ -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]) diff --git a/fail2ban/tests/filtertestcase.py b/fail2ban/tests/filtertestcase.py index b22cd0f8..cae1e173 100644 --- a/fail2ban/tests/filtertestcase.py +++ b/fail2ban/tests/filtertestcase.py @@ -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)) From 963e14c6855bca7e6ac470cce9554a71565e425d Mon Sep 17 00:00:00 2001 From: sebres Date: Sun, 6 Jan 2019 23:44:42 +0100 Subject: [PATCH 6/7] resolve sporadic timing errors (unban if ban still not occurred, resp. get list of IPs if not yet banned); simplify helper procedure for testJailBanList. --- fail2ban/tests/servertestcase.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index 14905b76..7198464c 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -103,33 +103,25 @@ class TransmitterBase(LogCaptureTestCase): # if we expected to get it set without problem, check new value self.assertEqual(v(self.transm.proceed(getCmd)), v((0, outValue))) - def getBanListTest(self, jail, banip=None, unbanip=None, outList=None): + def getBanListTest(self, jail, banip=None, unbanip=None, outList=[]): """Process set banip/set unbanip commands and compare the list of banned IP addresses with outList.""" - def v(value): - """Prepare value for comparison.""" - if value[1] is None: - tmp = [] - else: - tmp = map(str, value[1]) - return (value[0], sorted(tmp)) - # Ban IP address if banip is not None: self.assertEqual( self.transm.proceed(["set", jail, "banip", banip]), (0, banip)) - time.sleep(Utils.DEFAULT_SLEEP_TIME) # Give chance to ban + 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)) - time.sleep(Utils.DEFAULT_SLEEP_TIME) # Give chance to unban + self.assertLogged("Unban %s" % unbanip, wait=True) # Give chance to unban # Compare the list of banned IP addresses with outList - self.assertEqual( - v(self.transm.proceed(["get", jail, "banip"])), - v((0, outList))) + self.assertSortedEqual( + self.transm.proceed(["get", jail, "banip"]), + (0, outList)) def setGetTestNOK(self, cmd, inValue, jail=None): setCmd = ["set", cmd, inValue] From 59688d7cd563a930263c9dd6ba50d9b8251813ab Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 7 Jan 2019 00:05:27 +0100 Subject: [PATCH 7/7] move helper to test, normalize invocations in order to emphasize assert comparison. --- fail2ban/tests/servertestcase.py | 64 ++++++++++++++++---------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index 7198464c..166cd438 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -103,26 +103,6 @@ class TransmitterBase(LogCaptureTestCase): # if we expected to get it set without problem, check new value self.assertEqual(v(self.transm.proceed(getCmd)), v((0, outValue))) - def getBanListTest(self, jail, banip=None, unbanip=None, outList=[]): - """Process set banip/set unbanip commands and compare the list of - banned IP addresses with 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)) - def setGetTestNOK(self, cmd, inValue, jail=None): setCmd = ["set", cmd, inValue] getCmd = ["get", cmd] @@ -372,22 +352,40 @@ class Transmitter(TransmitterBase): self.server.addJail(jail, FAST_BACKEND) self.server.startJail(jail) - self.getBanListTest(jail) - self.getBanListTest( - jail, banip="127.0.0.1", outList=["127.0.0.1"]) - self.getBanListTest( - jail, banip="192.168.0.1", + # 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"]) - self.getBanListTest( - jail, banip="192.168.1.10", + _getBanListTest(jail, banip="192.168.1.10", outList=["127.0.0.1", "192.168.0.1", "192.168.1.10"]) - self.getBanListTest( - jail, unbanip="127.0.0.1", + _getBanListTest(jail, unbanip="127.0.0.1", outList=["192.168.0.1", "192.168.1.10"]) - self.getBanListTest( - jail, unbanip="192.168.1.10", outList=["192.168.0.1"]) - self.getBanListTest(jail, unbanip="192.168.0.1", outList=[]) - self.getBanListTest(jail) + _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)