diff --git a/fail2ban/client/jailreader.py b/fail2ban/client/jailreader.py index d3fa703f..e64a7a48 100644 --- a/fail2ban/client/jailreader.py +++ b/fail2ban/client/jailreader.py @@ -117,12 +117,13 @@ class JailReader(ConfigReader): "logencoding": ["string", None], "logpath": ["string", None], "skip_if_nologs": ["bool", False], + "systemd_if_nologs": ["bool", True], "action": ["string", ""] } _configOpts.update(FilterReader._configOpts) _ignoreOpts = set( - ['action', 'filter', 'enabled', 'backend', 'skip_if_nologs'] + + ['action', 'filter', 'enabled', 'backend', 'skip_if_nologs', 'systemd_if_nologs'] + list(FilterReader._configOpts.keys()) ) @@ -239,7 +240,7 @@ class JailReader(ConfigReader): return self.__opts return _merge_dicts(self.__opts, self.__filter.getCombined()) - def convert(self, allow_no_files=False): + def convert(self, allow_no_files=False, systemd_if_nologs=True): """Convert read before __opts to the commands stream Parameters @@ -277,14 +278,25 @@ class JailReader(ConfigReader): stream2.append( ["set", self.__name, "addlogpath", p, tail]) if not found_files: - msg = "Have not found any log file for %s jail" % self.__name + msg = "Have not found any log file for '%s' jail." % self.__name skip_if_nologs = self.__opts.get('skip_if_nologs', False) - if not allow_no_files and not skip_if_nologs: + # if auto and we can switch to systemd backend (only possible if jail have journalmatch): + if backend.startswith("auto") and systemd_if_nologs and ( + self.__opts.get('systemd_if_nologs', True) and + self.__opts.get('journalmatch', None) is not None + ): + # switch backend to systemd: + backend = 'systemd' + msg += " Jail will monitor systemd journal." + skip_if_nologs = False + elif not allow_no_files and not skip_if_nologs: raise ValueError(msg) logSys.warning(msg) if skip_if_nologs: - self.__opts['config-error'] = msg - stream = [['config-error', "Jail '%s' skipped, because of missing log files." % (self.__name,)]] + self.__opts['runtime-error'] = msg + msg = "Jail '%s' skipped, because of missing log files." % (self.__name,) + logSys.warning(msg) + stream = [['config-error', msg]] return stream elif opt == "ignoreip": stream.append(["set", self.__name, "addignoreip"] + splitwords(value)) diff --git a/fail2ban/client/jailsreader.py b/fail2ban/client/jailsreader.py index cd3409b4..e83bff4d 100644 --- a/fail2ban/client/jailsreader.py +++ b/fail2ban/client/jailsreader.py @@ -88,7 +88,7 @@ class JailsReader(ConfigReader): parse_status |= 2 return ((ignoreWrong and parse_status & 1) or not (parse_status & 2)) - def convert(self, allow_no_files=False): + def convert(self, allow_no_files=False, systemd_if_nologs=True): """Convert read before __opts and jails to the commands stream Parameters @@ -101,11 +101,14 @@ class JailsReader(ConfigReader): stream = list() # Convert jails for jail in self.__jails: - stream.extend(jail.convert(allow_no_files=allow_no_files)) + stream.extend(jail.convert(allow_no_files, systemd_if_nologs)) # Start jails for jail in self.__jails: - if not jail.options.get('config-error'): + if not jail.options.get('config-error') and not jail.options.get('runtime-error'): stream.append(["start", jail.getName()]) + else: + # just delete rtm-errors (to check next time if cached) + jail.options.pop('runtime-error', None) return stream diff --git a/fail2ban/tests/clientreadertestcase.py b/fail2ban/tests/clientreadertestcase.py index 4fdaeca3..e6a2806c 100644 --- a/fail2ban/tests/clientreadertestcase.py +++ b/fail2ban/tests/clientreadertestcase.py @@ -719,51 +719,67 @@ class JailsReaderTest(LogCaptureTestCase): jails = JailsReader(basedir=IMPERFECT_CONFIG, share_config=IMPERFECT_CONFIG_SHARE_CFG) self.assertTrue(jails.read()) self.assertFalse(jails.getOptions(ignoreWrong=False)) - self.assertRaises(ValueError, jails.convert) - comm_commands = jails.convert(allow_no_files=True) - self.maxDiff = None - self.assertSortedEqual(comm_commands, - [['add', 'emptyaction', 'auto'], - ['add', 'test-known-interp', 'auto'], - ['multi-set', 'test-known-interp', 'addfailregex', [ - 'failure test 1 (filter.d/test.conf) ', - 'failure test 2 (filter.d/test.local) ', - 'failure test 3 (jail.local) ' - ]], - ['start', 'test-known-interp'], - ['add', 'missinglogfiles', 'auto'], - ['set', 'missinglogfiles', 'addfailregex', ''], - ['config-error', "Jail 'missinglogfiles_skip' skipped, because of missing log files."], - ['add', 'brokenaction', 'auto'], - ['set', 'brokenaction', 'addfailregex', ''], - ['set', 'brokenaction', 'addaction', 'brokenaction'], - ['multi-set', 'brokenaction', 'action', 'brokenaction', [ - ['actionban', 'hit with big stick '], - ['actname', 'brokenaction'], - ['name', 'brokenaction'] - ]], - ['add', 'parse_to_end_of_jail.conf', 'auto'], - ['set', 'parse_to_end_of_jail.conf', 'addfailregex', ''], - ['set', 'tz_correct', 'addfailregex', ''], - ['set', 'tz_correct', 'logtimezone', 'UTC+0200'], - ['start', 'emptyaction'], - ['start', 'missinglogfiles'], - ['start', 'brokenaction'], - ['start', 'parse_to_end_of_jail.conf'], - ['add', 'tz_correct', 'auto'], - ['start', 'tz_correct'], - ['config-error', - "Jail 'brokenactiondef' skipped, because of wrong configuration: Invalid action definition 'joho[foo': unexpected option syntax"], - ['config-error', - "Jail 'brokenfilterdef' skipped, because of wrong configuration: Invalid filter definition 'flt[test': unexpected option syntax"], - ['config-error', - "Jail 'missingaction' skipped, because of wrong configuration: Unable to read action 'noactionfileforthisaction'"], - ['config-error', - "Jail 'missingbitsjail' skipped, because of wrong configuration: Unable to read the filter 'catchallthebadies'"], - ]) + self.assertRaises(ValueError, lambda: jails.convert(systemd_if_nologs=False)) self.assertLogged("Errors in jail 'missingbitsjail'.") self.assertNotLogged("Skipping...") + # check with allow no files (just to cover other jail problems), but without switch to systemd: + self.pruneLog('[test-phase] allow no files, no switch to systemd ...') + comm_commands = jails.convert(allow_no_files=True, systemd_if_nologs=False) + self.maxDiff = None + def _checkStream(comm_commands, backend='auto'): + self.assertSortedEqual(comm_commands, + [['add', 'emptyaction', 'auto'], + ['add', 'test-known-interp', 'auto'], + ['multi-set', 'test-known-interp', 'addfailregex', [ + 'failure test 1 (filter.d/test.conf) ', + 'failure test 2 (filter.d/test.local) ', + 'failure test 3 (jail.local) ' + ]], + ['start', 'test-known-interp'], + ['add', 'missinglogfiles', backend], # can switch backend because have journalmatch + ['set', 'missinglogfiles', 'addfailregex', ''], + ['set', 'missinglogfiles', 'addjournalmatch', '_COMM=test'], + ['config-error', "Jail 'missinglogfiles_skip' skipped, because of missing log files."], + ['add', 'brokenaction', 'auto'], + ['set', 'brokenaction', 'addfailregex', ''], + ['set', 'brokenaction', 'addaction', 'brokenaction'], + ['multi-set', 'brokenaction', 'action', 'brokenaction', [ + ['actionban', 'hit with big stick '], + ['actname', 'brokenaction'], + ['name', 'brokenaction'] + ]], + ['add', 'parse_to_end_of_jail.conf', 'auto'], + ['set', 'parse_to_end_of_jail.conf', 'addfailregex', ''], + ['set', 'tz_correct', 'addfailregex', ''], + ['set', 'tz_correct', 'logtimezone', 'UTC+0200'], + ['start', 'emptyaction'], + ['start', 'missinglogfiles'], + ['start', 'brokenaction'], + ['start', 'parse_to_end_of_jail.conf'], + ['add', 'tz_correct', 'auto'], + ['start', 'tz_correct'], + ['config-error', + "Jail 'brokenactiondef' skipped, because of wrong configuration: Invalid action definition 'joho[foo': unexpected option syntax"], + ['config-error', + "Jail 'brokenfilterdef' skipped, because of wrong configuration: Invalid filter definition 'flt[test': unexpected option syntax"], + ['config-error', + "Jail 'missingaction' skipped, because of wrong configuration: Unable to read action 'noactionfileforthisaction'"], + ['config-error', + "Jail 'missingbitsjail' skipped, because of wrong configuration: Unable to read the filter 'catchallthebadies'"], + ]) + _checkStream(comm_commands, backend='auto') + self.assertNotLogged("Have not found any log file for 'missinglogfiles' jail. Jail will monitor systemd journal.") self.assertLogged("No file(s) found for glob /weapons/of/mass/destruction") + self.assertLogged("Jail 'missinglogfiles_skip' skipped, because of missing log files.") + # switch backend auto to systemd if no files found, note that jail "missinglogfiles_skip" will be skipped yet, + # because for this jail configured skip_if_nologs = true, all other jails shall switch to systemd with warning + self.pruneLog('[test-phase] auto -> systemd') + comm_commands = jails.convert(allow_no_files=True) + _checkStream(comm_commands, backend='systemd') + self.assertNotLogged("Errors in jail 'missingbitsjail'.") + self.assertLogged("Have not found any log file for 'missinglogfiles' jail. Jail will monitor systemd journal.") + self.assertLogged("No file(s) found for glob /weapons/of/mass/destruction") + self.assertLogged("Jail 'missinglogfiles_skip' skipped, because of missing log files.") def testReadStockActionConf(self): unittest.F2B.SkipIfCfgMissing(stock=True) diff --git a/fail2ban/tests/config/jail.conf b/fail2ban/tests/config/jail.conf index 2a2a111b..2cf89172 100644 --- a/fail2ban/tests/config/jail.conf +++ b/fail2ban/tests/config/jail.conf @@ -21,6 +21,7 @@ failregex = %(known/failregex)s [missinglogfiles] enabled = true +journalmatch = _COMM=test ;# allow to switch to systemd (by backend = `auto` and no logs found) logpath = /weapons/of/mass/destruction [missinglogfiles_skip]