From 34b586b51ebc2ca914bdc9e0a4a943481866745d Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 4 Apr 2018 23:28:44 +0200 Subject: [PATCH 1/4] fix for JSON serialization bug for set object (gh-2103): currently there are only users, so simply serialized as a list. --- fail2ban/server/database.py | 24 ++++++++++++++++-------- fail2ban/tests/databasetestcase.py | 23 ++++++++++++++++++----- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/fail2ban/server/database.py b/fail2ban/server/database.py index d7ba12c1..a06db21c 100644 --- a/fail2ban/server/database.py +++ b/fail2ban/server/database.py @@ -39,9 +39,14 @@ from ..helpers import getLogger, PREFER_ENC logSys = getLogger(__name__) if sys.version_info >= (3,): + def _json_default(x): + if isinstance(x, set): + x = list(x) + return x + def _json_dumps_safe(x): try: - x = json.dumps(x, ensure_ascii=False).encode( + x = json.dumps(x, ensure_ascii=False, default=_json_default).encode( PREFER_ENC, 'replace') except Exception as e: # pragma: no cover logSys.error('json dumps failed: %s', e) @@ -60,7 +65,7 @@ else: def _normalize(x): if isinstance(x, dict): return dict((_normalize(k), _normalize(v)) for k, v in x.iteritems()) - elif isinstance(x, list): + elif isinstance(x, (list, set)): return [_normalize(element) for element in x] elif isinstance(x, unicode): return x.encode(PREFER_ENC) @@ -527,10 +532,13 @@ class Fail2BanDb(object): except KeyError: pass #TODO: Implement data parts once arbitrary match keys completed + data = ticket.getData() + matches = data.get('matches') + if matches and len(matches) > self.maxEntries: + data['matches'] = matches[-self.maxEntries:] cur.execute( "INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)", - (jail.name, ip, int(round(ticket.getTime())), - ticket.getData())) + (jail.name, ip, int(round(ticket.getTime())), data)) @commitandrollback def delBan(self, cur, jail, *args): @@ -659,11 +667,11 @@ class Fail2BanDb(object): else: matches = m[-maxadd:] + matches failures += data.get('failures', 1) - tickdata.update(data.get('data', {})) + data['failures'] = failures + data['matches'] = matches + tickdata.update(data) prev_timeofban = timeofban - ticket = FailTicket(banip, prev_timeofban, matches) - ticket.setAttempt(failures) - ticket.setData(**tickdata) + ticket = FailTicket(banip, prev_timeofban, data=tickdata) tickets.append(ticket) if cacheKey: diff --git a/fail2ban/tests/databasetestcase.py b/fail2ban/tests/databasetestcase.py index 1ee523d9..7690525e 100644 --- a/fail2ban/tests/databasetestcase.py +++ b/fail2ban/tests/databasetestcase.py @@ -302,12 +302,18 @@ class DatabaseTest(LogCaptureTestCase): def testGetBansMerged_MaxEntries(self): self.testAddJail() maxEntries = 2 - failures = ["abc\n", "123\n", "ABC\n", "1234\n"] + failures = [ + {"matches": ["abc\n"], "user": set(['test'])}, + {"matches": ["123\n"], "user": set(['test'])}, + {"matches": ["ABC\n"], "user": set(['test', 'root'])}, + {"matches": ["1234\n"], "user": set(['test', 'root'])}, + ] + matches2find = [f["matches"][0] for f in failures] # add failures sequential: i = 80 for f in failures: i -= 10 - ticket = FailTicket("127.0.0.1", MyTime.time() - i, [f]) + ticket = FailTicket("127.0.0.1", MyTime.time() - i, data=f) ticket.setAttempt(1) self.db.addBan(self.jail, ticket) # should retrieve 2 matches only, but count of all attempts: @@ -316,9 +322,10 @@ class DatabaseTest(LogCaptureTestCase): self.assertEqual(ticket.getIP(), "127.0.0.1") self.assertEqual(ticket.getAttempt(), len(failures)) self.assertEqual(len(ticket.getMatches()), maxEntries) - self.assertEqual(ticket.getMatches(), failures[len(failures) - maxEntries:]) + self.assertEqual(ticket.getMatches(), matches2find[-maxEntries:]) # add more failures at once: - ticket = FailTicket("127.0.0.1", MyTime.time() - 10, failures) + ticket = FailTicket("127.0.0.1", MyTime.time() - 10, matches2find, + data={"user": set(['test', 'root'])}) ticket.setAttempt(len(failures)) self.db.addBan(self.jail, ticket) # should retrieve 2 matches only, but count of all attempts: @@ -326,7 +333,13 @@ class DatabaseTest(LogCaptureTestCase): ticket = self.db.getBansMerged("127.0.0.1") self.assertEqual(ticket.getAttempt(), 2 * len(failures)) self.assertEqual(len(ticket.getMatches()), maxEntries) - self.assertEqual(ticket.getMatches(), failures[len(failures) - maxEntries:]) + self.assertEqual(ticket.getMatches(), matches2find[-maxEntries:]) + # also using getCurrentBans: + ticket = self.db.getCurrentBans(self.jail, "127.0.0.1", fromtime=MyTime.time()-100) + self.assertTrue(ticket is not None) + self.assertEqual(ticket.getAttempt(), len(failures)) + self.assertEqual(len(ticket.getMatches()), maxEntries) + self.assertEqual(ticket.getMatches(), matches2find[-maxEntries:]) def testGetBansMerged(self): self.testAddJail() From c1923f96443bf5834b21af2de556a8f801141d75 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 4 Apr 2018 23:32:22 +0200 Subject: [PATCH 2/4] update ChangeLog --- ChangeLog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog b/ChangeLog index 2d101ce0..c2f306b3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -34,6 +34,9 @@ Incompatibility list (compared to v.0.9): ver. 0.10.3 (2018/04/04) - the-time-is-always-right-to-do-what-is-right ----------- +### ver. 0.10.3.1: +* fixed JSON serialization for the set-object within dump into database (gh-2103). + ### Fixes * `filter.d/asterisk.conf`: fixed failregex prefix by log over remote syslog server (gh-2060); * `filter.d/exim.conf`: failregex extended - SMTP call dropped: too many syntax or protocol errors (gh-2048); From cb0f4cbb32657f2faf0aa666cc86e4144dc69bf3 Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 5 Apr 2018 00:17:00 +0200 Subject: [PATCH 3/4] test_badips.py: amend to 2ff65f5d3ce1a4bd107cb4dbbd5343f7146a0677: increase timeout in normal mode + catch timeout exceptions to skip the test (avoid sporadic CI errors if badips gets slowly). --- fail2ban/tests/action_d/test_badips.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/fail2ban/tests/action_d/test_badips.py b/fail2ban/tests/action_d/test_badips.py index c3c417b8..c3cf33a4 100644 --- a/fail2ban/tests/action_d/test_badips.py +++ b/fail2ban/tests/action_d/test_badips.py @@ -20,6 +20,8 @@ import os import unittest import sys +from socket import timeout +from ssl import SSLError from ..actiontestcase import CallingMap from ..dummyjail import DummyJail @@ -51,7 +53,7 @@ if sys.version_info >= (2,7): # pragma: no cover - may be unavailable BadIPsActionTest.pythonModule = self.jail.actions._load_python_module(pythonModuleName) BadIPsActionTest.modAction = BadIPsActionTest.pythonModule.Action self.jail.actions._load_python_module(pythonModuleName) - BadIPsActionTest.available = BadIPsActionTest.modAction.isAvailable(timeout=2 if unittest.F2B.fast else 60) + BadIPsActionTest.available = BadIPsActionTest.modAction.isAvailable(timeout=2 if unittest.F2B.fast else 30) if not BadIPsActionTest.available[0]: raise unittest.SkipTest('Skip test because service is not available: %s' % BadIPsActionTest.available[1]) @@ -62,7 +64,7 @@ if sys.version_info >= (2,7): # pragma: no cover - may be unavailable 'score': 5, 'key': "fail2ban-test-suite", #'bankey': "fail2ban-test-suite", - 'timeout': (3 if unittest.F2B.fast else 30), + 'timeout': (3 if unittest.F2B.fast else 60), }) self.action = self.jail.actions["badips"] @@ -108,11 +110,16 @@ if sys.version_info >= (2,7): # pragma: no cover - may be unavailable self.action.updateperiod = "900" def testStartStop(self): - self.action.start() - self.assertTrue(len(self.action._bannedips) > 10, - "%s is fewer as 10: %r" % (len(self.action._bannedips), self.action._bannedips)) - self.action.stop() - self.assertTrue(len(self.action._bannedips) == 0) + try: + self.action.start() + self.assertTrue(len(self.action._bannedips) > 10, + "%s is fewer as 10: %r" % (len(self.action._bannedips), self.action._bannedips)) + self.action.stop() + self.assertTrue(len(self.action._bannedips) == 0) + except (SSLError, timeout) as e: # pragma: no cover - timeout only + if not isinstance(e, timeout) and 'timed out' not in str(e): + raise + raise unittest.SkipTest('Skip test because of %s' % e) def testBanIP(self): aInfo = CallingMap({ From ac0d441fd68852ffda7b15c71f16b7f4fde1a7ee Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 5 Apr 2018 00:21:30 +0200 Subject: [PATCH 4/4] 0.10.3.fix1: version bump --- fail2ban/version.py | 2 +- man/fail2ban-client.1 | 4 ++-- man/fail2ban-regex.1 | 2 +- man/fail2ban-server.1 | 4 ++-- man/fail2ban-testcases.1 | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fail2ban/version.py b/fail2ban/version.py index 0f36b315..3c16adea 100644 --- a/fail2ban/version.py +++ b/fail2ban/version.py @@ -24,4 +24,4 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko, Steven Hiscocks, Daniel Black" __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2005-2016 Yaroslav Halchenko, 2013-2014 Steven Hiscocks, Daniel Black" __license__ = "GPL-v2+" -version = "0.10.3" +version = "0.10.3.fix1" diff --git a/man/fail2ban-client.1 b/man/fail2ban-client.1 index c4b96650..53292b29 100644 --- a/man/fail2ban-client.1 +++ b/man/fail2ban-client.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH FAIL2BAN-CLIENT "1" "April 2018" "fail2ban-client v0.10.3" "User Commands" +.TH FAIL2BAN-CLIENT "1" "April 2018" "fail2ban-client v0.10.3.fix1" "User Commands" .SH NAME fail2ban-client \- configure and control the server .SH SYNOPSIS .B fail2ban-client [\fI\,OPTIONS\/\fR] \fI\,\/\fR .SH DESCRIPTION -Fail2Ban v0.10.3 reads log file that contains password failure report +Fail2Ban v0.10.3.fix1 reads log file that contains password failure report and bans the corresponding IP addresses using firewall rules. .SH OPTIONS .TP diff --git a/man/fail2ban-regex.1 b/man/fail2ban-regex.1 index 1d8d7da8..fb2952e8 100644 --- a/man/fail2ban-regex.1 +++ b/man/fail2ban-regex.1 @@ -1,5 +1,5 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH FAIL2BAN-REGEX "1" "April 2018" "fail2ban-regex 0.10.3" "User Commands" +.TH FAIL2BAN-REGEX "1" "April 2018" "fail2ban-regex 0.10.3.fix1" "User Commands" .SH NAME fail2ban-regex \- test Fail2ban "failregex" option .SH SYNOPSIS diff --git a/man/fail2ban-server.1 b/man/fail2ban-server.1 index 560bedac..3bbe4eb0 100644 --- a/man/fail2ban-server.1 +++ b/man/fail2ban-server.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH FAIL2BAN-SERVER "1" "April 2018" "fail2ban-server v0.10.3" "User Commands" +.TH FAIL2BAN-SERVER "1" "April 2018" "fail2ban-server v0.10.3.fix1" "User Commands" .SH NAME fail2ban-server \- start the server .SH SYNOPSIS .B fail2ban-server [\fI\,OPTIONS\/\fR] .SH DESCRIPTION -Fail2Ban v0.10.3 reads log file that contains password failure report +Fail2Ban v0.10.3.fix1 reads log file that contains password failure report and bans the corresponding IP addresses using firewall rules. .SH OPTIONS .TP diff --git a/man/fail2ban-testcases.1 b/man/fail2ban-testcases.1 index cae79879..6f8869b6 100644 --- a/man/fail2ban-testcases.1 +++ b/man/fail2ban-testcases.1 @@ -1,5 +1,5 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH FAIL2BAN-TESTCASES "1" "April 2018" "fail2ban-testcases 0.10.3" "User Commands" +.TH FAIL2BAN-TESTCASES "1" "April 2018" "fail2ban-testcases 0.10.3.fix1" "User Commands" .SH NAME fail2ban-testcases \- run Fail2Ban unit-tests .SH SYNOPSIS