Merge branch 'systemd-review'

Large set of fixes and enhancements for `systemd` and `auto` backends:
* fixes `systemd` bug with missing journal descriptor after rotation by reopening of journal if it is recognized as not alive (gh-3929)
* improve threaded clean-up of all filters, new thread functions `afterStop` (to force clean-up after stop) and `done`, invoking `afterStop` once
* ensure journal-reader is always closed (additional prevention against leaks and "too many open files"), thereby avoid sporadic segfault in systemd module (see https://github.com/systemd/python-systemd/issues/143)
* fixes `systemd` causing "too many open files" error for a lot of journal files and large amout of systemd jails (see new parameter `rotated` below, gh-3391);
* backend `systemd` extended with new parameter `rotated` (default `false`, as prevention against "too many open files"),
  that allows to monitor only actual journals and ignore now a lot of rotated files by default; so can drastically reduce
  amount of used file descriptors, normally to 1 or 2 descriptors per jail (gh-3391)
* implements automatic switch `backend = auto` to backend `systemd`, when the following is true (RFE gh-3768):
  - no files matching `logpath` found for this jail;
  - no `systemd_if_nologs = false` is specified for the jail (`true` by default);
  - option `journalmatch` is set for the jail or its filter (otherwise it'd be too heavy to allow all auto-jails,
    even if they have never been foreseen for journal monitoring);
  (option `skip_if_nologs` will be ignored if we could switch backend to `systemd`)
pull/3927/merge
sebres 2025-03-31 01:15:40 +02:00
commit a0093b557e
14 changed files with 346 additions and 116 deletions

View File

@ -11,6 +11,12 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition
----------- -----------
### Fixes ### Fixes
* fixes `systemd` bug with missing journal descriptor after rotation by reopening of journal if it is recognized as not alive (gh-3929)
* improve threaded clean-up of all filters, new thread functions `afterStop` (to force clean-up after stop) and `done`, invoking `afterStop` once
* ensure journal-reader is always closed (additional prevention against leaks and "too many open files"), thereby avoid sporadic segfault
in systemd module (see https://github.com/systemd/python-systemd/issues/143)
* fixes `systemd` causing "too many open files" error for a lot of journal files and large amout of systemd jails
(see new parameter `rotated` below, gh-3391);
* `jail.conf`: * `jail.conf`:
- default banactions need to be specified in `paths-*.conf` (maintainer level) now - default banactions need to be specified in `paths-*.conf` (maintainer level) now
- since stock fail2ban includes `paths-debian.conf` by default, banactions are `nftables` - since stock fail2ban includes `paths-debian.conf` by default, banactions are `nftables`
@ -43,8 +49,17 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition
* `filter.d/vsftpd.conf` - fixed regex (if failures generated by systemd-journal, gh-3954) * `filter.d/vsftpd.conf` - fixed regex (if failures generated by systemd-journal, gh-3954)
### New Features and Enhancements ### New Features and Enhancements
* backend `systemd` extended with new parameter `rotated` (default `false`, as prevention against "too many open files"),
that allows to monitor only actual journals and ignore now a lot of rotated files by default; so can drastically reduce
amount of used file descriptors, normally to 1 or 2 descriptors per jail (gh-3391)
* new jail option `skip_if_nologs` to ignore jail if no `logpath` matches found, fail2ban continue to start with warnings/errors, * new jail option `skip_if_nologs` to ignore jail if no `logpath` matches found, fail2ban continue to start with warnings/errors,
thus other jails become running (gh-2756) thus other jails become running (gh-2756)
* implements automatic switch `backend = auto` to backend `systemd`, when the following is true (RFE gh-3768):
- no files matching `logpath` found for this jail;
- no `systemd_if_nologs = false` is specified for the jail (`true` by default);
- option `journalmatch` is set for the jail or its filter (otherwise it'd be too heavy to allow all auto-jails,
even if they have never been foreseen for journal monitoring);
(option `skip_if_nologs` will be ignored if we could switch backend to `systemd`)
* configuration `ignoreip` and fail2ban-client commands `addignoreip`/`delignoreip` extended with `file:...` syntax * configuration `ignoreip` and fail2ban-client commands `addignoreip`/`delignoreip` extended with `file:...` syntax
to ignore IPs from file-ip-set (containing IP, subnet, dns/fqdn or raw strings); the file would be read lazy on demand, to ignore IPs from file-ip-set (containing IP, subnet, dns/fqdn or raw strings); the file would be read lazy on demand,
by first ban (and automatically reloaded by update after small latency to avoid expensive stats check on every compare); by first ban (and automatically reloaded by update after small latency to avoid expensive stats check on every compare);

View File

@ -117,12 +117,13 @@ class JailReader(ConfigReader):
"logencoding": ["string", None], "logencoding": ["string", None],
"logpath": ["string", None], "logpath": ["string", None],
"skip_if_nologs": ["bool", False], "skip_if_nologs": ["bool", False],
"systemd_if_nologs": ["bool", True],
"action": ["string", ""] "action": ["string", ""]
} }
_configOpts.update(FilterReader._configOpts) _configOpts.update(FilterReader._configOpts)
_ignoreOpts = set( _ignoreOpts = set(
['action', 'filter', 'enabled', 'backend', 'skip_if_nologs'] + ['action', 'filter', 'enabled', 'backend', 'skip_if_nologs', 'systemd_if_nologs'] +
list(FilterReader._configOpts.keys()) list(FilterReader._configOpts.keys())
) )
@ -239,7 +240,7 @@ class JailReader(ConfigReader):
return self.__opts return self.__opts
return _merge_dicts(self.__opts, self.__filter.getCombined()) 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 """Convert read before __opts to the commands stream
Parameters Parameters
@ -277,14 +278,25 @@ class JailReader(ConfigReader):
stream2.append( stream2.append(
["set", self.__name, "addlogpath", p, tail]) ["set", self.__name, "addlogpath", p, tail])
if not found_files: 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) 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) raise ValueError(msg)
logSys.warning(msg) logSys.warning(msg)
if skip_if_nologs: if skip_if_nologs:
self.__opts['config-error'] = msg self.__opts['runtime-error'] = msg
stream = [['config-error', "Jail '%s' skipped, because of missing log files." % (self.__name,)]] msg = "Jail '%s' skipped, because of missing log files." % (self.__name,)
logSys.warning(msg)
stream = [['config-error', msg]]
return stream return stream
elif opt == "ignoreip": elif opt == "ignoreip":
stream.append(["set", self.__name, "addignoreip"] + splitwords(value)) stream.append(["set", self.__name, "addignoreip"] + splitwords(value))

View File

@ -88,7 +88,7 @@ class JailsReader(ConfigReader):
parse_status |= 2 parse_status |= 2
return ((ignoreWrong and parse_status & 1) or not (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 """Convert read before __opts and jails to the commands stream
Parameters Parameters
@ -101,11 +101,14 @@ class JailsReader(ConfigReader):
stream = list() stream = list()
# Convert jails # Convert jails
for jail in self.__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 # Start jails
for jail in self.__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()]) stream.append(["start", jail.getName()])
else:
# just delete rtm-errors (to check next time if cached)
jail.options.pop('runtime-error', None)
return stream return stream

View File

@ -1288,24 +1288,15 @@ class FileFilter(Filter):
break break
db.updateLog(self.jail, log) db.updateLog(self.jail, log)
def onStop(self): def afterStop(self):
"""Stop monitoring of log-file(s). Invoked after run method. """Stop monitoring of log-file(s). Invoked after run method.
""" """
# ensure positions of pending logs are up-to-date:
if self._pendDBUpdates and self.jail.database:
self._updateDBPending()
# stop files monitoring: # stop files monitoring:
for path in list(self.__logs.keys()): for path in list(self.__logs.keys()):
self.delLogPath(path) self.delLogPath(path)
# ensure positions of pending logs are up-to-date:
def stop(self): if self._pendDBUpdates and self.jail.database:
"""Stop filter self._updateDBPending()
"""
# normally onStop will be called automatically in thread after its run ends,
# but for backwards compatibilities we'll invoke it in caller of stop method.
self.onStop()
# stop thread:
super(Filter, self).stop()
## ##
# FileContainer class. # FileContainer class.

View File

@ -367,19 +367,18 @@ class FilterPyinotify(FileFilter):
self.commonError("unhandled", e) self.commonError("unhandled", e)
logSys.debug("[%s] filter exited (pyinotifier)", self.jailName) logSys.debug("[%s] filter exited (pyinotifier)", self.jailName)
self.__notifier = None self.done()
return True return True
## ##
# Call super.stop() and then stop the 'Notifier' # Clean-up: then stop the 'Notifier'
def stop(self): def afterStop(self):
# stop filter thread:
super(FilterPyinotify, self).stop()
try: try:
if self.__notifier: # stop the notifier if self.__notifier: # stop the notifier
self.__notifier.stop() self.__notifier.stop()
self.__notifier = None
except AttributeError: # pragma: no cover except AttributeError: # pragma: no cover
if self.__notifier: raise if self.__notifier: raise

View File

@ -25,18 +25,61 @@ __license__ = "GPL"
import os import os
import time import time
from glob import glob
from systemd import journal from systemd import journal
from .failmanager import FailManagerEmpty from .failmanager import FailManagerEmpty
from .filter import JournalFilter, Filter from .filter import JournalFilter, Filter
from .mytime import MyTime from .mytime import MyTime
from .utils import Utils from .utils import Utils
from ..helpers import getLogger, logging, splitwords, uni_decode from ..helpers import getLogger, logging, splitwords, uni_decode, _as_bool
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger(__name__) logSys = getLogger(__name__)
_systemdPathCache = Utils.Cache()
def _getSystemdPath(path):
"""Get systemd path using systemd-path command (cached)"""
p = _systemdPathCache.get(path)
if p: return p
p = Utils.executeCmd('systemd-path %s' % path, timeout=10, shell=True, output=True)
if p and p[0]:
p = str(p[1].decode('utf-8')).split('\n')[0]
_systemdPathCache.set(path, p)
return p
p = '/var/log' if path == 'system-state-logs' else ('/run/log' if path == 'system-runtime-logs' else None)
_systemdPathCache.set(path, p)
return p
def _globJournalFiles(flags=None, path=None):
"""Get journal files without rotated files."""
filesSet = set()
_join = os.path.join
def _addJF(filesSet, p, flags):
"""add journal files to set corresponding path and flags (without rotated *@*.journal)"""
# system journal:
if (flags is None) or (flags & journal.SYSTEM_ONLY):
filesSet |= set(glob(_join(p,'system.journal'))) - set(glob(_join(p,'system*@*.journal')))
# current user-journal:
if (flags is not None) and (flags & journal.CURRENT_USER):
uid = os.getuid()
filesSet |= set(glob(_join(p,('user-%s.journal' % uid)))) - set(glob(_join(p,('user-%s@*.journal' % uid))))
# all local journals:
if (flags is None) or not (flags & (journal.SYSTEM_ONLY|journal.CURRENT_USER)):
filesSet |= set(glob(_join(p,'*.journal'))) - set(glob(_join(p,'*@*.journal')))
if path:
# journals relative given path only:
_addJF(filesSet, path, flags)
else:
# persistent journals corresponding flags:
if (flags is None) or not (flags & journal.RUNTIME_ONLY):
_addJF(filesSet, _join(_getSystemdPath('system-state-logs'), 'journal/*'), flags)
# runtime journals corresponding flags:
_addJF(filesSet, _join(_getSystemdPath('system-runtime-logs'), 'journal/*'), flags)
return filesSet
## ##
# Journal reader class. # Journal reader class.
# #
@ -52,12 +95,13 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
# @param jail the jail object # @param jail the jail object
def __init__(self, jail, **kwargs): def __init__(self, jail, **kwargs):
jrnlargs = FilterSystemd._getJournalArgs(kwargs) self.__jrnlargs = FilterSystemd._getJournalArgs(kwargs)
JournalFilter.__init__(self, jail, **kwargs) JournalFilter.__init__(self, jail, **kwargs)
self.__modified = 0 self.__modified = 0
# Initialise systemd-journal connection # Initialise systemd-journal connection
self.__journal = journal.Reader(**jrnlargs) self.__journal = journal.Reader(**self.__jrnlargs)
self.__matches = [] self.__matches = []
self.__bypassInvalidateMsg = 0
self.setDatePattern(None) self.setDatePattern(None)
logSys.debug("Created FilterSystemd") logSys.debug("Created FilterSystemd")
@ -74,31 +118,86 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
except KeyError: except KeyError:
pass pass
else: else:
import glob
p = args['files'] p = args['files']
if not isinstance(p, (list, set, tuple)): if not isinstance(p, (list, set, tuple)):
p = splitwords(p) p = splitwords(p)
files = [] files = []
for p in p: for p in p:
files.extend(glob.glob(p)) files.extend(glob(p))
args['files'] = list(set(files)) args['files'] = list(set(files))
# Default flags is SYSTEM_ONLY(4). This would lead to ignore user session files, rotated = _as_bool(kwargs.pop('rotated', 0))
# so can prevent "Too many open files" errors on a lot of user sessions (see gh-2392): # Default flags is SYSTEM_ONLY(4) or LOCAL_ONLY(1), depending on rotated parameter.
# This could lead to ignore user session files, so together with ignoring rotated
# files would prevent "Too many open files" errors on a lot of user sessions (see gh-2392):
try: try:
args['flags'] = int(kwargs.pop('journalflags')) args['flags'] = int(kwargs.pop('journalflags'))
except KeyError: except KeyError:
# be sure all journal types will be opened if files/path specified (don't set flags): # be sure all journal types will be opened if files/path specified (don't set flags):
if ('files' not in args or not len(args['files'])) and ('path' not in args or not args['path']): if (not args.get('files') and not args.get('path')):
args['flags'] = int(os.getenv("F2B_SYSTEMD_DEFAULT_FLAGS", 4)) args['flags'] = os.getenv("F2B_SYSTEMD_DEFAULT_FLAGS", None)
if args['flags'] is not None:
args['flags'] = int(args['flags'])
elif rotated:
args['flags'] = journal.SYSTEM_ONLY
try: try:
args['namespace'] = kwargs.pop('namespace') args['namespace'] = kwargs.pop('namespace')
except KeyError: except KeyError:
pass pass
# To avoid monitoring rotated logs, as prevention against "Too many open files",
# set the files to system.journal and user-*.journal (without rotated *@*.journal):
if not rotated and not args.get('files') and not args.get('namespace'):
args['files'] = _globJournalFiles(
args.get('flags', journal.LOCAL_ONLY), args.get('path'))
if args['files']:
args['files'] = list(args['files'])
args['path'] = None; # cannot be cannot be specified simultaneously with files
else:
args['files'] = None
return args return args
@property
def _journalAlive(self):
"""Checks journal is online.
"""
try:
# open?
if self.__journal.closed: # pragma: no cover
return False
# has cursor? if it is broken (e. g. no descriptor) - it'd raise this:
# OSError: [Errno 99] Cannot assign requested address
if self.__journal._get_cursor():
return True
except OSError: # pragma: no cover
pass
return False
def _reopenJournal(self): # pragma: no cover
"""Reopen journal (if it becomes offline after rotation)
"""
if self.__journal.closed:
# recreate reader:
self.__journal = journal.Reader(**self.__jrnlargs)
else:
try:
# workaround for gh-3929 (no journal descriptor after rotation),
# to reopen journal we'd simply invoke inherited init again:
self.__journal.close()
ja = self.__jrnlargs
super(journal.Reader, self.__journal).__init__(
ja.get('flags', 0), ja.get('path'), ja.get('files'), ja.get('namespace'))
except:
# cannot reopen in that way, so simply recreate reader:
self.closeJournal()
self.__journal = journal.Reader(**self.__jrnlargs)
# restore journalmatch specified for the jail:
self.resetJournalMatches()
# just to avoid "Invalidate signaled" happening again after reopen:
self.__bypassInvalidateMsg = MyTime.time() + 1
## ##
# Add a journal match filters from list structure # Add a journal match filters from list structure
# #
@ -257,6 +356,8 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
def inOperationMode(self): def inOperationMode(self):
self.inOperation = True self.inOperation = True
logSys.info("[%s] Jail is in operation now (process new journal entries)", self.jailName) logSys.info("[%s] Jail is in operation now (process new journal entries)", self.jailName)
# just to avoid "Invalidate signaled" happening often at start:
self.__bypassInvalidateMsg = MyTime.time() + 1
## ##
# Main loop. # Main loop.
@ -314,6 +415,14 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
while self.active: while self.active:
# wait for records (or for timeout in sleeptime seconds): # wait for records (or for timeout in sleeptime seconds):
try: try:
if self.idle:
# because journal.wait will returns immediately if we have records in journal,
# just wait a little bit here for not idle, to prevent hi-load:
if not Utils.wait_for(lambda: not self.active or not self.idle,
self.sleeptime * 10, self.sleeptime
):
self.ticks += 1
continue
## wait for entries using journal.wait: ## wait for entries using journal.wait:
if wcode == journal.NOP and self.inOperation: if wcode == journal.NOP and self.inOperation:
## todo: find better method as wait_for to break (e.g. notify) journal.wait(self.sleeptime), ## todo: find better method as wait_for to break (e.g. notify) journal.wait(self.sleeptime),
@ -328,8 +437,10 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
## if invalidate (due to rotation, vacuuming or journal files added/removed etc): ## if invalidate (due to rotation, vacuuming or journal files added/removed etc):
if self.active and wcode == journal.INVALIDATE: if self.active and wcode == journal.INVALIDATE:
if self.ticks: if self.ticks:
logSys.log(logging.DEBUG, "[%s] Invalidate signaled, take a little break (rotation ends)", self.jailName) if not self.__bypassInvalidateMsg or MyTime.time() > self.__bypassInvalidateMsg:
logSys.log(logging.MSG, "[%s] Invalidate signaled, take a little break (rotation ends)", self.jailName)
time.sleep(self.sleeptime * 0.25) time.sleep(self.sleeptime * 0.25)
self.__bypassInvalidateMsg = 0
Utils.wait_for(lambda: not self.active or \ Utils.wait_for(lambda: not self.active or \
self.__journal.wait(Utils.DEFAULT_SLEEP_INTERVAL) != journal.INVALIDATE, self.__journal.wait(Utils.DEFAULT_SLEEP_INTERVAL) != journal.INVALIDATE,
self.sleeptime * 3, 0.00001) self.sleeptime * 3, 0.00001)
@ -340,14 +451,11 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
if self.__journal.get_previous(): self.__journal.get_next() if self.__journal.get_previous(): self.__journal.get_next()
except OSError: except OSError:
pass pass
if self.idle: # if it is not alive - reopen:
# because journal.wait will returns immediately if we have records in journal, if not self._journalAlive:
# just wait a little bit here for not idle, to prevent hi-load: logSys.log(logging.MSG, "[%s] Journal reader seems to be offline, reopen journal", self.jailName)
if not Utils.wait_for(lambda: not self.active or not self.idle, self._reopenJournal()
self.sleeptime * 10, self.sleeptime wcode = journal.NOP
):
self.ticks += 1
continue
self.__modified = 0 self.__modified = 0
while self.active: while self.active:
logentry = None logentry = None
@ -408,8 +516,8 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
logSys.debug("[%s] filter terminated", self.jailName) logSys.debug("[%s] filter terminated", self.jailName)
# close journal: # call afterStop once (close journal, etc):
self.closeJournal() self.done()
logSys.debug("[%s] filter exited (systemd)", self.jailName) logSys.debug("[%s] filter exited (systemd)", self.jailName)
return True return True
@ -443,12 +551,10 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
break break
db.updateJournal(self.jail, log, *args) db.updateJournal(self.jail, log, *args)
def onStop(self): def afterStop(self):
"""Stop monitoring of journal. Invoked after run method. """Cleanup"""
"""
# close journal: # close journal:
self.closeJournal() self.closeJournal()
# ensure positions of pending logs are up-to-date: # ensure positions of pending logs are up-to-date:
if self._pendDBUpdates and self.jail.database: if self._pendDBUpdates and self.jail.database:
self._updateDBPending() self._updateDBPending()

View File

@ -335,7 +335,9 @@ class Jail(object):
try: try:
## signal to stop filter / actions: ## signal to stop filter / actions:
if stop: if stop:
obj.stop() if obj.isAlive():
obj.stop()
obj.done(); # and clean-up everything
## wait for end of threads: ## wait for end of threads:
if join: if join:
obj.join() obj.join()

View File

@ -103,7 +103,21 @@ class JailThread(Thread):
def stop(self): def stop(self):
"""Sets `active` property to False, to flag run method to return. """Sets `active` property to False, to flag run method to return.
""" """
self.active = False if self.active: self.active = False
# normally onStop will be called automatically in thread after its run ends,
# but for backwards compatibilities we'll invoke it in caller of stop method.
self.onStop()
self.onStop = lambda:()
self.done()
def done(self):
self.done = lambda:()
# if still runniung - wait a bit before initiate clean-up:
if self.is_alive():
Utils.wait_for(lambda: not self.is_alive(), 5)
# now clean-up everything:
self.afterStop()
@abstractmethod @abstractmethod
def run(self): # pragma: no cover - absract def run(self): # pragma: no cover - absract
@ -111,11 +125,15 @@ class JailThread(Thread):
""" """
pass pass
def afterStop(self):
"""Cleanup resources."""
pass
def join(self): def join(self):
""" Safer join, that could be called also for not started (or ended) threads (used for cleanup). """ Safer join, that could be called also for not started (or ended) threads (used for cleanup).
""" """
## if cleanup needed - create derivative and call it before join... ## if cleanup needed - create derivative and call it before join...
self.done()
## if was really started - should call join: ## if was really started - should call join:
if self.active is not None: if self.active is not None:
super(JailThread, self).join() super(JailThread, self).join()

View File

@ -719,51 +719,67 @@ class JailsReaderTest(LogCaptureTestCase):
jails = JailsReader(basedir=IMPERFECT_CONFIG, share_config=IMPERFECT_CONFIG_SHARE_CFG) jails = JailsReader(basedir=IMPERFECT_CONFIG, share_config=IMPERFECT_CONFIG_SHARE_CFG)
self.assertTrue(jails.read()) self.assertTrue(jails.read())
self.assertFalse(jails.getOptions(ignoreWrong=False)) self.assertFalse(jails.getOptions(ignoreWrong=False))
self.assertRaises(ValueError, jails.convert) self.assertRaises(ValueError, lambda: jails.convert(systemd_if_nologs=False))
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) <HOST>',
'failure test 2 (filter.d/test.local) <HOST>',
'failure test 3 (jail.local) <HOST>'
]],
['start', 'test-known-interp'],
['add', 'missinglogfiles', 'auto'],
['set', 'missinglogfiles', 'addfailregex', '<IP>'],
['config-error', "Jail 'missinglogfiles_skip' skipped, because of missing log files."],
['add', 'brokenaction', 'auto'],
['set', 'brokenaction', 'addfailregex', '<IP>'],
['set', 'brokenaction', 'addaction', 'brokenaction'],
['multi-set', 'brokenaction', 'action', 'brokenaction', [
['actionban', 'hit with big stick <ip>'],
['actname', 'brokenaction'],
['name', 'brokenaction']
]],
['add', 'parse_to_end_of_jail.conf', 'auto'],
['set', 'parse_to_end_of_jail.conf', 'addfailregex', '<IP>'],
['set', 'tz_correct', 'addfailregex', '<IP>'],
['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.assertLogged("Errors in jail 'missingbitsjail'.") self.assertLogged("Errors in jail 'missingbitsjail'.")
self.assertNotLogged("Skipping...") 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) <HOST>',
'failure test 2 (filter.d/test.local) <HOST>',
'failure test 3 (jail.local) <HOST>'
]],
['start', 'test-known-interp'],
['add', 'missinglogfiles', backend], # can switch backend because have journalmatch
['set', 'missinglogfiles', 'addfailregex', '<IP>'],
['set', 'missinglogfiles', 'addjournalmatch', '_COMM=test'],
['config-error', "Jail 'missinglogfiles_skip' skipped, because of missing log files."],
['add', 'brokenaction', 'auto'],
['set', 'brokenaction', 'addfailregex', '<IP>'],
['set', 'brokenaction', 'addaction', 'brokenaction'],
['multi-set', 'brokenaction', 'action', 'brokenaction', [
['actionban', 'hit with big stick <ip>'],
['actname', 'brokenaction'],
['name', 'brokenaction']
]],
['add', 'parse_to_end_of_jail.conf', 'auto'],
['set', 'parse_to_end_of_jail.conf', 'addfailregex', '<IP>'],
['set', 'tz_correct', 'addfailregex', '<IP>'],
['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("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): def testReadStockActionConf(self):
unittest.F2B.SkipIfCfgMissing(stock=True) unittest.F2B.SkipIfCfgMissing(stock=True)

View File

@ -21,6 +21,7 @@ failregex = %(known/failregex)s
[missinglogfiles] [missinglogfiles]
enabled = true enabled = true
journalmatch = _COMM=test ;# allow to switch to systemd (by backend = `auto` and no logs found)
logpath = /weapons/of/mass/destruction logpath = /weapons/of/mass/destruction
[missinglogfiles_skip] [missinglogfiles_skip]

View File

@ -32,7 +32,7 @@ import tempfile
import uuid import uuid
try: try:
from systemd import journal from ..server.filtersystemd import journal, _globJournalFiles
except ImportError: except ImportError:
journal = None journal = None
@ -1477,7 +1477,7 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
self.filter.addFailRegex(r"(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>") self.filter.addFailRegex(r"(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>")
def tearDown(self): def tearDown(self):
if self.filter and self.filter.active: if self.filter and (self.filter.active or self.filter.active is None):
self.filter.stop() self.filter.stop()
self.filter.join() # wait for the thread to terminate self.filter.join() # wait for the thread to terminate
super(MonitorJournalFailures, self).tearDown() super(MonitorJournalFailures, self).tearDown()
@ -1510,6 +1510,34 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
return MonitorJournalFailures._runtimeJournal return MonitorJournalFailures._runtimeJournal
raise unittest.SkipTest('systemd journal seems to be not available (e. g. no rights to read)') raise unittest.SkipTest('systemd journal seems to be not available (e. g. no rights to read)')
def testGlobJournal_System(self):
if not journal: # pragma: no cover
raise unittest.SkipTest("systemd python interface not available")
jrnlfile = self._getRuntimeJournal()
jrnlpath = os.path.dirname(jrnlfile)
self.assertIn(jrnlfile, _globJournalFiles(journal.SYSTEM_ONLY))
self.assertIn(jrnlfile, _globJournalFiles(journal.SYSTEM_ONLY, jrnlpath))
self.assertIn(jrnlfile, _globJournalFiles(journal.LOCAL_ONLY))
self.assertIn(jrnlfile, _globJournalFiles(journal.LOCAL_ONLY, jrnlpath))
@with_tmpdir
def testGlobJournal(self, tmp):
if not journal: # pragma: no cover
raise unittest.SkipTest("systemd python interface not available")
# no files yet in temp-path:
self.assertFalse(_globJournalFiles(None, tmp))
# test against temp-path, shall ignore all rotated files:
tsysjrnl = os.path.join(tmp, 'system.journal')
tusrjrnl = os.path.join(tmp, 'user-%s.journal' % os.getuid())
def touch(fn): os.close(os.open(fn, os.O_CREAT|os.O_APPEND))
touch(tsysjrnl);
touch(tusrjrnl);
touch(os.path.join(tmp, 'system@test-rotated.journal'));
touch(os.path.join(tmp, 'user-%s@test-rotated.journal' % os.getuid()));
self.assertSortedEqual(_globJournalFiles(None, tmp), {tsysjrnl, tusrjrnl})
self.assertSortedEqual(_globJournalFiles(journal.SYSTEM_ONLY, tmp), {tsysjrnl})
self.assertSortedEqual(_globJournalFiles(journal.CURRENT_USER, tmp), {tusrjrnl})
def testJournalFilesArg(self): def testJournalFilesArg(self):
# retrieve current system journal path # retrieve current system journal path
jrnlfile = self._getRuntimeJournal() jrnlfile = self._getRuntimeJournal()
@ -1533,9 +1561,16 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
self.assertTrue(self.isEmpty(1)) self.assertTrue(self.isEmpty(1))
self.assertEqual(len(self.jail), 0) self.assertEqual(len(self.jail), 0)
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan) self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
def testJournalPath_RotatedArg(self):
# retrieve current system journal path
jrnlpath = self._getRuntimeJournal()
jrnlpath = os.path.dirname(jrnlpath)
self._initFilter(journalpath=jrnlpath, rotated=1)
def testJournalFlagsArg(self): def testJournalFlagsArg(self):
self._initFilter(journalflags=0) # e. g. 2 - journal.RUNTIME_ONLY self._initFilter(journalflags=0)
def testJournalFlags_RotatedArg(self):
self._initFilter(journalflags=0, rotated=1)
def assert_correct_ban(self, test_ip, test_attempts): def assert_correct_ban(self, test_ip, test_attempts):
self.assertTrue(self.waitFailTotal(test_attempts, 10)) # give Filter a chance to react self.assertTrue(self.waitFailTotal(test_attempts, 10)) # give Filter a chance to react

View File

@ -350,7 +350,7 @@ class TestsUtilsTest(LogCaptureTestCase):
unittest.F2B.fast = True unittest.F2B.fast = True
try: try:
self.assertEqual(unittest.F2B.maxWaitTime(lambda: 50)(), 50) self.assertEqual(unittest.F2B.maxWaitTime(lambda: 50)(), 50)
self.assertEqual(unittest.F2B.maxWaitTime(25), 2.5) self.assertEqual(unittest.F2B.maxWaitTime(25), 5)
self.assertEqual(unittest.F2B.maxWaitTime(25.), 25.0) self.assertEqual(unittest.F2B.maxWaitTime(25.), 25.0)
finally: finally:
unittest.F2B.fast = orgfast unittest.F2B.fast = orgfast

View File

@ -219,7 +219,7 @@ class F2B(DefaultTestOptions):
# short only integer interval (avoid by conditional wait with callable, and dual # short only integer interval (avoid by conditional wait with callable, and dual
# wrapping in some routines, if it will be called twice): # wrapping in some routines, if it will be called twice):
if self.fast and isinstance(wtime, int): if self.fast and isinstance(wtime, int):
wtime = float(wtime) / 10 wtime = float(wtime) / (10 if wtime < 10 else 5)
return wtime return wtime

View File

@ -182,6 +182,8 @@ This specifies the stack size (in KiB) to be used for subsequently created threa
.SH "JAIL CONFIGURATION FILE(S) (\fIjail.conf\fB)" .SH "JAIL CONFIGURATION FILE(S) (\fIjail.conf\fB)"
The following options are applicable to any jail. They appear in a section specifying the jail name or in the \fI[DEFAULT]\fR section which defines default values to be used if not specified in the individual section. The following options are applicable to any jail. They appear in a section specifying the jail name or in the \fI[DEFAULT]\fR section which defines default values to be used if not specified in the individual section.
.sp
It is also possible to specify or to overwrite any option of filter file directly in jail (see section FILTER FILES).
.TP .TP
.B filter .B filter
name of the filter -- filename of the filter in /etc/fail2ban/filter.d/ without the .conf/.local extension. name of the filter -- filename of the filter in /etc/fail2ban/filter.d/ without the .conf/.local extension.
@ -198,7 +200,10 @@ Optional space separated option 'tail' can be added to the end of the path to ca
Ensure syslog or the program that generates the log file isn't configured to compress repeated log messages to "\fI*last message repeated 5 time*s\fR" otherwise it will fail to detect. This is called \fIRepeatedMsgReduction\fR in rsyslog and should be \fIOff\fR. Ensure syslog or the program that generates the log file isn't configured to compress repeated log messages to "\fI*last message repeated 5 time*s\fR" otherwise it will fail to detect. This is called \fIRepeatedMsgReduction\fR in rsyslog and should be \fIOff\fR.
.TP .TP
.B skip_if_nologs .B skip_if_nologs
if no logpath matches found, skip the jail by start of fail2ban if \fIskip_if_nologs\fR set to true, otherwise (default: false) start of fail2ban will fail with an error "Have not found any log file". if no logpath matches found, skip the jail by start of fail2ban if \fIskip_if_nologs\fR set to true, otherwise (default: false) start of fail2ban will fail with an error "Have not found any log file", unless the backend is \fIauto\fR and the jail is able to swith backend to \fIsystemd\fR (see \fIauto\fR in section \fBBackends\fR below).
.TP
.B systemd_if_nologs
if no logpath matches found, switch backend \fIauto\fR to \fIsystemd\fR (see \fBBackends\fR section), unless disabled with \fBsystemd_if_nologs = false\fR (default \fBtrue\fR).
.TP .TP
.B logencoding .B logencoding
encoding of log files used for decoding. Default value of "auto" uses current system locale. encoding of log files used for decoding. Default value of "auto" uses current system locale.
@ -280,7 +285,7 @@ number of failures that have to occur in the last \fBfindtime\fR seconds to ban
.B backend .B backend
backend to be used to detect changes in the logpath. backend to be used to detect changes in the logpath.
.br .br
It defaults to "auto" which will try "pyinotify", "systemd" before "polling". Any of these can be specified. "pyinotify" is only valid on Linux systems with the "pyinotify" Python libraries. It defaults to "auto" which will try "pyinotify" before "polling" and may switch to "systemd" if no files matching \fBlogpath\fR will be found (see section \fBBackends\fR below). Any of these can be specified. "pyinotify" is only valid on Linux systems with the "pyinotify" Python libraries.
.TP .TP
.B usedns .B usedns
use DNS to resolve HOST names that appear in the logs. By default it is "warn" which will resolve hostnames to IPs however it will also log a warning. If you are using DNS here you could be blocking the wrong IPs due to the asymmetric nature of reverse DNS (that the application used to write the domain name to log) compared to forward DNS that fail2ban uses to resolve this back to an IP (but not necessarily the same one). Ideally you should configure your applications to log a real IP. This can be set to "yes" to prevent warnings in the log or "no" to disable DNS resolution altogether (thus ignoring entries where hostname, not an IP is logged).. use DNS to resolve HOST names that appear in the logs. By default it is "warn" which will resolve hostnames to IPs however it will also log a warning. If you are using DNS here you could be blocking the wrong IPs due to the asymmetric nature of reverse DNS (that the application used to write the domain name to log) compared to forward DNS that fail2ban uses to resolve this back to an IP (but not necessarily the same one). Ideally you should configure your applications to log a real IP. This can be set to "yes" to prevent warnings in the log or "no" to disable DNS resolution altogether (thus ignoring entries where hostname, not an IP is logged)..
@ -300,21 +305,46 @@ max number of matched log-lines the jail would hold in memory per ticket. By def
.SS Backends .SS Backends
Available options are listed below. Available options are listed below.
.TP .TP
.B auto
automatically selects best suitable \fBbackend\fR, starting with file-based like \fIpyinotify\fR or \fIpolling\fR to monitor the \fBlogpath\fR matching files, but can also automatically switch to backend \fIsystemd\fR, when the following is true:
.RS
.IP \(bu 4n
no files matching \fBlogpath\fR found for this jail;
.IP \(bu 4n
no \fBsystemd_if_nologs = false\fR is specified for the jail;
.IP \(bu 4n
option \fBjournalmatch\fR is set for the jail or its filter (otherwise it'd be too heavy to allow all auto-jails, even if they have never been foreseen for journal monitoring);
.TP
.br
Option \fBskip_if_nologs\fR will be ignored if we could switch \fBbackend\fR to \fIsystemd\fR.
.RE
.TP
.B pyinotify .B pyinotify
requires pyinotify (a file alteration monitor) to be installed. If pyinotify is not installed, Fail2ban will use auto. requires pyinotify (a file alteration monitor) to be installed. The backend would receive modification events from a built-in Linux kernel \fIinotify\fR feature used to watch for changes on tracking files and directories, and therefore is better suitable for monitoring of logfiles than \fIpolling\fR.
.TP .TP
.B polling .B polling
uses a polling algorithm which does not require external libraries. uses a polling algorithm which does not require additional libraries.
.TP .TP
.B systemd .B systemd
uses systemd python library to access the systemd journal. Specifying \fBlogpath\fR is not valid for this backend and instead utilises \fBjournalmatch\fR from the jails associated filter config. Multiple systemd-specific flags can be passed to the backend, including \fBjournalpath\fR and \fBjournalfiles\fR, to explicitly set the path to a directory or set of files. \fBjournalflags\fR, which by default is 4 and excludes user session files, can be set to include them with \fBjournalflags=1\fR, see the python-systemd documentation for other settings and further details. Examples: uses systemd python library to access the systemd journal. Specifying \fBlogpath\fR is not valid for this backend and instead utilises \fBjournalmatch\fR from the jails associated filter config. Multiple systemd-specific flags can be passed to the backend, including \fBjournalpath\fR and \fBjournalfiles\fR, to explicitly set the path to a directory or set of files, \fBjournalflags\fR, which by default is 1 (LOCAL_ONLY) and opens journal on local machine only, can be set to 4 (SYSTEM_ONLY) with \fBjournalflags=4\fR to exclude user session files, or \fBnamespace\fR.
.br
Note that \fBjournalflags\fR, \fBjournalpath\fR, \fBjournalfiles\fR and \fBnamespace\fR are exclusive. See the python-systemd documentation for other settings and further details.
.sp 1
Examples:
.PP .PP
.RS .RS
.nf .nf
backend = systemd[journalpath=/run/log/journal/machine-1] backend = systemd[journalpath=/run/log/journal/machine-1]
backend = systemd[journalfiles="/path/to/system.journal, /path/to/user.journal"] backend = systemd[journalfiles="/path/to/system.journal, /path/to/user.journal"]
backend = systemd[journalflags=1] backend = systemd[journalflags=4, rotated=on]
.fi .fi
.sp 1
To avoid "too many open files" situation (descriptors exhaustion), fail2ban will ignore rotated journal files by default and has own specific parameter \fBrotated\fR (default \fBfalse\fR), so it'd automatically retrieve non-rotated set of \fBjournalfiles\fR corresponding \fBjournalflags\fR (and \fBjournalpath\fR if set).
Thus \fBsystemd\fR backend works by default similar to file-based backends and can find only actual (not rotated) messages and could seek (findtime etc) maximally to the time point of last rotation only.
.br
The same is valid for \fBfail2ban-regex systemd-journal ...\fR, so it will ignore messages from rotated journal files by default. To search across whole journal one shall use \fBfail2ban-regex systemd-journal[rotated=on] ...\fR.
.RE
.SS Actions .SS Actions
Each jail can be configured with only a single filter, but may have multiple actions. By default, the name of a action is the action filename, and in the case of Python actions, the ".py" file extension is stripped. Where multiple of the same action are to be used, the \fBactname\fR option can be assigned to the action to avoid duplication e.g.: Each jail can be configured with only a single filter, but may have multiple actions. By default, the name of a action is the action filename, and in the case of Python actions, the ".py" file extension is stripped. Where multiple of the same action are to be used, the \fBactname\fR option can be assigned to the action to avoid duplication e.g.:
@ -493,11 +523,11 @@ is the regex (\fBreg\fRular \fBex\fRpression) that will match failed attempts. T
\fI<F-ID>...</F-ID>\fR - free regex capturing group targeting identifier used for ban (instead of IP address or hostname). \fI<F-ID>...</F-ID>\fR - free regex capturing group targeting identifier used for ban (instead of IP address or hostname).
.IP .IP
\fI<F-*>...</F-*>\fR - free regex capturing named group stored in ticket, which can be used in action. \fI<F-*>...</F-*>\fR - free regex capturing named group stored in ticket, which can be used in action.
.nf .br
For example \fI<F-USER>[^@]+</F-USER>\fR matches and stores a user name, that can be used in action with interpolation tag \fI<F-USER>\fR. For example \fI<F-USER>[^@]+</F-USER>\fR matches and stores a user name, that can be used in action with interpolation tag \fI<F-USER>\fR.
.IP .IP
\fI<F-ALT_*n>...</F-ALT_*n>\fR - free regex capturing alternative named group stored in ticket. \fI<F-ALT_*n>...</F-ALT_*n>\fR - free regex capturing alternative named group stored in ticket.
.nf .br
For example first found matched value defined in regex as \fI<F-ALT_USER>\fR, \fI<F-ALT_USER1>\fR or \fI<F-ALT_USER2>\fR would be stored as <F-USER> (if direct match is not found or empty). For example first found matched value defined in regex as \fI<F-ALT_USER>\fR, \fI<F-ALT_USER1>\fR or \fI<F-ALT_USER2>\fR would be stored as <F-USER> (if direct match is not found or empty).
.PP .PP
Every of abovementioned tags can be specified in \fBprefregex\fR and in \fBfailregex\fR, thereby if specified in both, the value matched in \fBfailregex\fR overwrites a value matched in \fBprefregex\fR. Every of abovementioned tags can be specified in \fBprefregex\fR and in \fBfailregex\fR, thereby if specified in both, the value matched in \fBfailregex\fR overwrites a value matched in \fBprefregex\fR.
@ -518,10 +548,10 @@ This is an obsolete handling and if the lines contain some common identifier, be
is the regex to identify log entries that should be ignored by Fail2Ban, even if they match failregex. is the regex to identify log entries that should be ignored by Fail2Ban, even if they match failregex.
.TP .TP
\fBmaxlines\fR .B maxlines
specifies the maximum number of lines to buffer to match multi-line regexs. For some log formats this will not required to be changed. Other logs may require to increase this value if a particular log file is frequently written to. specifies the maximum number of lines to buffer to match multi-line regexs. For some log formats this will not required to be changed. Other logs may require to increase this value if a particular log file is frequently written to.
.TP .TP
\fBdatepattern\fR .B datepattern
specifies a custom date pattern/regex as an alternative to the default date detectors e.g. %%Y-%%m-%%d %%H:%%M(?::%%S)?. specifies a custom date pattern/regex as an alternative to the default date detectors e.g. %%Y-%%m-%%d %%H:%%M(?::%%S)?.
For a list of valid format directives, see Python library documentation for strptime behaviour. For a list of valid format directives, see Python library documentation for strptime behaviour.
.br .br
@ -544,8 +574,10 @@ There are several prefixes and words with special meaning that could be specifie
\fI{NONE}\fR - value would allow one to find failures totally without date-time in log message. Filter will use now as a timestamp (or last known timestamp from previous line with timestamp). \fI{NONE}\fR - value would allow one to find failures totally without date-time in log message. Filter will use now as a timestamp (or last known timestamp from previous line with timestamp).
.RE .RE
.TP .TP
\fBjournalmatch\fR .B journalmatch
specifies the systemd journal match used to filter the journal entries. See \fBjournalctl(1)\fR and \fBsystemd.journal-fields(7)\fR for matches syntax and more details on special journal fields. This option is only valid for the \fIsystemd\fR backend. specifies the systemd journal match used to filter the journal entries. See \fBjournalctl(1)\fR and \fBsystemd.journal-fields(7)\fR for matches syntax and more details on special journal fields. This option is only applied by the \fIsystemd\fR and \fIauto\fR backends and it is mandatory for automatical switch to \fIsystemd\fR by \fIauto\fR backend.
.RE
.PP .PP
Similar to actions, filters may have an [Init] section also (optional since v.0.10). All parameters of both sections [Definition] and [Init] can be overridden (redefined or extended) in \fIjail.conf\fR or \fIjail.local\fR (or in related \fIfilter.d/filter-name.local\fR). Similar to actions, filters may have an [Init] section also (optional since v.0.10). All parameters of both sections [Definition] and [Init] can be overridden (redefined or extended) in \fIjail.conf\fR or \fIjail.local\fR (or in related \fIfilter.d/filter-name.local\fR).
Every option supplied in the jail to the filter overwrites the value specified in [Init] section, which in turn would overwrite the value in [Definition] section. Every option supplied in the jail to the filter overwrites the value specified in [Init] section, which in turn would overwrite the value in [Definition] section.