diff --git a/fail2ban/tests/action_d/test_smtp.py b/fail2ban/tests/action_d/test_smtp.py index d0858b85..8d20055a 100644 --- a/fail2ban/tests/action_d/test_smtp.py +++ b/fail2ban/tests/action_d/test_smtp.py @@ -52,6 +52,7 @@ class SMTPActionTest(unittest.TestCase): def setUp(self): """Call before every test case.""" + unittest.F2B.SkipIfCfgMissing(action='smtp.py') super(SMTPActionTest, self).setUp() self.jail = DummyJail() pythonModule = os.path.join(CONFIG_DIR, "action.d", "smtp.py") diff --git a/fail2ban/tests/clientreadertestcase.py b/fail2ban/tests/clientreadertestcase.py index e8cd2912..0472b770 100644 --- a/fail2ban/tests/clientreadertestcase.py +++ b/fail2ban/tests/clientreadertestcase.py @@ -45,8 +45,6 @@ TEST_FILES_DIR_SHARE_CFG = {} from .utils import CONFIG_DIR CONFIG_DIR_SHARE_CFG = unittest.F2B.share_config -STOCK = os.path.exists(os.path.join('config', 'fail2ban.conf')) - IMPERFECT_CONFIG = os.path.join(os.path.dirname(__file__), 'config') IMPERFECT_CONFIG_SHARE_CFG = {} @@ -246,15 +244,15 @@ class JailReaderTest(LogCaptureTestCase): self.assertTrue(jail.isEnabled()) self.assertLogged("Invalid filter definition 'flt[test'") - if STOCK: - def testStockSSHJail(self): - jail = JailReader('sshd', basedir=CONFIG_DIR, share_config=CONFIG_DIR_SHARE_CFG) # we are running tests from root project dir atm - self.assertTrue(jail.read()) - self.assertTrue(jail.getOptions()) - self.assertFalse(jail.isEnabled()) - self.assertEqual(jail.getName(), 'sshd') - jail.setName('ssh-funky-blocker') - self.assertEqual(jail.getName(), 'ssh-funky-blocker') + def testStockSSHJail(self): + unittest.F2B.SkipIfCfgMissing(stock=True) + jail = JailReader('sshd', basedir=CONFIG_DIR, share_config=CONFIG_DIR_SHARE_CFG) # we are running tests from root project dir atm + self.assertTrue(jail.read()) + self.assertTrue(jail.getOptions()) + self.assertFalse(jail.isEnabled()) + self.assertEqual(jail.getName(), 'sshd') + jail.setName('ssh-funky-blocker') + self.assertEqual(jail.getName(), 'ssh-funky-blocker') def testSplitOption(self): # Simple example @@ -307,6 +305,7 @@ class JailReaderTest(LogCaptureTestCase): self.assertEqual(expected2, result) def testVersionAgent(self): + unittest.F2B.SkipIfCfgMissing(stock=True) jail = JailReader('blocklisttest', force_enable=True, basedir=CONFIG_DIR) # emulate jail.read(), because such jail not exists: ConfigReader.read(jail, "jail"); @@ -597,222 +596,226 @@ class JailsReaderTest(LogCaptureTestCase): self.assertNotLogged("Skipping...") self.assertLogged("No file(s) found for glob /weapons/of/mass/destruction") - if STOCK: - def testReadStockActionConf(self): - for actionConfig in glob.glob(os.path.join(CONFIG_DIR, 'action.d', '*.conf')): - actionName = os.path.basename(actionConfig).replace('.conf', '') - actionReader = ActionReader(actionName, "TEST", {}, basedir=CONFIG_DIR) - self.assertTrue(actionReader.read()) - try: - actionReader.getOptions({}) # populate _opts - except Exception as e: # pragma: no cover - self.fail("action %r\n%s: %s" % (actionName, type(e).__name__, e)) - if not actionName.endswith('-common'): - self.assertIn('Definition', actionReader.sections(), - msg="Action file %r is lacking [Definition] section" % actionConfig) - # all must have some actionban defined - self.assertTrue(actionReader._opts.get('actionban', '').strip(), - msg="Action file %r is lacking actionban" % actionConfig) - # test name of jail is set in options (also if not supplied within parameters): - opts = actionReader.getCombined( - ignore=CommandAction._escapedTags | set(('timeout', 'bantime'))) - self.assertEqual(opts.get('name'), 'TEST', - msg="Action file %r does not contains jail-name 'f2b-TEST'" % actionConfig) - # and the name is substituted (test several actions surely contains name-interpolation): - if actionName in ('pf', 'iptables-allports', 'iptables-multiport'): - #print('****', actionName, opts.get('actionstart', '')) - self.assertIn('f2b-TEST', opts.get('actionstart', ''), - msg="Action file %r: interpolation of actionstart does not contains jail-name 'f2b-TEST'" % actionConfig) + def testReadStockActionConf(self): + unittest.F2B.SkipIfCfgMissing(stock=True) + for actionConfig in glob.glob(os.path.join(CONFIG_DIR, 'action.d', '*.conf')): + actionName = os.path.basename(actionConfig).replace('.conf', '') + actionReader = ActionReader(actionName, "TEST", {}, basedir=CONFIG_DIR) + self.assertTrue(actionReader.read()) + try: + actionReader.getOptions({}) # populate _opts + except Exception as e: # pragma: no cover + self.fail("action %r\n%s: %s" % (actionName, type(e).__name__, e)) + if not actionName.endswith('-common'): + self.assertIn('Definition', actionReader.sections(), + msg="Action file %r is lacking [Definition] section" % actionConfig) + # all must have some actionban defined + self.assertTrue(actionReader._opts.get('actionban', '').strip(), + msg="Action file %r is lacking actionban" % actionConfig) + # test name of jail is set in options (also if not supplied within parameters): + opts = actionReader.getCombined( + ignore=CommandAction._escapedTags | set(('timeout', 'bantime'))) + self.assertEqual(opts.get('name'), 'TEST', + msg="Action file %r does not contains jail-name 'f2b-TEST'" % actionConfig) + # and the name is substituted (test several actions surely contains name-interpolation): + if actionName in ('pf', 'iptables-allports', 'iptables-multiport'): + #print('****', actionName, opts.get('actionstart', '')) + self.assertIn('f2b-TEST', opts.get('actionstart', ''), + msg="Action file %r: interpolation of actionstart does not contains jail-name 'f2b-TEST'" % actionConfig) - def testReadStockJailConf(self): - jails = JailsReader(basedir=CONFIG_DIR, share_config=CONFIG_DIR_SHARE_CFG) # we are running tests from root project dir atm - self.assertTrue(jails.read()) # opens fine - self.assertTrue(jails.getOptions()) # reads fine - comm_commands = jails.convert() - # by default None of the jails is enabled and we get no - # commands to communicate to the server - self.assertEqual(comm_commands, []) + def testReadStockJailConf(self): + unittest.F2B.SkipIfCfgMissing(stock=True) + jails = JailsReader(basedir=CONFIG_DIR, share_config=CONFIG_DIR_SHARE_CFG) # we are running tests from root project dir atm + self.assertTrue(jails.read()) # opens fine + self.assertTrue(jails.getOptions()) # reads fine + comm_commands = jails.convert() + # by default None of the jails is enabled and we get no + # commands to communicate to the server + self.assertEqual(comm_commands, []) - # TODO: make sure this is handled well - ## We should not "read" some bogus jail - #old_comm_commands = comm_commands[:] # make a copy - #self.assertRaises(ValueError, jails.getOptions, "BOGUS") - #self.printLog() - #self.assertLogged("No section: 'BOGUS'") - ## and there should be no side-effects - #self.assertEqual(jails.convert(), old_comm_commands) + # TODO: make sure this is handled well + ## We should not "read" some bogus jail + #old_comm_commands = comm_commands[:] # make a copy + #self.assertRaises(ValueError, jails.getOptions, "BOGUS") + #self.printLog() + #self.assertLogged("No section: 'BOGUS'") + ## and there should be no side-effects + #self.assertEqual(jails.convert(), old_comm_commands) - allFilters = set() + allFilters = set() - # All jails must have filter and action set - # TODO: evolve into a parametric test - for jail in jails.sections(): - if jail == 'INCLUDES': - continue - filterName = jails.get(jail, 'filter') - filterName, filterOpt = extractOptions(filterName) - allFilters.add(filterName) - self.assertTrue(len(filterName)) - # moreover we must have a file for it - # and it must be readable as a Filter - filterReader = FilterReader(filterName, jail, filterOpt, + # All jails must have filter and action set + # TODO: evolve into a parametric test + for jail in jails.sections(): + if jail == 'INCLUDES': + continue + filterName = jails.get(jail, 'filter') + filterName, filterOpt = extractOptions(filterName) + allFilters.add(filterName) + self.assertTrue(len(filterName)) + # moreover we must have a file for it + # and it must be readable as a Filter + filterReader = FilterReader(filterName, jail, filterOpt, + share_config=CONFIG_DIR_SHARE_CFG, basedir=CONFIG_DIR) + self.assertTrue(filterReader.read(),"Failed to read filter:" + filterName) # opens fine + filterReader.getOptions({}) # reads fine + + # test if filter has failregex set + self.assertTrue(filterReader._opts.get('failregex', '').strip()) + + actions = jails.get(jail, 'action') + self.assertTrue(len(actions.strip())) + + # somewhat duplicating here what is done in JailsReader if + # the jail is enabled + for act in actions.split('\n'): + actName, actOpt = extractOptions(act) + self.assertTrue(len(actName)) + self.assertTrue(isinstance(actOpt, dict)) + if actName == 'iptables-multiport': + self.assertIn('port', actOpt) + + actionReader = ActionReader(actName, jail, {}, share_config=CONFIG_DIR_SHARE_CFG, basedir=CONFIG_DIR) - self.assertTrue(filterReader.read(),"Failed to read filter:" + filterName) # opens fine - filterReader.getOptions({}) # reads fine + self.assertTrue(actionReader.read()) + actionReader.getOptions({}) # populate _opts + cmds = actionReader.convert() + self.assertTrue(len(cmds)) - # test if filter has failregex set - self.assertTrue(filterReader._opts.get('failregex', '').strip()) + # all must have some actionban + self.assertTrue(actionReader._opts.get('actionban', '').strip()) - actions = jails.get(jail, 'action') - self.assertTrue(len(actions.strip())) + # Verify that all filters found under config/ have a jail + def testReadStockJailFilterComplete(self): + unittest.F2B.SkipIfCfgMissing(stock=True) + jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=CONFIG_DIR_SHARE_CFG) + self.assertTrue(jails.read()) # opens fine + self.assertTrue(jails.getOptions()) # reads fine + # grab all filter names + filters = set(os.path.splitext(os.path.split(a)[1])[0] + for a in glob.glob(os.path.join('config', 'filter.d', '*.conf')) + if not (a.endswith('common.conf') or a.endswith('-aggressive.conf'))) + # get filters of all jails (filter names without options inside filter[...]) + filters_jail = set( + extractOptions(jail.options['filter'])[0] for jail in jails.jails + ) + self.maxDiff = None + self.assertTrue(filters.issubset(filters_jail), + "More filters exists than are referenced in stock jail.conf %r" % filters.difference(filters_jail)) + self.assertTrue(filters_jail.issubset(filters), + "Stock jail.conf references non-existent filters %r" % filters_jail.difference(filters)) - # somewhat duplicating here what is done in JailsReader if - # the jail is enabled - for act in actions.split('\n'): - actName, actOpt = extractOptions(act) - self.assertTrue(len(actName)) - self.assertTrue(isinstance(actOpt, dict)) - if actName == 'iptables-multiport': - self.assertIn('port', actOpt) + def testReadStockJailConfForceEnabled(self): + unittest.F2B.SkipIfCfgMissing(stock=True) + # more of a smoke test to make sure that no obvious surprises + # on users' systems when enabling shipped jails + jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=CONFIG_DIR_SHARE_CFG) # we are running tests from root project dir atm + self.assertTrue(jails.read()) # opens fine + self.assertTrue(jails.getOptions()) # reads fine + comm_commands = jails.convert(allow_no_files=True) - actionReader = ActionReader(actName, jail, {}, - share_config=CONFIG_DIR_SHARE_CFG, basedir=CONFIG_DIR) - self.assertTrue(actionReader.read()) - actionReader.getOptions({}) # populate _opts - cmds = actionReader.convert() - self.assertTrue(len(cmds)) + # by default we have lots of jails ;) + self.assertTrue(len(comm_commands)) - # all must have some actionban - self.assertTrue(actionReader._opts.get('actionban', '').strip()) + # some common sanity checks for commands + for command in comm_commands: + if len(command) >= 3 and [command[0], command[2]] == ['set', 'bantime']: + self.assertTrue(MyTime.str2seconds(command[3]) > 0) + - # Verify that all filters found under config/ have a jail - def testReadStockJailFilterComplete(self): - jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=CONFIG_DIR_SHARE_CFG) - self.assertTrue(jails.read()) # opens fine - self.assertTrue(jails.getOptions()) # reads fine - # grab all filter names - filters = set(os.path.splitext(os.path.split(a)[1])[0] - for a in glob.glob(os.path.join('config', 'filter.d', '*.conf')) - if not (a.endswith('common.conf') or a.endswith('-aggressive.conf'))) - # get filters of all jails (filter names without options inside filter[...]) - filters_jail = set( - extractOptions(jail.options['filter'])[0] for jail in jails.jails - ) - self.maxDiff = None - self.assertTrue(filters.issubset(filters_jail), - "More filters exists than are referenced in stock jail.conf %r" % filters.difference(filters_jail)) - self.assertTrue(filters_jail.issubset(filters), - "Stock jail.conf references non-existent filters %r" % filters_jail.difference(filters)) + # and we know even some of them by heart + for j in ['sshd', 'recidive']: + # by default we have 'auto' backend ATM, but some distributions can overwrite it, + # (e.g. fedora default is 'systemd') therefore let check it without backend... + self.assertIn(['add', j], + (cmd[:2] for cmd in comm_commands if len(cmd) == 3 and cmd[0] == 'add')) + # and warn on useDNS + self.assertIn(['set', j, 'usedns', 'warn'], comm_commands) + self.assertIn(['start', j], comm_commands) - def testReadStockJailConfForceEnabled(self): - # more of a smoke test to make sure that no obvious surprises - # on users' systems when enabling shipped jails - jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=CONFIG_DIR_SHARE_CFG) # we are running tests from root project dir atm - self.assertTrue(jails.read()) # opens fine - self.assertTrue(jails.getOptions()) # reads fine - comm_commands = jails.convert(allow_no_files=True) + # last commands should be the 'start' commands + self.assertEqual(comm_commands[-1][0], 'start') - # by default we have lots of jails ;) - self.assertTrue(len(comm_commands)) + for j in jails._JailsReader__jails: + actions = j._JailReader__actions + jail_name = j.getName() + # make sure that all of the jails have actions assigned, + # otherwise it makes little to no sense + self.assertTrue(len(actions), + msg="No actions found for jail %s" % jail_name) - # some common sanity checks for commands - for command in comm_commands: - if len(command) >= 3 and [command[0], command[2]] == ['set', 'bantime']: - self.assertTrue(MyTime.str2seconds(command[3]) > 0) - + # Test for presence of blocktype (in relation to gh-232) + for action in actions: + commands = action.convert() + action_name = action.getName() + if '' in str(commands): + # Verify that it is among cInfo + self.assertIn('blocktype', action._initOpts) + # Verify that we have a call to set it up + blocktype_present = False + target_command = [jail_name, 'action', action_name] + for command in commands: + if (len(command) > 4 and command[0] == 'multi-set' and + command[1:4] == target_command): + blocktype_present = ('blocktype' in [cmd[0] for cmd in command[4]]) + elif (len(command) > 5 and command[0] == 'set' and + command[1:4] == target_command and command[4] == 'blocktype'): # pragma: no cover - because of multi-set + blocktype_present = True + if blocktype_present: + break + self.assertTrue( + blocktype_present, + msg="Found no %s command among %s" + % (target_command, str(commands)) ) - # and we know even some of them by heart - for j in ['sshd', 'recidive']: - # by default we have 'auto' backend ATM, but some distributions can overwrite it, - # (e.g. fedora default is 'systemd') therefore let check it without backend... - self.assertIn(['add', j], - (cmd[:2] for cmd in comm_commands if len(cmd) == 3 and cmd[0] == 'add')) - # and warn on useDNS - self.assertIn(['set', j, 'usedns', 'warn'], comm_commands) - self.assertIn(['start', j], comm_commands) + def testStockConfigurator(self): + unittest.F2B.SkipIfCfgMissing(stock=True) + configurator = Configurator() + configurator.setBaseDir(CONFIG_DIR) + self.assertEqual(configurator.getBaseDir(), CONFIG_DIR) - # last commands should be the 'start' commands - self.assertEqual(comm_commands[-1][0], 'start') + configurator.readEarly() + opts = configurator.getEarlyOptions() + # our current default settings + self.assertEqual(opts['socket'], '/var/run/fail2ban/fail2ban.sock') + self.assertEqual(opts['pidfile'], '/var/run/fail2ban/fail2ban.pid') - for j in jails._JailsReader__jails: - actions = j._JailReader__actions - jail_name = j.getName() - # make sure that all of the jails have actions assigned, - # otherwise it makes little to no sense - self.assertTrue(len(actions), - msg="No actions found for jail %s" % jail_name) + configurator.readAll() + configurator.getOptions() + configurator.convertToProtocol() + commands = configurator.getConfigStream() - # Test for presence of blocktype (in relation to gh-232) - for action in actions: - commands = action.convert() - action_name = action.getName() - if '' in str(commands): - # Verify that it is among cInfo - self.assertIn('blocktype', action._initOpts) - # Verify that we have a call to set it up - blocktype_present = False - target_command = [jail_name, 'action', action_name] - for command in commands: - if (len(command) > 4 and command[0] == 'multi-set' and - command[1:4] == target_command): - blocktype_present = ('blocktype' in [cmd[0] for cmd in command[4]]) - elif (len(command) > 5 and command[0] == 'set' and - command[1:4] == target_command and command[4] == 'blocktype'): # pragma: no cover - because of multi-set - blocktype_present = True - if blocktype_present: - break - self.assertTrue( - blocktype_present, - msg="Found no %s command among %s" - % (target_command, str(commands)) ) + # verify that dbfile comes before dbpurgeage + def find_set(option): + for i, e in enumerate(commands): + if e[0] == 'set' and e[1] == option: + return i + raise ValueError("Did not find command 'set %s' among commands %s" + % (option, commands)) - def testStockConfigurator(self): - configurator = Configurator() - configurator.setBaseDir(CONFIG_DIR) - self.assertEqual(configurator.getBaseDir(), CONFIG_DIR) + # Set up of logging should come first + self.assertEqual(find_set('syslogsocket'), 0) + self.assertEqual(find_set('loglevel'), 1) + self.assertEqual(find_set('logtarget'), 2) + # then dbfile should be before dbpurgeage + self.assertTrue(find_set('dbpurgeage') > find_set('dbfile')) - configurator.readEarly() - opts = configurator.getEarlyOptions() - # our current default settings - self.assertEqual(opts['socket'], '/var/run/fail2ban/fail2ban.sock') - self.assertEqual(opts['pidfile'], '/var/run/fail2ban/fail2ban.pid') + # and there is logging information left to be passed into the + # server + self.assertSortedEqual(commands, + [['set', 'dbfile', + '/var/lib/fail2ban/fail2ban.sqlite3'], + ['set', 'dbpurgeage', '1d'], + ['set', 'loglevel', "INFO"], + ['set', 'logtarget', '/var/log/fail2ban.log'], + ['set', 'syslogsocket', 'auto']]) - configurator.readAll() - configurator.getOptions() - configurator.convertToProtocol() - commands = configurator.getConfigStream() - - # verify that dbfile comes before dbpurgeage - def find_set(option): - for i, e in enumerate(commands): - if e[0] == 'set' and e[1] == option: - return i - raise ValueError("Did not find command 'set %s' among commands %s" - % (option, commands)) - - # Set up of logging should come first - self.assertEqual(find_set('syslogsocket'), 0) - self.assertEqual(find_set('loglevel'), 1) - self.assertEqual(find_set('logtarget'), 2) - # then dbfile should be before dbpurgeage - self.assertTrue(find_set('dbpurgeage') > find_set('dbfile')) - - # and there is logging information left to be passed into the - # server - self.assertSortedEqual(commands, - [['set', 'dbfile', - '/var/lib/fail2ban/fail2ban.sqlite3'], - ['set', 'dbpurgeage', '1d'], - ['set', 'loglevel', "INFO"], - ['set', 'logtarget', '/var/log/fail2ban.log'], - ['set', 'syslogsocket', 'auto']]) - - # and if we force change configurator's fail2ban's baseDir - # there should be an error message (test visually ;) -- - # otherwise just a code smoke test) - configurator._Configurator__jails.setBaseDir('/tmp') - self.assertEqual(configurator._Configurator__jails.getBaseDir(), '/tmp') - self.assertEqual(configurator.getBaseDir(), CONFIG_DIR) + # and if we force change configurator's fail2ban's baseDir + # there should be an error message (test visually ;) -- + # otherwise just a code smoke test) + configurator._Configurator__jails.setBaseDir('/tmp') + self.assertEqual(configurator._Configurator__jails.getBaseDir(), '/tmp') + self.assertEqual(configurator.getBaseDir(), CONFIG_DIR) @with_tmpdir def testMultipleSameAction(self, basedir): diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index e2cb8ab4..8e175006 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -43,16 +43,14 @@ from .. import protocol from ..server import server from ..server.mytime import MyTime from ..server.utils import Utils -from .utils import LogCaptureTestCase, logSys as DefLogSys, with_tmpdir, shutil, logging -from .utils import CONFIG_DIR as STOCK_CONF_DIR +from .utils import LogCaptureTestCase, logSys as DefLogSys, with_tmpdir, shutil, logging, \ + STOCK, CONFIG_DIR as STOCK_CONF_DIR from ..helpers import getLogger # Gets the instance of the logger. logSys = getLogger(__name__) -STOCK = exists(pjoin(STOCK_CONF_DIR, 'fail2ban.conf')) - CLIENT = "fail2ban-client" SERVER = "fail2ban-server" BIN = dirname(Fail2banServer.getServerPath()) @@ -1171,6 +1169,7 @@ class Fail2banServerTest(Fail2banClientServerBase): "Jail 'test-jail1' started", all=True) # test action.d/nginx-block-map.conf -- + @unittest.F2B.skip_if_cfg_missing(action="nginx-block-map") @with_foreground_server_thread(startextra={ # create log-file (avoid "not found" errors): 'create_before_start': ('%(tmp)s/blck-failures.log',), diff --git a/fail2ban/tests/filtertestcase.py b/fail2ban/tests/filtertestcase.py index 40cf51f9..59c7820a 100644 --- a/fail2ban/tests/filtertestcase.py +++ b/fail2ban/tests/filtertestcase.py @@ -24,7 +24,6 @@ __license__ = "GPL" from __builtin__ import open as fopen import unittest -import getpass import os import sys import time, datetime @@ -43,14 +42,12 @@ from ..server.failmanager import FailManagerEmpty from ..server.ipdns import DNSUtils, IPAddr from ..server.mytime import MyTime from ..server.utils import Utils, uni_decode -from .utils import setUpMyTime, tearDownMyTime, mtimesleep, with_tmpdir, LogCaptureTestCase +from .utils import setUpMyTime, tearDownMyTime, mtimesleep, with_tmpdir, LogCaptureTestCase, \ + CONFIG_DIR as STOCK_CONF_DIR from .dummyjail import DummyJail TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files") -STOCK_CONF_DIR = "config" -STOCK = os.path.exists(os.path.join(STOCK_CONF_DIR, 'fail2ban.conf')) - # yoh: per Steven Hiscocks's insight while troubleshooting # https://github.com/fail2ban/fail2ban/issues/103#issuecomment-15542836 @@ -445,8 +442,7 @@ class IgnoreIPDNS(LogCaptureTestCase): self.assertFalse(self.filter.inIgnoreIPList("128.178.222.70")) def testIgnoreCmdApacheFakegooglebot(self): - if not STOCK: # pragma: no cover - raise unittest.SkipTest('Skip test because of no STOCK config') + unittest.F2B.SkipIfCfgMissing(stock=True) cmd = os.path.join(STOCK_CONF_DIR, "filter.d/ignorecommands/apache-fakegooglebot") ## below test direct as python module: mod = Utils.load_python_module(cmd) diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index 8485aa74..d18b660c 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -1034,7 +1034,7 @@ class LoggingTests(LogCaptureTestCase): os.remove(f) -from clientreadertestcase import ActionReader, JailsReader, CONFIG_DIR, STOCK +from clientreadertestcase import ActionReader, JailsReader, CONFIG_DIR class ServerConfigReaderTests(LogCaptureTestCase): @@ -1091,814 +1091,816 @@ class ServerConfigReaderTests(LogCaptureTestCase): logSys.debug('# === stop ==='); self.pruneLog() action.stop() - if STOCK: + def testCheckStockJailActions(self): + unittest.F2B.SkipIfCfgMissing(stock=True) + # we are running tests from root project dir atm + jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=self.__share_cfg) + self.assertTrue(jails.read()) # opens fine + self.assertTrue(jails.getOptions()) # reads fine + stream = jails.convert(allow_no_files=True) - def testCheckStockJailActions(self): - # we are running tests from root project dir atm - jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=self.__share_cfg) - self.assertTrue(jails.read()) # opens fine - self.assertTrue(jails.getOptions()) # reads fine - stream = jails.convert(allow_no_files=True) + server = TestServer() + transm = server._Server__transm + cmdHandler = transm._Transmitter__commandHandler - server = TestServer() - transm = server._Server__transm - cmdHandler = transm._Transmitter__commandHandler + # for cmd in stream: + # print(cmd) + + # filter all start commands (we want not start all jails): + for cmd in stream: + if cmd[0] != 'start': + # change to the fast init backend: + if cmd[0] == 'add': + cmd[2] = 'polling' + # change log path to test log of the jail + # (to prevent "Permission denied" on /var/logs/ for test-user): + elif len(cmd) > 3 and cmd[0] == 'set' and cmd[2] == 'addlogpath': + fn = os.path.join(TEST_FILES_DIR, 'logs', cmd[1]) + # fallback to testcase01 if jail has no its own test log-file + # (should not matter really): + if not os.path.exists(fn): # pragma: no cover + fn = os.path.join(TEST_FILES_DIR, 'testcase01.log') + cmd[3] = fn + # if fast add dummy regex to prevent too long compile of all regexp + # (we don't use it in this test at all): + elif unittest.F2B.fast and ( + len(cmd) > 3 and cmd[0] in ('set', 'multi-set') and cmd[2] == 'addfailregex' + ): # pragma: no cover + cmd[0] = "set" + cmd[3] = "DUMMY-REGEX " + # command to server, use cmdHandler direct instead of `transm.proceed(cmd)`: + try: + cmdHandler(cmd) + except Exception as e: # pragma: no cover + self.fail("Command %r has failed. Received %r" % (cmd, e)) + + # jails = server._Server__jails + # for j in jails: + # print(j, jails[j]) + + # test default stock actions sepecified in all stock jails: + if not unittest.F2B.fast: + self._testExecActions(server) + + def getDefaultJailStream(self, jail, act): + act = act.replace('%(__name__)s', jail) + actName, actOpt = extractOptions(act) + stream = [ + ['add', jail, 'polling'], + # ['set', jail, 'addfailregex', 'DUMMY-REGEX '], + ] + action = ActionReader( + actName, jail, actOpt, + share_config=self.__share_cfg, basedir=CONFIG_DIR) + self.assertTrue(action.read()) + action.getOptions({}) + stream.extend(action.convert()) + return stream + + def testCheckStockAllActions(self): + unittest.F2B.SkipIfCfgMissing(stock=True) + unittest.F2B.SkipIfFast() + import glob + + server = TestServer() + transm = server._Server__transm + + for actCfg in glob.glob(os.path.join(CONFIG_DIR, 'action.d', '*.conf')): + act = os.path.basename(actCfg).replace('.conf', '') + # transmit artifical jail with each action to the server: + stream = self.getDefaultJailStream('j-'+act, act) + for cmd in stream: + # command to server: + ret, res = transm.proceed(cmd) + self.assertEqual(ret, 0) + # test executing action commands: + self._testExecActions(server) + + + def testCheckStockCommandActions(self): + unittest.F2B.SkipIfCfgMissing(stock=True) + # test cases to check valid ipv4/ipv6 action definition, tuple with (('jail', 'action[params]', 'tests', ...) + # where tests is a dictionary contains: + # 'ip4' - should not be found (logged) on ban/unban of IPv6 (negative test), + # 'ip6' - should not be found (logged) on ban/unban of IPv4 (negative test), + # 'start', 'stop' - should be found (logged) on action start/stop, + # etc. + testJailsActions = ( + # dummy -- + ('j-dummy', 'dummy[name=%(__name__)s, init="==", target="/tmp/fail2ban.dummy"]', { + 'ip4': ('family: inet4',), 'ip6': ('family: inet6',), + 'start': ( + '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- started"`', + ), + 'flush': ( + '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- clear all"`', + ), + 'stop': ( + '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- stopped"`', + ), + 'ip4-check': (), + 'ip6-check': (), + 'ip4-ban': ( + '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- banned 192.0.2.1 (family: inet4)"`', + ), + 'ip4-unban': ( + '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- unbanned 192.0.2.1 (family: inet4)"`', + ), + 'ip6-ban': ( + '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- banned 2001:db8:: (family: inet6)"`', + ), + 'ip6-unban': ( + '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- unbanned 2001:db8:: (family: inet6)"`', + ), + }), + # iptables-multiport -- + ('j-w-iptables-mp', 'iptables-multiport[name=%(__name__)s, bantime="10m", port="http,https", protocol="tcp", chain=""]', { + 'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'), + 'ip4-start': ( + "`iptables -w -N f2b-j-w-iptables-mp`", + "`iptables -w -A f2b-j-w-iptables-mp -j RETURN`", + "`iptables -w -I INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`", + ), + 'ip6-start': ( + "`ip6tables -w -N f2b-j-w-iptables-mp`", + "`ip6tables -w -A f2b-j-w-iptables-mp -j RETURN`", + "`ip6tables -w -I INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`", + ), + 'flush': ( + "`iptables -w -F f2b-j-w-iptables-mp`", + "`ip6tables -w -F f2b-j-w-iptables-mp`", + ), + 'stop': ( + "`iptables -w -D INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`", + "`iptables -w -F f2b-j-w-iptables-mp`", + "`iptables -w -X f2b-j-w-iptables-mp`", + "`ip6tables -w -D INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`", + "`ip6tables -w -F f2b-j-w-iptables-mp`", + "`ip6tables -w -X f2b-j-w-iptables-mp`", + ), + 'ip4-check': ( + r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-mp[ \t]'`""", + ), + 'ip6-check': ( + r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-mp[ \t]'`""", + ), + 'ip4-ban': ( + r"`iptables -w -I f2b-j-w-iptables-mp 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip4-unban': ( + r"`iptables -w -D f2b-j-w-iptables-mp -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip6-ban': ( + r"`ip6tables -w -I f2b-j-w-iptables-mp 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", + ), + 'ip6-unban': ( + r"`ip6tables -w -D f2b-j-w-iptables-mp -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", + ), + }), + # iptables-allports -- + ('j-w-iptables-ap', 'iptables-allports[name=%(__name__)s, bantime="10m", protocol="tcp", chain=""]', { + 'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'), + 'ip4-start': ( + "`iptables -w -N f2b-j-w-iptables-ap`", + "`iptables -w -A f2b-j-w-iptables-ap -j RETURN`", + "`iptables -w -I INPUT -p tcp -j f2b-j-w-iptables-ap`", + ), + 'ip6-start': ( + "`ip6tables -w -N f2b-j-w-iptables-ap`", + "`ip6tables -w -A f2b-j-w-iptables-ap -j RETURN`", + "`ip6tables -w -I INPUT -p tcp -j f2b-j-w-iptables-ap`", + ), + 'flush': ( + "`iptables -w -F f2b-j-w-iptables-ap`", + "`ip6tables -w -F f2b-j-w-iptables-ap`", + ), + 'stop': ( + "`iptables -w -D INPUT -p tcp -j f2b-j-w-iptables-ap`", + "`iptables -w -F f2b-j-w-iptables-ap`", + "`iptables -w -X f2b-j-w-iptables-ap`", + "`ip6tables -w -D INPUT -p tcp -j f2b-j-w-iptables-ap`", + "`ip6tables -w -F f2b-j-w-iptables-ap`", + "`ip6tables -w -X f2b-j-w-iptables-ap`", + ), + 'ip4-check': ( + r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-ap[ \t]'`""", + ), + 'ip6-check': ( + r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-ap[ \t]'`""", + ), + 'ip4-ban': ( + r"`iptables -w -I f2b-j-w-iptables-ap 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip4-unban': ( + r"`iptables -w -D f2b-j-w-iptables-ap -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip6-ban': ( + r"`ip6tables -w -I f2b-j-w-iptables-ap 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", + ), + 'ip6-unban': ( + r"`ip6tables -w -D f2b-j-w-iptables-ap -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", + ), + }), + # iptables-ipset-proto6 -- + ('j-w-iptables-ipset', 'iptables-ipset-proto6[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain=""]', { + 'ip4': (' f2b-j-w-iptables-ipset ',), 'ip6': (' f2b-j-w-iptables-ipset6 ',), + 'ip4-start': ( + "`ipset create f2b-j-w-iptables-ipset hash:ip timeout 600`", + "`iptables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset src -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip6-start': ( + "`ipset create f2b-j-w-iptables-ipset6 hash:ip timeout 600 family inet6`", + "`ip6tables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`", + ), + 'flush': ( + "`ipset flush f2b-j-w-iptables-ipset`", + "`ipset flush f2b-j-w-iptables-ipset6`", + ), + 'stop': ( + "`iptables -w -D INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset src -j REJECT --reject-with icmp-port-unreachable`", + "`ipset flush f2b-j-w-iptables-ipset`", + "`ipset destroy f2b-j-w-iptables-ipset`", + "`ip6tables -w -D INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`", + "`ipset flush f2b-j-w-iptables-ipset6`", + "`ipset destroy f2b-j-w-iptables-ipset6`", + ), + 'ip4-check': (), + 'ip6-check': (), + 'ip4-ban': ( + r"`ipset add f2b-j-w-iptables-ipset 192.0.2.1 timeout 600 -exist`", + ), + 'ip4-unban': ( + r"`ipset del f2b-j-w-iptables-ipset 192.0.2.1 -exist`", + ), + 'ip6-ban': ( + r"`ipset add f2b-j-w-iptables-ipset6 2001:db8:: timeout 600 -exist`", + ), + 'ip6-unban': ( + r"`ipset del f2b-j-w-iptables-ipset6 2001:db8:: -exist`", + ), + }), + # iptables-ipset-proto6-allports -- + ('j-w-iptables-ipset-ap', 'iptables-ipset-proto6-allports[name=%(__name__)s, bantime="10m", chain=""]', { + 'ip4': (' f2b-j-w-iptables-ipset-ap ',), 'ip6': (' f2b-j-w-iptables-ipset-ap6 ',), + 'ip4-start': ( + "`ipset create f2b-j-w-iptables-ipset-ap hash:ip timeout 600`", + "`iptables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip6-start': ( + "`ipset create f2b-j-w-iptables-ipset-ap6 hash:ip timeout 600 family inet6`", + "`ip6tables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`", + ), + 'flush': ( + "`ipset flush f2b-j-w-iptables-ipset-ap`", + "`ipset flush f2b-j-w-iptables-ipset-ap6`", + ), + 'stop': ( + "`iptables -w -D INPUT -m set --match-set f2b-j-w-iptables-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`", + "`ipset flush f2b-j-w-iptables-ipset-ap`", + "`ipset destroy f2b-j-w-iptables-ipset-ap`", + "`ip6tables -w -D INPUT -m set --match-set f2b-j-w-iptables-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`", + "`ipset flush f2b-j-w-iptables-ipset-ap6`", + "`ipset destroy f2b-j-w-iptables-ipset-ap6`", + ), + 'ip4-check': (), + 'ip6-check': (), + 'ip4-ban': ( + r"`ipset add f2b-j-w-iptables-ipset-ap 192.0.2.1 timeout 600 -exist`", + ), + 'ip4-unban': ( + r"`ipset del f2b-j-w-iptables-ipset-ap 192.0.2.1 -exist`", + ), + 'ip6-ban': ( + r"`ipset add f2b-j-w-iptables-ipset-ap6 2001:db8:: timeout 600 -exist`", + ), + 'ip6-unban': ( + r"`ipset del f2b-j-w-iptables-ipset-ap6 2001:db8:: -exist`", + ), + }), + # iptables -- + ('j-w-iptables', 'iptables[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain=""]', { + 'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'), + 'ip4-start': ( + "`iptables -w -N f2b-j-w-iptables`", + "`iptables -w -A f2b-j-w-iptables -j RETURN`", + "`iptables -w -I INPUT -p tcp --dport http -j f2b-j-w-iptables`", + ), + 'ip6-start': ( + "`ip6tables -w -N f2b-j-w-iptables`", + "`ip6tables -w -A f2b-j-w-iptables -j RETURN`", + "`ip6tables -w -I INPUT -p tcp --dport http -j f2b-j-w-iptables`", + ), + 'flush': ( + "`iptables -w -F f2b-j-w-iptables`", + "`ip6tables -w -F f2b-j-w-iptables`", + ), + 'stop': ( + "`iptables -w -D INPUT -p tcp --dport http -j f2b-j-w-iptables`", + "`iptables -w -F f2b-j-w-iptables`", + "`iptables -w -X f2b-j-w-iptables`", + "`ip6tables -w -D INPUT -p tcp --dport http -j f2b-j-w-iptables`", + "`ip6tables -w -F f2b-j-w-iptables`", + "`ip6tables -w -X f2b-j-w-iptables`", + ), + 'ip4-check': ( + r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables[ \t]'`""", + ), + 'ip6-check': ( + r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables[ \t]'`""", + ), + 'ip4-ban': ( + r"`iptables -w -I f2b-j-w-iptables 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip4-unban': ( + r"`iptables -w -D f2b-j-w-iptables -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip6-ban': ( + r"`ip6tables -w -I f2b-j-w-iptables 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", + ), + 'ip6-unban': ( + r"`ip6tables -w -D f2b-j-w-iptables -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", + ), + }), + # iptables-new -- + ('j-w-iptables-new', 'iptables-new[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain=""]', { + 'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'), + 'ip4-start': ( + "`iptables -w -N f2b-j-w-iptables-new`", + "`iptables -w -A f2b-j-w-iptables-new -j RETURN`", + "`iptables -w -I INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`", + ), + 'ip6-start': ( + "`ip6tables -w -N f2b-j-w-iptables-new`", + "`ip6tables -w -A f2b-j-w-iptables-new -j RETURN`", + "`ip6tables -w -I INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`", + ), + 'flush': ( + "`iptables -w -F f2b-j-w-iptables-new`", + "`ip6tables -w -F f2b-j-w-iptables-new`", + ), + 'stop': ( + "`iptables -w -D INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`", + "`iptables -w -F f2b-j-w-iptables-new`", + "`iptables -w -X f2b-j-w-iptables-new`", + "`ip6tables -w -D INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`", + "`ip6tables -w -F f2b-j-w-iptables-new`", + "`ip6tables -w -X f2b-j-w-iptables-new`", + ), + 'ip4-check': ( + r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-new[ \t]'`""", + ), + 'ip6-check': ( + r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-new[ \t]'`""", + ), + 'ip4-ban': ( + r"`iptables -w -I f2b-j-w-iptables-new 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip4-unban': ( + r"`iptables -w -D f2b-j-w-iptables-new -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip6-ban': ( + r"`ip6tables -w -I f2b-j-w-iptables-new 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", + ), + 'ip6-unban': ( + r"`ip6tables -w -D f2b-j-w-iptables-new -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", + ), + }), + # iptables-xt_recent-echo -- + ('j-w-iptables-xtre', 'iptables-xt_recent-echo[name=%(__name__)s, bantime="10m", chain=""]', { + 'ip4': ('`iptables ', '/f2b-j-w-iptables-xtre`'), 'ip6': ('`ip6tables ', '/f2b-j-w-iptables-xtre6`'), + 'ip4-start': ( + "`if [ `id -u` -eq 0 ];then iptables -w -I INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre -j REJECT --reject-with icmp-port-unreachable;fi`", + ), + 'ip6-start': ( + "`if [ `id -u` -eq 0 ];then ip6tables -w -I INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre6 -j REJECT --reject-with icmp6-port-unreachable;fi`", + ), + 'stop': ( + "`echo / > /proc/net/xt_recent/f2b-j-w-iptables-xtre`", + "`if [ `id -u` -eq 0 ];then iptables -w -D INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre -j REJECT --reject-with icmp-port-unreachable;fi`", + "`echo / > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`", + "`if [ `id -u` -eq 0 ];then ip6tables -w -D INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre6 -j REJECT --reject-with icmp6-port-unreachable;fi`", + ), + 'ip4-check': ( + r"`test -e /proc/net/xt_recent/f2b-j-w-iptables-xtre`", + ), + 'ip6-check': ( + r"`test -e /proc/net/xt_recent/f2b-j-w-iptables-xtre6`", + ), + 'ip4-ban': ( + r"`echo +192.0.2.1 > /proc/net/xt_recent/f2b-j-w-iptables-xtre`", + ), + 'ip4-unban': ( + r"`echo -192.0.2.1 > /proc/net/xt_recent/f2b-j-w-iptables-xtre`", + ), + 'ip6-ban': ( + r"`echo +2001:db8:: > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`", + ), + 'ip6-unban': ( + r"`echo -2001:db8:: > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`", + ), + }), + # pf default -- multiport on default port (tag set in jail.conf, but not in this test case) + ('j-w-pf', 'pf[name=%(__name__)s, actionstart_on_demand=false]', { + 'ip4': (), 'ip6': (), + 'start': ( + '`echo "table persist counters" | pfctl -a f2b/j-w-pf -f-`', + 'port=""', + '`echo "block quick proto tcp from to any port $port" | pfctl -a f2b/j-w-pf -f-`', + ), + 'flush': ( + '`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T flush`', + ), + 'stop': ( + '`pfctl -a f2b/j-w-pf -sr 2>/dev/null | grep -v f2b-j-w-pf | pfctl -a f2b/j-w-pf -f-`', + '`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T flush`', + '`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T kill`', + ), + 'ip4-check': ("`pfctl -a f2b/j-w-pf -sr | grep -q f2b-j-w-pf`",), + 'ip6-check': ("`pfctl -a f2b/j-w-pf -sr | grep -q f2b-j-w-pf`",), + 'ip4-ban': ("`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T add 192.0.2.1`",), + 'ip4-unban': ("`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T delete 192.0.2.1`",), + 'ip6-ban': ("`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T add 2001:db8::`",), + 'ip6-unban': ("`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T delete 2001:db8::`",), + }), + # pf multiport with custom ports -- + ('j-w-pf-mp', 'pf[actiontype=][name=%(__name__)s, port="http,https"]', { + 'ip4': (), 'ip6': (), + 'start': ( + '`echo "table persist counters" | pfctl -a f2b/j-w-pf-mp -f-`', + 'port="http,https"', + '`echo "block quick proto tcp from to any port $port" | pfctl -a f2b/j-w-pf-mp -f-`', + ), + 'flush': ( + '`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T flush`', + ), + 'stop': ( + '`pfctl -a f2b/j-w-pf-mp -sr 2>/dev/null | grep -v f2b-j-w-pf-mp | pfctl -a f2b/j-w-pf-mp -f-`', + '`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T flush`', + '`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T kill`', + ), + 'ip4-check': ("`pfctl -a f2b/j-w-pf-mp -sr | grep -q f2b-j-w-pf-mp`",), + 'ip6-check': ("`pfctl -a f2b/j-w-pf-mp -sr | grep -q f2b-j-w-pf-mp`",), + 'ip4-ban': ("`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T add 192.0.2.1`",), + 'ip4-unban': ("`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T delete 192.0.2.1`",), + 'ip6-ban': ("`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T add 2001:db8::`",), + 'ip6-unban': ("`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T delete 2001:db8::`",), + }), + # pf allports -- test additionally "actionstart_on_demand" was set to true + ('j-w-pf-ap', 'pf[actiontype=, actionstart_on_demand=true][name=%(__name__)s]', { + 'ip4': (), 'ip6': (), + 'ip4-start': ( + '`echo "table persist counters" | pfctl -a f2b/j-w-pf-ap -f-`', + '`echo "block quick proto tcp from to any" | pfctl -a f2b/j-w-pf-ap -f-`', + ), + 'ip6-start': (), # the same as ipv4 + 'flush': ( + '`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T flush`', + ), + 'stop': ( + '`pfctl -a f2b/j-w-pf-ap -sr 2>/dev/null | grep -v f2b-j-w-pf-ap | pfctl -a f2b/j-w-pf-ap -f-`', + '`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T flush`', + '`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T kill`', + ), + 'ip4-check': ("`pfctl -a f2b/j-w-pf-ap -sr | grep -q f2b-j-w-pf-ap`",), + 'ip6-check': ("`pfctl -a f2b/j-w-pf-ap -sr | grep -q f2b-j-w-pf-ap`",), + 'ip4-ban': ("`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T add 192.0.2.1`",), + 'ip4-unban': ("`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T delete 192.0.2.1`",), + 'ip6-ban': ("`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T add 2001:db8::`",), + 'ip6-unban': ("`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T delete 2001:db8::`",), + }), + # firewallcmd-multiport -- + ('j-w-fwcmd-mp', 'firewallcmd-multiport[name=%(__name__)s, bantime="10m", port="http,https", protocol="tcp", chain=""]', { + 'ip4': (' ipv4 ', 'icmp-port-unreachable'), 'ip6': (' ipv6 ', 'icmp6-port-unreachable'), + 'ip4-start': ( + "`firewall-cmd --direct --add-chain ipv4 filter f2b-j-w-fwcmd-mp`", + "`firewall-cmd --direct --add-rule ipv4 filter f2b-j-w-fwcmd-mp 1000 -j RETURN`", + "`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -m conntrack --ctstate NEW -p tcp -m multiport --dports http,https -j f2b-j-w-fwcmd-mp`", + ), + 'ip6-start': ( + "`firewall-cmd --direct --add-chain ipv6 filter f2b-j-w-fwcmd-mp`", + "`firewall-cmd --direct --add-rule ipv6 filter f2b-j-w-fwcmd-mp 1000 -j RETURN`", + "`firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 -m conntrack --ctstate NEW -p tcp -m multiport --dports http,https -j f2b-j-w-fwcmd-mp`", + ), + 'stop': ( + "`firewall-cmd --direct --remove-rule ipv4 filter INPUT_direct 0 -m conntrack --ctstate NEW -p tcp -m multiport --dports http,https -j f2b-j-w-fwcmd-mp`", + "`firewall-cmd --direct --remove-rules ipv4 filter f2b-j-w-fwcmd-mp`", + "`firewall-cmd --direct --remove-chain ipv4 filter f2b-j-w-fwcmd-mp`", + "`firewall-cmd --direct --remove-rule ipv6 filter INPUT_direct 0 -m conntrack --ctstate NEW -p tcp -m multiport --dports http,https -j f2b-j-w-fwcmd-mp`", + "`firewall-cmd --direct --remove-rules ipv6 filter f2b-j-w-fwcmd-mp`", + "`firewall-cmd --direct --remove-chain ipv6 filter f2b-j-w-fwcmd-mp`", + ), + 'ip4-check': ( + r"`firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-j-w-fwcmd-mp$'`", + ), + 'ip6-check': ( + r"`firewall-cmd --direct --get-chains ipv6 filter | sed -e 's, ,\n,g' | grep -q '^f2b-j-w-fwcmd-mp$'`", + ), + 'ip4-ban': ( + r"`firewall-cmd --direct --add-rule ipv4 filter f2b-j-w-fwcmd-mp 0 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip4-unban': ( + r"`firewall-cmd --direct --remove-rule ipv4 filter f2b-j-w-fwcmd-mp 0 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip6-ban': ( + r"`firewall-cmd --direct --add-rule ipv6 filter f2b-j-w-fwcmd-mp 0 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", + ), + 'ip6-unban': ( + r"`firewall-cmd --direct --remove-rule ipv6 filter f2b-j-w-fwcmd-mp 0 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", + ), + }), + # firewallcmd-allports -- + ('j-w-fwcmd-ap', 'firewallcmd-allports[name=%(__name__)s, bantime="10m", protocol="tcp", chain=""]', { + 'ip4': (' ipv4 ', 'icmp-port-unreachable'), 'ip6': (' ipv6 ', 'icmp6-port-unreachable'), + 'ip4-start': ( + "`firewall-cmd --direct --add-chain ipv4 filter f2b-j-w-fwcmd-ap`", + "`firewall-cmd --direct --add-rule ipv4 filter f2b-j-w-fwcmd-ap 1000 -j RETURN`", + "`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -j f2b-j-w-fwcmd-ap`", + ), + 'ip6-start': ( + "`firewall-cmd --direct --add-chain ipv6 filter f2b-j-w-fwcmd-ap`", + "`firewall-cmd --direct --add-rule ipv6 filter f2b-j-w-fwcmd-ap 1000 -j RETURN`", + "`firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 -j f2b-j-w-fwcmd-ap`", + ), + 'stop': ( + "`firewall-cmd --direct --remove-rule ipv4 filter INPUT_direct 0 -j f2b-j-w-fwcmd-ap`", + "`firewall-cmd --direct --remove-rules ipv4 filter f2b-j-w-fwcmd-ap`", + "`firewall-cmd --direct --remove-chain ipv4 filter f2b-j-w-fwcmd-ap`", + "`firewall-cmd --direct --remove-rule ipv6 filter INPUT_direct 0 -j f2b-j-w-fwcmd-ap`", + "`firewall-cmd --direct --remove-rules ipv6 filter f2b-j-w-fwcmd-ap`", + "`firewall-cmd --direct --remove-chain ipv6 filter f2b-j-w-fwcmd-ap`", + ), + 'ip4-check': ( + r"`firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-j-w-fwcmd-ap$'`", + ), + 'ip6-check': ( + r"`firewall-cmd --direct --get-chains ipv6 filter | sed -e 's, ,\n,g' | grep -q '^f2b-j-w-fwcmd-ap$'`", + ), + 'ip4-ban': ( + r"`firewall-cmd --direct --add-rule ipv4 filter f2b-j-w-fwcmd-ap 0 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip4-unban': ( + r"`firewall-cmd --direct --remove-rule ipv4 filter f2b-j-w-fwcmd-ap 0 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip6-ban': ( + r"`firewall-cmd --direct --add-rule ipv6 filter f2b-j-w-fwcmd-ap 0 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", + ), + 'ip6-unban': ( + r"`firewall-cmd --direct --remove-rule ipv6 filter f2b-j-w-fwcmd-ap 0 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", + ), + }), + # firewallcmd-ipset (multiport) -- + ('j-w-fwcmd-ipset', 'firewallcmd-ipset[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain=""]', { + 'ip4': (' f2b-j-w-fwcmd-ipset ',), 'ip6': (' f2b-j-w-fwcmd-ipset6 ',), + 'ip4-start': ( + "`ipset create f2b-j-w-fwcmd-ipset hash:ip timeout 600`", + "`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset src -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip6-start': ( + "`ipset create f2b-j-w-fwcmd-ipset6 hash:ip timeout 600 family inet6`", + "`firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`", + ), + 'flush': ( + "`ipset flush f2b-j-w-fwcmd-ipset`", + "`ipset flush f2b-j-w-fwcmd-ipset6`", + ), + 'stop': ( + "`firewall-cmd --direct --remove-rule ipv4 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset src -j REJECT --reject-with icmp-port-unreachable`", + "`ipset flush f2b-j-w-fwcmd-ipset`", + "`ipset destroy f2b-j-w-fwcmd-ipset`", + "`firewall-cmd --direct --remove-rule ipv6 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`", + "`ipset flush f2b-j-w-fwcmd-ipset6`", + "`ipset destroy f2b-j-w-fwcmd-ipset6`", + ), + 'ip4-check': (), + 'ip6-check': (), + 'ip4-ban': ( + r"`ipset add f2b-j-w-fwcmd-ipset 192.0.2.1 timeout 600 -exist`", + ), + 'ip4-unban': ( + r"`ipset del f2b-j-w-fwcmd-ipset 192.0.2.1 -exist`", + ), + 'ip6-ban': ( + r"`ipset add f2b-j-w-fwcmd-ipset6 2001:db8:: timeout 600 -exist`", + ), + 'ip6-unban': ( + r"`ipset del f2b-j-w-fwcmd-ipset6 2001:db8:: -exist`", + ), + }), + # firewallcmd-ipset (allports) -- + ('j-w-fwcmd-ipset-ap', 'firewallcmd-ipset[name=%(__name__)s, bantime="10m", actiontype=, protocol="tcp", chain=""]', { + 'ip4': (' f2b-j-w-fwcmd-ipset-ap ',), 'ip6': (' f2b-j-w-fwcmd-ipset-ap6 ',), + 'ip4-start': ( + "`ipset create f2b-j-w-fwcmd-ipset-ap hash:ip timeout 600`", + "`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -p tcp -m set --match-set f2b-j-w-fwcmd-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`", + ), + 'ip6-start': ( + "`ipset create f2b-j-w-fwcmd-ipset-ap6 hash:ip timeout 600 family inet6`", + "`firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 -p tcp -m set --match-set f2b-j-w-fwcmd-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`", + ), + 'flush': ( + "`ipset flush f2b-j-w-fwcmd-ipset-ap`", + "`ipset flush f2b-j-w-fwcmd-ipset-ap6`", + ), + 'stop': ( + "`firewall-cmd --direct --remove-rule ipv4 filter INPUT_direct 0 -p tcp -m set --match-set f2b-j-w-fwcmd-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`", + "`ipset flush f2b-j-w-fwcmd-ipset-ap`", + "`ipset destroy f2b-j-w-fwcmd-ipset-ap`", + "`firewall-cmd --direct --remove-rule ipv6 filter INPUT_direct 0 -p tcp -m set --match-set f2b-j-w-fwcmd-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`", + "`ipset flush f2b-j-w-fwcmd-ipset-ap6`", + "`ipset destroy f2b-j-w-fwcmd-ipset-ap6`", + ), + 'ip4-check': (), + 'ip6-check': (), + 'ip4-ban': ( + r"`ipset add f2b-j-w-fwcmd-ipset-ap 192.0.2.1 timeout 600 -exist`", + ), + 'ip4-unban': ( + r"`ipset del f2b-j-w-fwcmd-ipset-ap 192.0.2.1 -exist`", + ), + 'ip6-ban': ( + r"`ipset add f2b-j-w-fwcmd-ipset-ap6 2001:db8:: timeout 600 -exist`", + ), + 'ip6-unban': ( + r"`ipset del f2b-j-w-fwcmd-ipset-ap6 2001:db8:: -exist`", + ), + }), + ) + server = TestServer() + transm = server._Server__transm + cmdHandler = transm._Transmitter__commandHandler + + for jail, act, tests in testJailsActions: + stream = self.getDefaultJailStream(jail, act) # for cmd in stream: # print(cmd) - # filter all start commands (we want not start all jails): + # transmit jail to the server: for cmd in stream: - if cmd[0] != 'start': - # change to the fast init backend: - if cmd[0] == 'add': - cmd[2] = 'polling' - # change log path to test log of the jail - # (to prevent "Permission denied" on /var/logs/ for test-user): - elif len(cmd) > 3 and cmd[0] == 'set' and cmd[2] == 'addlogpath': - fn = os.path.join(TEST_FILES_DIR, 'logs', cmd[1]) - # fallback to testcase01 if jail has no its own test log-file - # (should not matter really): - if not os.path.exists(fn): # pragma: no cover - fn = os.path.join(TEST_FILES_DIR, 'testcase01.log') - cmd[3] = fn - # if fast add dummy regex to prevent too long compile of all regexp - # (we don't use it in this test at all): - elif unittest.F2B.fast and ( - len(cmd) > 3 and cmd[0] in ('set', 'multi-set') and cmd[2] == 'addfailregex' - ): # pragma: no cover - cmd[0] = "set" - cmd[3] = "DUMMY-REGEX " - # command to server, use cmdHandler direct instead of `transm.proceed(cmd)`: - try: - cmdHandler(cmd) - except Exception as e: # pragma: no cover - self.fail("Command %r has failed. Received %r" % (cmd, e)) + # command to server: + ret, res = transm.proceed(cmd) + self.assertEqual(ret, 0) - # jails = server._Server__jails - # for j in jails: - # print(j, jails[j]) + jails = server._Server__jails - # test default stock actions sepecified in all stock jails: - if not unittest.F2B.fast: - self._testExecActions(server) + tickets = { + 'ip4': BanTicket('192.0.2.1'), + 'ip6': BanTicket('2001:DB8::'), + } + for jail, act, tests in testJailsActions: + # print(jail, jails[jail]) + for a in jails[jail].actions: + action = jails[jail].actions[a] + logSys.debug('# ' + ('=' * 50)) + logSys.debug('# == %-44s ==', jail + ' - ' + action._name) + logSys.debug('# ' + ('=' * 50)) + self.assertTrue(isinstance(action, _actions.CommandAction)) + # wrap default command processor: + action.executeCmd = self._executeCmd + # test start : + self.pruneLog('# === start ===') + action.start() + if tests.get('start'): + self.assertLogged(*tests['start'], all=True) + else: + self.assertNotLogged(*tests['ip4-start']+tests['ip6-start'], all=True) + ainfo = { + 'ip4': _actions.Actions.ActionInfo(tickets['ip4'], jails[jail]), + 'ip6': _actions.Actions.ActionInfo(tickets['ip6'], jails[jail]), + } + # test ban ip4 : + self.pruneLog('# === ban-ipv4 ===') + action.ban(ainfo['ip4']) + if tests.get('ip4-start'): self.assertLogged(*tests['ip4-start'], all=True) + if tests.get('ip6-start'): self.assertNotLogged(*tests['ip6-start'], all=True) + self.assertLogged(*tests['ip4-check']+tests['ip4-ban'], all=True) + self.assertNotLogged(*tests['ip6'], all=True) + # test unban ip4 : + self.pruneLog('# === unban ipv4 ===') + action.unban(ainfo['ip4']) + self.assertLogged(*tests['ip4-check']+tests['ip4-unban'], all=True) + self.assertNotLogged(*tests['ip6'], all=True) + # test ban ip6 : + self.pruneLog('# === ban ipv6 ===') + action.ban(ainfo['ip6']) + if tests.get('ip6-start'): self.assertLogged(*tests['ip6-start'], all=True) + if tests.get('ip4-start'): self.assertNotLogged(*tests['ip4-start'], all=True) + self.assertLogged(*tests['ip6-check']+tests['ip6-ban'], all=True) + self.assertNotLogged(*tests['ip4'], all=True) + # test unban ip6 : + self.pruneLog('# === unban ipv6 ===') + action.unban(ainfo['ip6']) + self.assertLogged(*tests['ip6-check']+tests['ip6-unban'], all=True) + self.assertNotLogged(*tests['ip4'], all=True) + # test flush for actions should supported this: + if tests.get('flush'): + self.pruneLog('# === flush ===') + action.flush() + self.assertLogged(*tests['flush'], all=True) + # test stop : + self.pruneLog('# === stop ===') + action.stop() + self.assertLogged(*tests['stop'], all=True) - def getDefaultJailStream(self, jail, act): - act = act.replace('%(__name__)s', jail) - actName, actOpt = extractOptions(act) - stream = [ - ['add', jail, 'polling'], - # ['set', jail, 'addfailregex', 'DUMMY-REGEX '], - ] - action = ActionReader( - actName, jail, actOpt, - share_config=self.__share_cfg, basedir=CONFIG_DIR) - self.assertTrue(action.read()) - action.getOptions({}) - stream.extend(action.convert()) - return stream + def _executeMailCmd(self, realCmd, timeout=60): + # replace pipe to mail with pipe to cat: + realCmd = re.sub(r'\)\s*\|\s*mail\b([^\n]*)', + r') | cat; printf "\\n... | "; echo mail \1', realCmd) + # replace abuse retrieving (possible no-network), just replace first occurrence of 'dig...': + realCmd = re.sub(r'\bADDRESSES=\$\(dig\s[^\n]+', + lambda m: 'ADDRESSES="abuse-1@abuse-test-server, abuse-2@abuse-test-server"', + realCmd, 1) + # execute action: + return _actions.CommandAction.executeCmd(realCmd, timeout=timeout) - def testCheckStockAllActions(self): - unittest.F2B.SkipIfFast() - import glob + def testComplexMailActionMultiLog(self): + unittest.F2B.SkipIfCfgMissing(stock=True) + testJailsActions = ( + # mail-whois-lines -- + ('j-mail-whois-lines', + 'mail-whois-lines[' + 'name=%(__name__)s, grepopts="-m 1", grepmax=2, mailcmd="mail -s", ' + + # 2 logs to test grep from multiple logs: + 'logpath="' + os.path.join(TEST_FILES_DIR, "testcase01.log") + '\n' + + ' ' + os.path.join(TEST_FILES_DIR, "testcase01a.log") + '", ' + '_whois_command="echo \'-- information about --\'"' + ']', + { + 'ip4-ban': ( + 'The IP 87.142.124.10 has just been banned by Fail2Ban after', + '100 attempts against j-mail-whois-lines.', + 'Here is more information about 87.142.124.10 :', + '-- information about 87.142.124.10 --', + 'Lines containing failures of 87.142.124.10 (max 2)', + 'testcase01.log:Dec 31 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 87.142.124.10', + 'testcase01a.log:Dec 31 11:55:01 [sshd] error: PAM: Authentication failure for test from 87.142.124.10', + ), + }), + # complain -- + ('j-complain-abuse', + 'complain[' + 'name=%(__name__)s, grepopts="-m 1", grepmax=2, mailcmd="mail -s \'Hostname: , family: \' - ",' + + # test reverse ip: + 'debug=1,' + + # 2 logs to test grep from multiple logs: + 'logpath="' + os.path.join(TEST_FILES_DIR, "testcase01.log") + '\n' + + ' ' + os.path.join(TEST_FILES_DIR, "testcase01a.log") + '", ' + ']', + { + 'ip4-ban': ( + # test reverse ip: + 'try to resolve 10.124.142.87.abuse-contacts.abusix.org', + 'Lines containing failures of 87.142.124.10 (max 2)', + 'testcase01.log:Dec 31 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 87.142.124.10', + 'testcase01a.log:Dec 31 11:55:01 [sshd] error: PAM: Authentication failure for test from 87.142.124.10', + # both abuse mails should be separated with space: + 'mail -s Hostname: test-host, family: inet4 - Abuse from 87.142.124.10 abuse-1@abuse-test-server abuse-2@abuse-test-server', + ), + 'ip6-ban': ( + # test reverse ip: + 'try to resolve 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.abuse-contacts.abusix.org', + 'Lines containing failures of 2001:db8::1 (max 2)', + # both abuse mails should be separated with space: + 'mail -s Hostname: test-host, family: inet6 - Abuse from 2001:db8::1 abuse-1@abuse-test-server abuse-2@abuse-test-server', + ), + }), + ) + server = TestServer() + transm = server._Server__transm + cmdHandler = transm._Transmitter__commandHandler - server = TestServer() - transm = server._Server__transm + for jail, act, tests in testJailsActions: + stream = self.getDefaultJailStream(jail, act) - for actCfg in glob.glob(os.path.join(CONFIG_DIR, 'action.d', '*.conf')): - act = os.path.basename(actCfg).replace('.conf', '') - # transmit artifical jail with each action to the server: - stream = self.getDefaultJailStream('j-'+act, act) - for cmd in stream: - # command to server: - ret, res = transm.proceed(cmd) - self.assertEqual(ret, 0) - # test executing action commands: - self._testExecActions(server) + # for cmd in stream: + # print(cmd) + # transmit jail to the server: + for cmd in stream: + # command to server: + ret, res = transm.proceed(cmd) + self.assertEqual(ret, 0) - def testCheckStockCommandActions(self): - # test cases to check valid ipv4/ipv6 action definition, tuple with (('jail', 'action[params]', 'tests', ...) - # where tests is a dictionary contains: - # 'ip4' - should not be found (logged) on ban/unban of IPv6 (negative test), - # 'ip6' - should not be found (logged) on ban/unban of IPv4 (negative test), - # 'start', 'stop' - should be found (logged) on action start/stop, - # etc. - testJailsActions = ( - # dummy -- - ('j-dummy', 'dummy[name=%(__name__)s, init="==", target="/tmp/fail2ban.dummy"]', { - 'ip4': ('family: inet4',), 'ip6': ('family: inet6',), - 'start': ( - '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- started"`', - ), - 'flush': ( - '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- clear all"`', - ), - 'stop': ( - '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- stopped"`', - ), - 'ip4-check': (), - 'ip6-check': (), - 'ip4-ban': ( - '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- banned 192.0.2.1 (family: inet4)"`', - ), - 'ip4-unban': ( - '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- unbanned 192.0.2.1 (family: inet4)"`', - ), - 'ip6-ban': ( - '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- banned 2001:db8:: (family: inet6)"`', - ), - 'ip6-unban': ( - '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- unbanned 2001:db8:: (family: inet6)"`', - ), - }), - # iptables-multiport -- - ('j-w-iptables-mp', 'iptables-multiport[name=%(__name__)s, bantime="10m", port="http,https", protocol="tcp", chain=""]', { - 'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'), - 'ip4-start': ( - "`iptables -w -N f2b-j-w-iptables-mp`", - "`iptables -w -A f2b-j-w-iptables-mp -j RETURN`", - "`iptables -w -I INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`", - ), - 'ip6-start': ( - "`ip6tables -w -N f2b-j-w-iptables-mp`", - "`ip6tables -w -A f2b-j-w-iptables-mp -j RETURN`", - "`ip6tables -w -I INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`", - ), - 'flush': ( - "`iptables -w -F f2b-j-w-iptables-mp`", - "`ip6tables -w -F f2b-j-w-iptables-mp`", - ), - 'stop': ( - "`iptables -w -D INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`", - "`iptables -w -F f2b-j-w-iptables-mp`", - "`iptables -w -X f2b-j-w-iptables-mp`", - "`ip6tables -w -D INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`", - "`ip6tables -w -F f2b-j-w-iptables-mp`", - "`ip6tables -w -X f2b-j-w-iptables-mp`", - ), - 'ip4-check': ( - r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-mp[ \t]'`""", - ), - 'ip6-check': ( - r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-mp[ \t]'`""", - ), - 'ip4-ban': ( - r"`iptables -w -I f2b-j-w-iptables-mp 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip4-unban': ( - r"`iptables -w -D f2b-j-w-iptables-mp -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip6-ban': ( - r"`ip6tables -w -I f2b-j-w-iptables-mp 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", - ), - 'ip6-unban': ( - r"`ip6tables -w -D f2b-j-w-iptables-mp -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", - ), - }), - # iptables-allports -- - ('j-w-iptables-ap', 'iptables-allports[name=%(__name__)s, bantime="10m", protocol="tcp", chain=""]', { - 'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'), - 'ip4-start': ( - "`iptables -w -N f2b-j-w-iptables-ap`", - "`iptables -w -A f2b-j-w-iptables-ap -j RETURN`", - "`iptables -w -I INPUT -p tcp -j f2b-j-w-iptables-ap`", - ), - 'ip6-start': ( - "`ip6tables -w -N f2b-j-w-iptables-ap`", - "`ip6tables -w -A f2b-j-w-iptables-ap -j RETURN`", - "`ip6tables -w -I INPUT -p tcp -j f2b-j-w-iptables-ap`", - ), - 'flush': ( - "`iptables -w -F f2b-j-w-iptables-ap`", - "`ip6tables -w -F f2b-j-w-iptables-ap`", - ), - 'stop': ( - "`iptables -w -D INPUT -p tcp -j f2b-j-w-iptables-ap`", - "`iptables -w -F f2b-j-w-iptables-ap`", - "`iptables -w -X f2b-j-w-iptables-ap`", - "`ip6tables -w -D INPUT -p tcp -j f2b-j-w-iptables-ap`", - "`ip6tables -w -F f2b-j-w-iptables-ap`", - "`ip6tables -w -X f2b-j-w-iptables-ap`", - ), - 'ip4-check': ( - r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-ap[ \t]'`""", - ), - 'ip6-check': ( - r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-ap[ \t]'`""", - ), - 'ip4-ban': ( - r"`iptables -w -I f2b-j-w-iptables-ap 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip4-unban': ( - r"`iptables -w -D f2b-j-w-iptables-ap -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip6-ban': ( - r"`ip6tables -w -I f2b-j-w-iptables-ap 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", - ), - 'ip6-unban': ( - r"`ip6tables -w -D f2b-j-w-iptables-ap -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", - ), - }), - # iptables-ipset-proto6 -- - ('j-w-iptables-ipset', 'iptables-ipset-proto6[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain=""]', { - 'ip4': (' f2b-j-w-iptables-ipset ',), 'ip6': (' f2b-j-w-iptables-ipset6 ',), - 'ip4-start': ( - "`ipset create f2b-j-w-iptables-ipset hash:ip timeout 600`", - "`iptables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset src -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip6-start': ( - "`ipset create f2b-j-w-iptables-ipset6 hash:ip timeout 600 family inet6`", - "`ip6tables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`", - ), - 'flush': ( - "`ipset flush f2b-j-w-iptables-ipset`", - "`ipset flush f2b-j-w-iptables-ipset6`", - ), - 'stop': ( - "`iptables -w -D INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset src -j REJECT --reject-with icmp-port-unreachable`", - "`ipset flush f2b-j-w-iptables-ipset`", - "`ipset destroy f2b-j-w-iptables-ipset`", - "`ip6tables -w -D INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`", - "`ipset flush f2b-j-w-iptables-ipset6`", - "`ipset destroy f2b-j-w-iptables-ipset6`", - ), - 'ip4-check': (), - 'ip6-check': (), - 'ip4-ban': ( - r"`ipset add f2b-j-w-iptables-ipset 192.0.2.1 timeout 600 -exist`", - ), - 'ip4-unban': ( - r"`ipset del f2b-j-w-iptables-ipset 192.0.2.1 -exist`", - ), - 'ip6-ban': ( - r"`ipset add f2b-j-w-iptables-ipset6 2001:db8:: timeout 600 -exist`", - ), - 'ip6-unban': ( - r"`ipset del f2b-j-w-iptables-ipset6 2001:db8:: -exist`", - ), - }), - # iptables-ipset-proto6-allports -- - ('j-w-iptables-ipset-ap', 'iptables-ipset-proto6-allports[name=%(__name__)s, bantime="10m", chain=""]', { - 'ip4': (' f2b-j-w-iptables-ipset-ap ',), 'ip6': (' f2b-j-w-iptables-ipset-ap6 ',), - 'ip4-start': ( - "`ipset create f2b-j-w-iptables-ipset-ap hash:ip timeout 600`", - "`iptables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip6-start': ( - "`ipset create f2b-j-w-iptables-ipset-ap6 hash:ip timeout 600 family inet6`", - "`ip6tables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`", - ), - 'flush': ( - "`ipset flush f2b-j-w-iptables-ipset-ap`", - "`ipset flush f2b-j-w-iptables-ipset-ap6`", - ), - 'stop': ( - "`iptables -w -D INPUT -m set --match-set f2b-j-w-iptables-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`", - "`ipset flush f2b-j-w-iptables-ipset-ap`", - "`ipset destroy f2b-j-w-iptables-ipset-ap`", - "`ip6tables -w -D INPUT -m set --match-set f2b-j-w-iptables-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`", - "`ipset flush f2b-j-w-iptables-ipset-ap6`", - "`ipset destroy f2b-j-w-iptables-ipset-ap6`", - ), - 'ip4-check': (), - 'ip6-check': (), - 'ip4-ban': ( - r"`ipset add f2b-j-w-iptables-ipset-ap 192.0.2.1 timeout 600 -exist`", - ), - 'ip4-unban': ( - r"`ipset del f2b-j-w-iptables-ipset-ap 192.0.2.1 -exist`", - ), - 'ip6-ban': ( - r"`ipset add f2b-j-w-iptables-ipset-ap6 2001:db8:: timeout 600 -exist`", - ), - 'ip6-unban': ( - r"`ipset del f2b-j-w-iptables-ipset-ap6 2001:db8:: -exist`", - ), - }), - # iptables -- - ('j-w-iptables', 'iptables[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain=""]', { - 'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'), - 'ip4-start': ( - "`iptables -w -N f2b-j-w-iptables`", - "`iptables -w -A f2b-j-w-iptables -j RETURN`", - "`iptables -w -I INPUT -p tcp --dport http -j f2b-j-w-iptables`", - ), - 'ip6-start': ( - "`ip6tables -w -N f2b-j-w-iptables`", - "`ip6tables -w -A f2b-j-w-iptables -j RETURN`", - "`ip6tables -w -I INPUT -p tcp --dport http -j f2b-j-w-iptables`", - ), - 'flush': ( - "`iptables -w -F f2b-j-w-iptables`", - "`ip6tables -w -F f2b-j-w-iptables`", - ), - 'stop': ( - "`iptables -w -D INPUT -p tcp --dport http -j f2b-j-w-iptables`", - "`iptables -w -F f2b-j-w-iptables`", - "`iptables -w -X f2b-j-w-iptables`", - "`ip6tables -w -D INPUT -p tcp --dport http -j f2b-j-w-iptables`", - "`ip6tables -w -F f2b-j-w-iptables`", - "`ip6tables -w -X f2b-j-w-iptables`", - ), - 'ip4-check': ( - r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables[ \t]'`""", - ), - 'ip6-check': ( - r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables[ \t]'`""", - ), - 'ip4-ban': ( - r"`iptables -w -I f2b-j-w-iptables 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip4-unban': ( - r"`iptables -w -D f2b-j-w-iptables -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip6-ban': ( - r"`ip6tables -w -I f2b-j-w-iptables 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", - ), - 'ip6-unban': ( - r"`ip6tables -w -D f2b-j-w-iptables -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", - ), - }), - # iptables-new -- - ('j-w-iptables-new', 'iptables-new[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain=""]', { - 'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'), - 'ip4-start': ( - "`iptables -w -N f2b-j-w-iptables-new`", - "`iptables -w -A f2b-j-w-iptables-new -j RETURN`", - "`iptables -w -I INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`", - ), - 'ip6-start': ( - "`ip6tables -w -N f2b-j-w-iptables-new`", - "`ip6tables -w -A f2b-j-w-iptables-new -j RETURN`", - "`ip6tables -w -I INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`", - ), - 'flush': ( - "`iptables -w -F f2b-j-w-iptables-new`", - "`ip6tables -w -F f2b-j-w-iptables-new`", - ), - 'stop': ( - "`iptables -w -D INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`", - "`iptables -w -F f2b-j-w-iptables-new`", - "`iptables -w -X f2b-j-w-iptables-new`", - "`ip6tables -w -D INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`", - "`ip6tables -w -F f2b-j-w-iptables-new`", - "`ip6tables -w -X f2b-j-w-iptables-new`", - ), - 'ip4-check': ( - r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-new[ \t]'`""", - ), - 'ip6-check': ( - r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-new[ \t]'`""", - ), - 'ip4-ban': ( - r"`iptables -w -I f2b-j-w-iptables-new 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip4-unban': ( - r"`iptables -w -D f2b-j-w-iptables-new -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip6-ban': ( - r"`ip6tables -w -I f2b-j-w-iptables-new 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", - ), - 'ip6-unban': ( - r"`ip6tables -w -D f2b-j-w-iptables-new -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", - ), - }), - # iptables-xt_recent-echo -- - ('j-w-iptables-xtre', 'iptables-xt_recent-echo[name=%(__name__)s, bantime="10m", chain=""]', { - 'ip4': ('`iptables ', '/f2b-j-w-iptables-xtre`'), 'ip6': ('`ip6tables ', '/f2b-j-w-iptables-xtre6`'), - 'ip4-start': ( - "`if [ `id -u` -eq 0 ];then iptables -w -I INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre -j REJECT --reject-with icmp-port-unreachable;fi`", - ), - 'ip6-start': ( - "`if [ `id -u` -eq 0 ];then ip6tables -w -I INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre6 -j REJECT --reject-with icmp6-port-unreachable;fi`", - ), - 'stop': ( - "`echo / > /proc/net/xt_recent/f2b-j-w-iptables-xtre`", - "`if [ `id -u` -eq 0 ];then iptables -w -D INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre -j REJECT --reject-with icmp-port-unreachable;fi`", - "`echo / > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`", - "`if [ `id -u` -eq 0 ];then ip6tables -w -D INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre6 -j REJECT --reject-with icmp6-port-unreachable;fi`", - ), - 'ip4-check': ( - r"`test -e /proc/net/xt_recent/f2b-j-w-iptables-xtre`", - ), - 'ip6-check': ( - r"`test -e /proc/net/xt_recent/f2b-j-w-iptables-xtre6`", - ), - 'ip4-ban': ( - r"`echo +192.0.2.1 > /proc/net/xt_recent/f2b-j-w-iptables-xtre`", - ), - 'ip4-unban': ( - r"`echo -192.0.2.1 > /proc/net/xt_recent/f2b-j-w-iptables-xtre`", - ), - 'ip6-ban': ( - r"`echo +2001:db8:: > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`", - ), - 'ip6-unban': ( - r"`echo -2001:db8:: > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`", - ), - }), - # pf default -- multiport on default port (tag set in jail.conf, but not in this test case) - ('j-w-pf', 'pf[name=%(__name__)s, actionstart_on_demand=false]', { - 'ip4': (), 'ip6': (), - 'start': ( - '`echo "table persist counters" | pfctl -a f2b/j-w-pf -f-`', - 'port=""', - '`echo "block quick proto tcp from to any port $port" | pfctl -a f2b/j-w-pf -f-`', - ), - 'flush': ( - '`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T flush`', - ), - 'stop': ( - '`pfctl -a f2b/j-w-pf -sr 2>/dev/null | grep -v f2b-j-w-pf | pfctl -a f2b/j-w-pf -f-`', - '`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T flush`', - '`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T kill`', - ), - 'ip4-check': ("`pfctl -a f2b/j-w-pf -sr | grep -q f2b-j-w-pf`",), - 'ip6-check': ("`pfctl -a f2b/j-w-pf -sr | grep -q f2b-j-w-pf`",), - 'ip4-ban': ("`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T add 192.0.2.1`",), - 'ip4-unban': ("`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T delete 192.0.2.1`",), - 'ip6-ban': ("`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T add 2001:db8::`",), - 'ip6-unban': ("`pfctl -a f2b/j-w-pf -t f2b-j-w-pf -T delete 2001:db8::`",), - }), - # pf multiport with custom ports -- - ('j-w-pf-mp', 'pf[actiontype=][name=%(__name__)s, port="http,https"]', { - 'ip4': (), 'ip6': (), - 'start': ( - '`echo "table persist counters" | pfctl -a f2b/j-w-pf-mp -f-`', - 'port="http,https"', - '`echo "block quick proto tcp from to any port $port" | pfctl -a f2b/j-w-pf-mp -f-`', - ), - 'flush': ( - '`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T flush`', - ), - 'stop': ( - '`pfctl -a f2b/j-w-pf-mp -sr 2>/dev/null | grep -v f2b-j-w-pf-mp | pfctl -a f2b/j-w-pf-mp -f-`', - '`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T flush`', - '`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T kill`', - ), - 'ip4-check': ("`pfctl -a f2b/j-w-pf-mp -sr | grep -q f2b-j-w-pf-mp`",), - 'ip6-check': ("`pfctl -a f2b/j-w-pf-mp -sr | grep -q f2b-j-w-pf-mp`",), - 'ip4-ban': ("`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T add 192.0.2.1`",), - 'ip4-unban': ("`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T delete 192.0.2.1`",), - 'ip6-ban': ("`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T add 2001:db8::`",), - 'ip6-unban': ("`pfctl -a f2b/j-w-pf-mp -t f2b-j-w-pf-mp -T delete 2001:db8::`",), - }), - # pf allports -- test additionally "actionstart_on_demand" was set to true - ('j-w-pf-ap', 'pf[actiontype=, actionstart_on_demand=true][name=%(__name__)s]', { - 'ip4': (), 'ip6': (), - 'ip4-start': ( - '`echo "table persist counters" | pfctl -a f2b/j-w-pf-ap -f-`', - '`echo "block quick proto tcp from to any" | pfctl -a f2b/j-w-pf-ap -f-`', - ), - 'ip6-start': (), # the same as ipv4 - 'flush': ( - '`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T flush`', - ), - 'stop': ( - '`pfctl -a f2b/j-w-pf-ap -sr 2>/dev/null | grep -v f2b-j-w-pf-ap | pfctl -a f2b/j-w-pf-ap -f-`', - '`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T flush`', - '`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T kill`', - ), - 'ip4-check': ("`pfctl -a f2b/j-w-pf-ap -sr | grep -q f2b-j-w-pf-ap`",), - 'ip6-check': ("`pfctl -a f2b/j-w-pf-ap -sr | grep -q f2b-j-w-pf-ap`",), - 'ip4-ban': ("`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T add 192.0.2.1`",), - 'ip4-unban': ("`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T delete 192.0.2.1`",), - 'ip6-ban': ("`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T add 2001:db8::`",), - 'ip6-unban': ("`pfctl -a f2b/j-w-pf-ap -t f2b-j-w-pf-ap -T delete 2001:db8::`",), - }), - # firewallcmd-multiport -- - ('j-w-fwcmd-mp', 'firewallcmd-multiport[name=%(__name__)s, bantime="10m", port="http,https", protocol="tcp", chain=""]', { - 'ip4': (' ipv4 ', 'icmp-port-unreachable'), 'ip6': (' ipv6 ', 'icmp6-port-unreachable'), - 'ip4-start': ( - "`firewall-cmd --direct --add-chain ipv4 filter f2b-j-w-fwcmd-mp`", - "`firewall-cmd --direct --add-rule ipv4 filter f2b-j-w-fwcmd-mp 1000 -j RETURN`", - "`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -m conntrack --ctstate NEW -p tcp -m multiport --dports http,https -j f2b-j-w-fwcmd-mp`", - ), - 'ip6-start': ( - "`firewall-cmd --direct --add-chain ipv6 filter f2b-j-w-fwcmd-mp`", - "`firewall-cmd --direct --add-rule ipv6 filter f2b-j-w-fwcmd-mp 1000 -j RETURN`", - "`firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 -m conntrack --ctstate NEW -p tcp -m multiport --dports http,https -j f2b-j-w-fwcmd-mp`", - ), - 'stop': ( - "`firewall-cmd --direct --remove-rule ipv4 filter INPUT_direct 0 -m conntrack --ctstate NEW -p tcp -m multiport --dports http,https -j f2b-j-w-fwcmd-mp`", - "`firewall-cmd --direct --remove-rules ipv4 filter f2b-j-w-fwcmd-mp`", - "`firewall-cmd --direct --remove-chain ipv4 filter f2b-j-w-fwcmd-mp`", - "`firewall-cmd --direct --remove-rule ipv6 filter INPUT_direct 0 -m conntrack --ctstate NEW -p tcp -m multiport --dports http,https -j f2b-j-w-fwcmd-mp`", - "`firewall-cmd --direct --remove-rules ipv6 filter f2b-j-w-fwcmd-mp`", - "`firewall-cmd --direct --remove-chain ipv6 filter f2b-j-w-fwcmd-mp`", - ), - 'ip4-check': ( - r"`firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-j-w-fwcmd-mp$'`", - ), - 'ip6-check': ( - r"`firewall-cmd --direct --get-chains ipv6 filter | sed -e 's, ,\n,g' | grep -q '^f2b-j-w-fwcmd-mp$'`", - ), - 'ip4-ban': ( - r"`firewall-cmd --direct --add-rule ipv4 filter f2b-j-w-fwcmd-mp 0 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip4-unban': ( - r"`firewall-cmd --direct --remove-rule ipv4 filter f2b-j-w-fwcmd-mp 0 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip6-ban': ( - r"`firewall-cmd --direct --add-rule ipv6 filter f2b-j-w-fwcmd-mp 0 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", - ), - 'ip6-unban': ( - r"`firewall-cmd --direct --remove-rule ipv6 filter f2b-j-w-fwcmd-mp 0 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", - ), - }), - # firewallcmd-allports -- - ('j-w-fwcmd-ap', 'firewallcmd-allports[name=%(__name__)s, bantime="10m", protocol="tcp", chain=""]', { - 'ip4': (' ipv4 ', 'icmp-port-unreachable'), 'ip6': (' ipv6 ', 'icmp6-port-unreachable'), - 'ip4-start': ( - "`firewall-cmd --direct --add-chain ipv4 filter f2b-j-w-fwcmd-ap`", - "`firewall-cmd --direct --add-rule ipv4 filter f2b-j-w-fwcmd-ap 1000 -j RETURN`", - "`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -j f2b-j-w-fwcmd-ap`", - ), - 'ip6-start': ( - "`firewall-cmd --direct --add-chain ipv6 filter f2b-j-w-fwcmd-ap`", - "`firewall-cmd --direct --add-rule ipv6 filter f2b-j-w-fwcmd-ap 1000 -j RETURN`", - "`firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 -j f2b-j-w-fwcmd-ap`", - ), - 'stop': ( - "`firewall-cmd --direct --remove-rule ipv4 filter INPUT_direct 0 -j f2b-j-w-fwcmd-ap`", - "`firewall-cmd --direct --remove-rules ipv4 filter f2b-j-w-fwcmd-ap`", - "`firewall-cmd --direct --remove-chain ipv4 filter f2b-j-w-fwcmd-ap`", - "`firewall-cmd --direct --remove-rule ipv6 filter INPUT_direct 0 -j f2b-j-w-fwcmd-ap`", - "`firewall-cmd --direct --remove-rules ipv6 filter f2b-j-w-fwcmd-ap`", - "`firewall-cmd --direct --remove-chain ipv6 filter f2b-j-w-fwcmd-ap`", - ), - 'ip4-check': ( - r"`firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-j-w-fwcmd-ap$'`", - ), - 'ip6-check': ( - r"`firewall-cmd --direct --get-chains ipv6 filter | sed -e 's, ,\n,g' | grep -q '^f2b-j-w-fwcmd-ap$'`", - ), - 'ip4-ban': ( - r"`firewall-cmd --direct --add-rule ipv4 filter f2b-j-w-fwcmd-ap 0 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip4-unban': ( - r"`firewall-cmd --direct --remove-rule ipv4 filter f2b-j-w-fwcmd-ap 0 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip6-ban': ( - r"`firewall-cmd --direct --add-rule ipv6 filter f2b-j-w-fwcmd-ap 0 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", - ), - 'ip6-unban': ( - r"`firewall-cmd --direct --remove-rule ipv6 filter f2b-j-w-fwcmd-ap 0 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`", - ), - }), - # firewallcmd-ipset (multiport) -- - ('j-w-fwcmd-ipset', 'firewallcmd-ipset[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain=""]', { - 'ip4': (' f2b-j-w-fwcmd-ipset ',), 'ip6': (' f2b-j-w-fwcmd-ipset6 ',), - 'ip4-start': ( - "`ipset create f2b-j-w-fwcmd-ipset hash:ip timeout 600`", - "`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset src -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip6-start': ( - "`ipset create f2b-j-w-fwcmd-ipset6 hash:ip timeout 600 family inet6`", - "`firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`", - ), - 'flush': ( - "`ipset flush f2b-j-w-fwcmd-ipset`", - "`ipset flush f2b-j-w-fwcmd-ipset6`", - ), - 'stop': ( - "`firewall-cmd --direct --remove-rule ipv4 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset src -j REJECT --reject-with icmp-port-unreachable`", - "`ipset flush f2b-j-w-fwcmd-ipset`", - "`ipset destroy f2b-j-w-fwcmd-ipset`", - "`firewall-cmd --direct --remove-rule ipv6 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`", - "`ipset flush f2b-j-w-fwcmd-ipset6`", - "`ipset destroy f2b-j-w-fwcmd-ipset6`", - ), - 'ip4-check': (), - 'ip6-check': (), - 'ip4-ban': ( - r"`ipset add f2b-j-w-fwcmd-ipset 192.0.2.1 timeout 600 -exist`", - ), - 'ip4-unban': ( - r"`ipset del f2b-j-w-fwcmd-ipset 192.0.2.1 -exist`", - ), - 'ip6-ban': ( - r"`ipset add f2b-j-w-fwcmd-ipset6 2001:db8:: timeout 600 -exist`", - ), - 'ip6-unban': ( - r"`ipset del f2b-j-w-fwcmd-ipset6 2001:db8:: -exist`", - ), - }), - # firewallcmd-ipset (allports) -- - ('j-w-fwcmd-ipset-ap', 'firewallcmd-ipset[name=%(__name__)s, bantime="10m", actiontype=, protocol="tcp", chain=""]', { - 'ip4': (' f2b-j-w-fwcmd-ipset-ap ',), 'ip6': (' f2b-j-w-fwcmd-ipset-ap6 ',), - 'ip4-start': ( - "`ipset create f2b-j-w-fwcmd-ipset-ap hash:ip timeout 600`", - "`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -p tcp -m set --match-set f2b-j-w-fwcmd-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`", - ), - 'ip6-start': ( - "`ipset create f2b-j-w-fwcmd-ipset-ap6 hash:ip timeout 600 family inet6`", - "`firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 -p tcp -m set --match-set f2b-j-w-fwcmd-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`", - ), - 'flush': ( - "`ipset flush f2b-j-w-fwcmd-ipset-ap`", - "`ipset flush f2b-j-w-fwcmd-ipset-ap6`", - ), - 'stop': ( - "`firewall-cmd --direct --remove-rule ipv4 filter INPUT_direct 0 -p tcp -m set --match-set f2b-j-w-fwcmd-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`", - "`ipset flush f2b-j-w-fwcmd-ipset-ap`", - "`ipset destroy f2b-j-w-fwcmd-ipset-ap`", - "`firewall-cmd --direct --remove-rule ipv6 filter INPUT_direct 0 -p tcp -m set --match-set f2b-j-w-fwcmd-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`", - "`ipset flush f2b-j-w-fwcmd-ipset-ap6`", - "`ipset destroy f2b-j-w-fwcmd-ipset-ap6`", - ), - 'ip4-check': (), - 'ip6-check': (), - 'ip4-ban': ( - r"`ipset add f2b-j-w-fwcmd-ipset-ap 192.0.2.1 timeout 600 -exist`", - ), - 'ip4-unban': ( - r"`ipset del f2b-j-w-fwcmd-ipset-ap 192.0.2.1 -exist`", - ), - 'ip6-ban': ( - r"`ipset add f2b-j-w-fwcmd-ipset-ap6 2001:db8:: timeout 600 -exist`", - ), - 'ip6-unban': ( - r"`ipset del f2b-j-w-fwcmd-ipset-ap6 2001:db8:: -exist`", - ), - }), - ) - server = TestServer() - transm = server._Server__transm - cmdHandler = transm._Transmitter__commandHandler + jails = server._Server__jails - for jail, act, tests in testJailsActions: - stream = self.getDefaultJailStream(jail, act) - - # for cmd in stream: - # print(cmd) - - # transmit jail to the server: - for cmd in stream: - # command to server: - ret, res = transm.proceed(cmd) - self.assertEqual(ret, 0) - - jails = server._Server__jails - - tickets = { - 'ip4': BanTicket('192.0.2.1'), - 'ip6': BanTicket('2001:DB8::'), - } - for jail, act, tests in testJailsActions: - # print(jail, jails[jail]) - for a in jails[jail].actions: - action = jails[jail].actions[a] - logSys.debug('# ' + ('=' * 50)) - logSys.debug('# == %-44s ==', jail + ' - ' + action._name) - logSys.debug('# ' + ('=' * 50)) - self.assertTrue(isinstance(action, _actions.CommandAction)) - # wrap default command processor: - action.executeCmd = self._executeCmd - # test start : - self.pruneLog('# === start ===') - action.start() - if tests.get('start'): - self.assertLogged(*tests['start'], all=True) - else: - self.assertNotLogged(*tests['ip4-start']+tests['ip6-start'], all=True) - ainfo = { - 'ip4': _actions.Actions.ActionInfo(tickets['ip4'], jails[jail]), - 'ip6': _actions.Actions.ActionInfo(tickets['ip6'], jails[jail]), - } - # test ban ip4 : - self.pruneLog('# === ban-ipv4 ===') - action.ban(ainfo['ip4']) - if tests.get('ip4-start'): self.assertLogged(*tests['ip4-start'], all=True) - if tests.get('ip6-start'): self.assertNotLogged(*tests['ip6-start'], all=True) - self.assertLogged(*tests['ip4-check']+tests['ip4-ban'], all=True) - self.assertNotLogged(*tests['ip6'], all=True) - # test unban ip4 : - self.pruneLog('# === unban ipv4 ===') - action.unban(ainfo['ip4']) - self.assertLogged(*tests['ip4-check']+tests['ip4-unban'], all=True) - self.assertNotLogged(*tests['ip6'], all=True) - # test ban ip6 : - self.pruneLog('# === ban ipv6 ===') - action.ban(ainfo['ip6']) - if tests.get('ip6-start'): self.assertLogged(*tests['ip6-start'], all=True) - if tests.get('ip4-start'): self.assertNotLogged(*tests['ip4-start'], all=True) - self.assertLogged(*tests['ip6-check']+tests['ip6-ban'], all=True) - self.assertNotLogged(*tests['ip4'], all=True) - # test unban ip6 : - self.pruneLog('# === unban ipv6 ===') - action.unban(ainfo['ip6']) - self.assertLogged(*tests['ip6-check']+tests['ip6-unban'], all=True) - self.assertNotLogged(*tests['ip4'], all=True) - # test flush for actions should supported this: - if tests.get('flush'): - self.pruneLog('# === flush ===') - action.flush() - self.assertLogged(*tests['flush'], all=True) - # test stop : - self.pruneLog('# === stop ===') - action.stop() - self.assertLogged(*tests['stop'], all=True) - - def _executeMailCmd(self, realCmd, timeout=60): - # replace pipe to mail with pipe to cat: - realCmd = re.sub(r'\)\s*\|\s*mail\b([^\n]*)', - r') | cat; printf "\\n... | "; echo mail \1', realCmd) - # replace abuse retrieving (possible no-network), just replace first occurrence of 'dig...': - realCmd = re.sub(r'\bADDRESSES=\$\(dig\s[^\n]+', - lambda m: 'ADDRESSES="abuse-1@abuse-test-server, abuse-2@abuse-test-server"', - realCmd, 1) - # execute action: - return _actions.CommandAction.executeCmd(realCmd, timeout=timeout) - - def testComplexMailActionMultiLog(self): - testJailsActions = ( - # mail-whois-lines -- - ('j-mail-whois-lines', - 'mail-whois-lines[' - 'name=%(__name__)s, grepopts="-m 1", grepmax=2, mailcmd="mail -s", ' + - # 2 logs to test grep from multiple logs: - 'logpath="' + os.path.join(TEST_FILES_DIR, "testcase01.log") + '\n' + - ' ' + os.path.join(TEST_FILES_DIR, "testcase01a.log") + '", ' - '_whois_command="echo \'-- information about --\'"' - ']', - { - 'ip4-ban': ( - 'The IP 87.142.124.10 has just been banned by Fail2Ban after', - '100 attempts against j-mail-whois-lines.', - 'Here is more information about 87.142.124.10 :', - '-- information about 87.142.124.10 --', - 'Lines containing failures of 87.142.124.10 (max 2)', - 'testcase01.log:Dec 31 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 87.142.124.10', - 'testcase01a.log:Dec 31 11:55:01 [sshd] error: PAM: Authentication failure for test from 87.142.124.10', - ), - }), - # complain -- - ('j-complain-abuse', - 'complain[' - 'name=%(__name__)s, grepopts="-m 1", grepmax=2, mailcmd="mail -s \'Hostname: , family: \' - ",' + - # test reverse ip: - 'debug=1,' + - # 2 logs to test grep from multiple logs: - 'logpath="' + os.path.join(TEST_FILES_DIR, "testcase01.log") + '\n' + - ' ' + os.path.join(TEST_FILES_DIR, "testcase01a.log") + '", ' - ']', - { - 'ip4-ban': ( - # test reverse ip: - 'try to resolve 10.124.142.87.abuse-contacts.abusix.org', - 'Lines containing failures of 87.142.124.10 (max 2)', - 'testcase01.log:Dec 31 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 87.142.124.10', - 'testcase01a.log:Dec 31 11:55:01 [sshd] error: PAM: Authentication failure for test from 87.142.124.10', - # both abuse mails should be separated with space: - 'mail -s Hostname: test-host, family: inet4 - Abuse from 87.142.124.10 abuse-1@abuse-test-server abuse-2@abuse-test-server', - ), - 'ip6-ban': ( - # test reverse ip: - 'try to resolve 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.abuse-contacts.abusix.org', - 'Lines containing failures of 2001:db8::1 (max 2)', - # both abuse mails should be separated with space: - 'mail -s Hostname: test-host, family: inet6 - Abuse from 2001:db8::1 abuse-1@abuse-test-server abuse-2@abuse-test-server', - ), - }), - ) - server = TestServer() - transm = server._Server__transm - cmdHandler = transm._Transmitter__commandHandler - - for jail, act, tests in testJailsActions: - stream = self.getDefaultJailStream(jail, act) - - # for cmd in stream: - # print(cmd) - - # transmit jail to the server: - for cmd in stream: - # command to server: - ret, res = transm.proceed(cmd) - self.assertEqual(ret, 0) - - jails = server._Server__jails - - ipv4 = IPAddr('87.142.124.10') - ipv6 = IPAddr('2001:db8::1'); - dmyjail = DummyJail() - for jail, act, tests in testJailsActions: - # print(jail, jails[jail]) - for a in jails[jail].actions: - action = jails[jail].actions[a] - logSys.debug('# ' + ('=' * 50)) - logSys.debug('# == %-44s ==', jail + ' - ' + action._name) - logSys.debug('# ' + ('=' * 50)) - # wrap default command processor: - action.executeCmd = self._executeMailCmd - # test ban : - for (test, ip) in (('ip4-ban', ipv4), ('ip6-ban', ipv6)): - if not tests.get(test): continue - self.pruneLog('# === %s ===' % test) - ticket = BanTicket(ip) - ticket.setAttempt(100) - ticket = _actions.Actions.ActionInfo(ticket, dmyjail) - action.ban(ticket) - self.assertLogged(*tests[test], all=True) + ipv4 = IPAddr('87.142.124.10') + ipv6 = IPAddr('2001:db8::1'); + dmyjail = DummyJail() + for jail, act, tests in testJailsActions: + # print(jail, jails[jail]) + for a in jails[jail].actions: + action = jails[jail].actions[a] + logSys.debug('# ' + ('=' * 50)) + logSys.debug('# == %-44s ==', jail + ' - ' + action._name) + logSys.debug('# ' + ('=' * 50)) + # wrap default command processor: + action.executeCmd = self._executeMailCmd + # test ban : + for (test, ip) in (('ip4-ban', ipv4), ('ip6-ban', ipv6)): + if not tests.get(test): continue + self.pruneLog('# === %s ===' % test) + ticket = BanTicket(ip) + ticket.setAttempt(100) + ticket = _actions.Actions.ActionInfo(ticket, dmyjail) + action.ban(ticket) + self.assertLogged(*tests[test], all=True) diff --git a/fail2ban/tests/utils.py b/fail2ban/tests/utils.py index 0cf40e86..05d8d666 100644 --- a/fail2ban/tests/utils.py +++ b/fail2ban/tests/utils.py @@ -59,6 +59,9 @@ if not CONFIG_DIR: else: CONFIG_DIR = '/etc/fail2ban' +# Indicates that we've stock config: +STOCK = os.path.exists(os.path.join(CONFIG_DIR, 'fail2ban.conf')) + # During the test cases (or setup) use fail2ban modules from main directory: os.putenv('PYTHONPATH', os.path.dirname(os.path.dirname(os.path.dirname( os.path.abspath(__file__))))) @@ -187,6 +190,31 @@ class F2B(DefaultTestOptions): pass def SkipIfNoNetwork(self): pass + + def SkipIfCfgMissing(self, **kwargs): + """Helper to check action/filter config is available + """ + if not STOCK: # pragma: no cover + if kwargs.get('stock'): + raise unittest.SkipTest('Skip test because of missing stock-config files') + for t in ('action', 'filter'): + v = kwargs.get(t) + if v is None: continue + if os.path.splitext(v)[1] == '': v += '.conf' + if not os.path.exists(os.path.join(CONFIG_DIR, t+'.d', v)): + raise unittest.SkipTest('Skip test because of missing %s-config for %r' % (t, v)) + + def skip_if_cfg_missing(self, **decargs): + """Helper decorator to check action/filter config is available + """ + def _deco_wrapper(f): + @wraps(f) + def wrapper(self, *args, **kwargs): + unittest.F2B.SkipIfCfgMissing(**decargs) + return f(self, *args, **kwargs) + return wrapper + return _deco_wrapper + def maxWaitTime(self,wtime): if self.fast: wtime = float(wtime) / 10