fail2ban-regex: loading parsing settings from jail now (by simple name it'd prefer jail to the filter now), fallback:

- fail2ban-regex ... sshd
  + fail2ban-regex ... filter.d/sshd
closes gh-2655
pull/3641/head
sebres 2023-12-31 16:34:22 +01:00
parent 7de1057f94
commit 781321d609
2 changed files with 63 additions and 14 deletions

View File

@ -51,7 +51,7 @@ except ImportError:
FilterSystemd = None FilterSystemd = None
from ..version import version, normVersion from ..version import version, normVersion
from .filterreader import FilterReader from .jailreader import FilterReader, JailReader, NoJailError
from ..server.filter import Filter, FileContainer, MyTime from ..server.filter import Filter, FileContainer, MyTime
from ..server.failregex import Regex, RegexException from ..server.failregex import Regex, RegexException
@ -312,12 +312,18 @@ class Fail2banRegex(object):
def _dumpRealOptions(self, reader, fltOpt): def _dumpRealOptions(self, reader, fltOpt):
realopts = {} realopts = {}
combopts = reader.getCombined() combopts = reader.getCombined()
if isinstance(reader, FilterReader):
_get_opt = lambda k: reader.get('Definition', k)
elif reader.filter: # JailReader for jail with filter:
_get_opt = lambda k: reader.filter.get('Definition', k)
else: # JailReader for jail without filter:
_get_opt = lambda k: None
# output all options that are specified in filter-argument as well as some special (mostly interested): # output all options that are specified in filter-argument as well as some special (mostly interested):
for k in ['logtype', 'datepattern'] + list(fltOpt.keys()): for k in ['logtype', 'datepattern'] + list(fltOpt.keys()):
# combined options win, but they contain only a sub-set in filter expected keys, # combined options win, but they contain only a sub-set in filter expected keys,
# so get the rest from definition section: # so get the rest from definition section:
try: try:
realopts[k] = combopts[k] if k in combopts else reader.get('Definition', k) realopts[k] = combopts[k] if k in combopts else _get_opt(k)
except NoOptionError: # pragma: no cover except NoOptionError: # pragma: no cover
pass pass
self.output("Real filter options : %r" % realopts) self.output("Real filter options : %r" % realopts)
@ -330,16 +336,26 @@ class Fail2banRegex(object):
fltName = value fltName = value
fltFile = None fltFile = None
fltOpt = {} fltOpt = {}
jail = None
if regextype == 'fail': if regextype == 'fail':
if re.search(r'(?ms)^/{0,3}[\w/_\-.]+(?:\[.*\])?$', value): if re.search(r'(?ms)^/{0,3}[\w/_\-.]+(?:\[.*\])?$', value):
try: try:
fltName, fltOpt = extractOptions(value) fltName, fltOpt = extractOptions(value)
if re.search(r'(?ms)^[\w/_\-]+$', fltName): # name of jail?
try:
jail = JailReader(fltName, force_enable=True,
share_config=self.share_config)
jail.read()
except NoJailError:
jail = None
if "." in fltName[~5:]: if "." in fltName[~5:]:
tryNames = (fltName,) tryNames = (fltName,)
else: else:
tryNames = (fltName, fltName + '.conf', fltName + '.local') tryNames = (fltName, fltName + '.conf', fltName + '.local')
for fltFile in tryNames: for fltFile in tryNames:
if not "/" in fltFile: if os.path.dirname(fltFile) == 'filter.d':
fltFile = os.path.join(basedir, fltFile)
elif not "/" in fltFile:
if os.path.basename(basedir) == 'filter.d': if os.path.basename(basedir) == 'filter.d':
fltFile = os.path.join(basedir, fltFile) fltFile = os.path.join(basedir, fltFile)
else: else:
@ -354,8 +370,25 @@ class Fail2banRegex(object):
output(" while parsing: %s" % (value,)) output(" while parsing: %s" % (value,))
if self._verbose: raise(e) if self._verbose: raise(e)
return False return False
readercommands = None
# if it is jail:
if jail:
self.output( "Use %11s jail : %s" % ('', fltName) )
if fltOpt:
self.output( "Use jail/flt options : %r" % fltOpt )
if not fltOpt: fltOpt = {}
fltOpt['backend'] = self._backend
ret = jail.getOptions(addOpts=fltOpt)
if not ret:
output('ERROR: Failed to get jail for %r' % (value,))
return False
# show real options if expected:
if self._verbose > 1 or logSys.getEffectiveLevel()<=logging.DEBUG:
self._dumpRealOptions(jail, fltOpt)
readercommands = jail.convert(allow_no_files=True)
# if it is filter file: # if it is filter file:
if fltFile is not None: elif fltFile is not None:
if (basedir == self._opts.config if (basedir == self._opts.config
or os.path.basename(basedir) == 'filter.d' or os.path.basename(basedir) == 'filter.d'
or ("." not in fltName[~5:] and "/" not in fltName) or ("." not in fltName[~5:] and "/" not in fltName)
@ -364,10 +397,10 @@ class Fail2banRegex(object):
if os.path.basename(basedir) == 'filter.d': if os.path.basename(basedir) == 'filter.d':
basedir = os.path.dirname(basedir) basedir = os.path.dirname(basedir)
fltName = os.path.splitext(os.path.basename(fltName))[0] fltName = os.path.splitext(os.path.basename(fltName))[0]
self.output( "Use %11s filter file : %s, basedir: %s" % (regex, fltName, basedir) ) self.output( "Use %11s file : %s, basedir: %s" % ('filter', fltName, basedir) )
else: else:
## foreign file - readexplicit this file and includes if possible: ## foreign file - readexplicit this file and includes if possible:
self.output( "Use %11s file : %s" % (regex, fltName) ) self.output( "Use %11s file : %s" % ('filter', fltName) )
basedir = None basedir = None
if not os.path.isabs(fltName): # avoid join with "filter.d" inside FilterReader if not os.path.isabs(fltName): # avoid join with "filter.d" inside FilterReader
fltName = os.path.abspath(fltName) fltName = os.path.abspath(fltName)
@ -398,6 +431,7 @@ class Fail2banRegex(object):
# to stream: # to stream:
readercommands = reader.convert() readercommands = reader.convert()
if readercommands:
regex_values = {} regex_values = {}
for opt in readercommands: for opt in readercommands:
if opt[0] == 'multi-set': if opt[0] == 'multi-set':

View File

@ -29,16 +29,19 @@ import json
import os.path import os.path
import re import re
from .configreader import ConfigReaderUnshared, ConfigReader from .configreader import ConfigReaderUnshared, ConfigReader, NoSectionError
from .filterreader import FilterReader from .filterreader import FilterReader
from .actionreader import ActionReader from .actionreader import ActionReader
from ..version import version from ..version import version
from ..helpers import getLogger, extractOptions, splitWithOptions, splitwords from ..helpers import _merge_dicts, getLogger, extractOptions, splitWithOptions, splitwords
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger(__name__) logSys = getLogger(__name__)
class NoJailError(ValueError):
pass
class JailReader(ConfigReader): class JailReader(ConfigReader):
def __init__(self, name, force_enable=False, **kwargs): def __init__(self, name, force_enable=False, **kwargs):
@ -64,7 +67,7 @@ class JailReader(ConfigReader):
# Before returning -- verify that requested section # Before returning -- verify that requested section
# exists at all # exists at all
if not (self.__name in self.sections()): if not (self.__name in self.sections()):
raise ValueError("Jail %r was not found among available" raise NoJailError("Jail %r was not found among available"
% self.__name) % self.__name)
return out return out
@ -117,9 +120,9 @@ class JailReader(ConfigReader):
} }
_configOpts.update(FilterReader._configOpts) _configOpts.update(FilterReader._configOpts)
_ignoreOpts = set(['action', 'filter', 'enabled'] + list(FilterReader._configOpts.keys())) _ignoreOpts = set(['action', 'filter', 'enabled', 'backend'] + list(FilterReader._configOpts.keys()))
def getOptions(self): def getOptions(self, addOpts=None):
basedir = self.getBaseDir() basedir = self.getBaseDir()
@ -136,6 +139,8 @@ class JailReader(ConfigReader):
shouldExist=True) shouldExist=True)
if not self.__opts: # pragma: no cover if not self.__opts: # pragma: no cover
raise JailDefError("Init jail options failed") raise JailDefError("Init jail options failed")
if addOpts:
self.__opts = _merge_dicts(self.__opts, addOpts)
if not self.isEnabled(): if not self.isEnabled():
return True return True
@ -147,6 +152,8 @@ class JailReader(ConfigReader):
filterName, filterOpt = extractOptions(flt) filterName, filterOpt = extractOptions(flt)
except ValueError as e: except ValueError as e:
raise JailDefError("Invalid filter definition %r: %s" % (flt, e)) raise JailDefError("Invalid filter definition %r: %s" % (flt, e))
if addOpts:
filterOpt = _merge_dicts(filterOpt, addOpts)
self.__filter = FilterReader( self.__filter = FilterReader(
filterName, self.__name, filterOpt, filterName, self.__name, filterOpt,
share_config=self.share_config, basedir=basedir) share_config=self.share_config, basedir=basedir)
@ -219,6 +226,15 @@ class JailReader(ConfigReader):
return False return False
return True return True
@property
def filter(self):
return self.__filter
def getCombined(self):
if not self.__filter:
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):
"""Convert read before __opts to the commands stream """Convert read before __opts to the commands stream
@ -240,9 +256,10 @@ class JailReader(ConfigReader):
stream.extend(self.__filter.convert()) stream.extend(self.__filter.convert())
# and using options from jail: # and using options from jail:
FilterReader._fillStream(stream, self.__opts, self.__name) FilterReader._fillStream(stream, self.__opts, self.__name)
backend = self.__opts.get('backend', 'auto')
for opt, value in self.__opts.items(): for opt, value in self.__opts.items():
if opt == "logpath": if opt == "logpath":
if self.__opts.get('backend', '').startswith("systemd"): continue if backend.startswith("systemd"): continue
found_files = 0 found_files = 0
for path in value.split("\n"): for path in value.split("\n"):
path = path.rsplit(" ", 1) path = path.rsplit(" ", 1)
@ -260,8 +277,6 @@ class JailReader(ConfigReader):
if not allow_no_files: if not allow_no_files:
raise ValueError(msg) raise ValueError(msg)
logSys.warning(msg) logSys.warning(msg)
elif opt == "backend":
backend = value
elif opt == "ignoreip": elif opt == "ignoreip":
stream.append(["set", self.__name, "addignoreip"] + splitwords(value)) stream.append(["set", self.__name, "addignoreip"] + splitwords(value))
elif opt not in JailReader._ignoreOpts: elif opt not in JailReader._ignoreOpts: