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
from ..version import version, normVersion
from .filterreader import FilterReader
from .jailreader import FilterReader, JailReader, NoJailError
from ..server.filter import Filter, FileContainer, MyTime
from ..server.failregex import Regex, RegexException
@ -312,12 +312,18 @@ class Fail2banRegex(object):
def _dumpRealOptions(self, reader, fltOpt):
realopts = {}
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):
for k in ['logtype', 'datepattern'] + list(fltOpt.keys()):
# combined options win, but they contain only a sub-set in filter expected keys,
# so get the rest from definition section:
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
pass
self.output("Real filter options : %r" % realopts)
@ -330,16 +336,26 @@ class Fail2banRegex(object):
fltName = value
fltFile = None
fltOpt = {}
jail = None
if regextype == 'fail':
if re.search(r'(?ms)^/{0,3}[\w/_\-.]+(?:\[.*\])?$', value):
try:
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:]:
tryNames = (fltName,)
else:
tryNames = (fltName, fltName + '.conf', fltName + '.local')
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':
fltFile = os.path.join(basedir, fltFile)
else:
@ -354,8 +370,25 @@ class Fail2banRegex(object):
output(" while parsing: %s" % (value,))
if self._verbose: raise(e)
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 fltFile is not None:
elif fltFile is not None:
if (basedir == self._opts.config
or os.path.basename(basedir) == 'filter.d'
or ("." not in fltName[~5:] and "/" not in fltName)
@ -364,10 +397,10 @@ class Fail2banRegex(object):
if os.path.basename(basedir) == 'filter.d':
basedir = os.path.dirname(basedir)
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:
## 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
if not os.path.isabs(fltName): # avoid join with "filter.d" inside FilterReader
fltName = os.path.abspath(fltName)
@ -398,6 +431,7 @@ class Fail2banRegex(object):
# to stream:
readercommands = reader.convert()
if readercommands:
regex_values = {}
for opt in readercommands:
if opt[0] == 'multi-set':

View File

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