mirror of https://github.com/fail2ban/fail2ban
allow substitute section-related parameters like `<Definition/option>` in all config-readers as well as during substitute after supply of init arguments;
test cases extended;pull/2034/head
parent
f547a7c7b1
commit
435f359a06
|
@ -33,7 +33,7 @@ if sys.version_info >= (3,2):
|
||||||
|
|
||||||
# SafeConfigParser deprecated from Python 3.2 (renamed to ConfigParser)
|
# SafeConfigParser deprecated from Python 3.2 (renamed to ConfigParser)
|
||||||
from configparser import ConfigParser as SafeConfigParser, BasicInterpolation, \
|
from configparser import ConfigParser as SafeConfigParser, BasicInterpolation, \
|
||||||
InterpolationMissingOptionError, NoSectionError
|
InterpolationMissingOptionError, NoOptionError, NoSectionError
|
||||||
|
|
||||||
# And interpolation of __name__ was simply removed, thus we need to
|
# And interpolation of __name__ was simply removed, thus we need to
|
||||||
# decorate default interpolator to handle it
|
# decorate default interpolator to handle it
|
||||||
|
@ -63,7 +63,7 @@ if sys.version_info >= (3,2):
|
||||||
|
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
from ConfigParser import SafeConfigParser, \
|
from ConfigParser import SafeConfigParser, \
|
||||||
InterpolationMissingOptionError, NoSectionError
|
InterpolationMissingOptionError, NoOptionError, NoSectionError
|
||||||
|
|
||||||
# Interpolate missing known/option as option from default section
|
# Interpolate missing known/option as option from default section
|
||||||
SafeConfigParser._cp_interpolate_some = SafeConfigParser._interpolate_some
|
SafeConfigParser._cp_interpolate_some = SafeConfigParser._interpolate_some
|
||||||
|
@ -112,6 +112,8 @@ after = 1.conf
|
||||||
|
|
||||||
SECTION_NAME = "INCLUDES"
|
SECTION_NAME = "INCLUDES"
|
||||||
|
|
||||||
|
SECTION_OPTNAME_CRE = re.compile(r'^([\w\-]+)/([^\s>]+)$')
|
||||||
|
|
||||||
SECTION_OPTSUBST_CRE = re.compile(r'%\(([\w\-]+/([^\)]+))\)s')
|
SECTION_OPTSUBST_CRE = re.compile(r'%\(([\w\-]+/([^\)]+))\)s')
|
||||||
|
|
||||||
CONDITIONAL_RE = re.compile(r"^(\w+)(\?.+)$")
|
CONDITIONAL_RE = re.compile(r"^(\w+)(\?.+)$")
|
||||||
|
@ -131,7 +133,36 @@ after = 1.conf
|
||||||
SafeConfigParser.__init__(self, *args, **kwargs)
|
SafeConfigParser.__init__(self, *args, **kwargs)
|
||||||
self._cfg_share = share_config
|
self._cfg_share = share_config
|
||||||
|
|
||||||
def _map_section_options(self, section, option, rest, map):
|
def get_ex(self, section, option, raw=False, vars={}):
|
||||||
|
"""Get an option value for a given section.
|
||||||
|
|
||||||
|
In opposite to `get`, it differentiate session-related option name like `sec/opt`.
|
||||||
|
"""
|
||||||
|
sopt = None
|
||||||
|
# if option name contains section:
|
||||||
|
if '/' in option:
|
||||||
|
sopt = SafeConfigParserWithIncludes.SECTION_OPTNAME_CRE.search(option)
|
||||||
|
# try get value from named section/option:
|
||||||
|
if sopt:
|
||||||
|
sec = sopt.group(1)
|
||||||
|
opt = sopt.group(2)
|
||||||
|
seclwr = sec.lower()
|
||||||
|
if seclwr == 'known':
|
||||||
|
# try get value firstly from known options, hereafter from current section:
|
||||||
|
sopt = ('KNOWN/'+section, section)
|
||||||
|
else:
|
||||||
|
sopt = (sec,) if seclwr != 'default' else ("DEFAULT",)
|
||||||
|
for sec in sopt:
|
||||||
|
try:
|
||||||
|
v = self.get(sec, opt, raw=raw)
|
||||||
|
return v
|
||||||
|
except (NoSectionError, NoOptionError) as e:
|
||||||
|
pass
|
||||||
|
# get value of section/option using given section and vars (fallback):
|
||||||
|
v = self.get(section, option, raw=raw, vars=vars)
|
||||||
|
return v
|
||||||
|
|
||||||
|
def _map_section_options(self, section, option, rest, defaults):
|
||||||
"""
|
"""
|
||||||
Interpolates values of the section options (name syntax `%(section/option)s`).
|
Interpolates values of the section options (name syntax `%(section/option)s`).
|
||||||
|
|
||||||
|
@ -139,37 +170,54 @@ after = 1.conf
|
||||||
"""
|
"""
|
||||||
if '/' not in rest or '%(' not in rest: # pragma: no cover
|
if '/' not in rest or '%(' not in rest: # pragma: no cover
|
||||||
return 0
|
return 0
|
||||||
|
rplcmnt = 0
|
||||||
soptrep = SafeConfigParserWithIncludes.SECTION_OPTSUBST_CRE.findall(rest)
|
soptrep = SafeConfigParserWithIncludes.SECTION_OPTSUBST_CRE.findall(rest)
|
||||||
if not soptrep: # pragma: no cover
|
if not soptrep: # pragma: no cover
|
||||||
return 0
|
return 0
|
||||||
for sopt, opt in soptrep:
|
for sopt, opt in soptrep:
|
||||||
if sopt not in map:
|
if sopt not in defaults:
|
||||||
sec = sopt[:~len(opt)]
|
sec = sopt[:~len(opt)]
|
||||||
seclwr = sec.lower()
|
seclwr = sec.lower()
|
||||||
if seclwr != 'default':
|
if seclwr != 'default':
|
||||||
|
usedef = 0
|
||||||
if seclwr == 'known':
|
if seclwr == 'known':
|
||||||
# try get raw value from known options:
|
# try get raw value from known options:
|
||||||
try:
|
try:
|
||||||
v = self._sections['KNOWN/'+section][opt]
|
v = self._sections['KNOWN/'+section][opt]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# fallback to default:
|
# fallback to default:
|
||||||
try:
|
usedef = 1
|
||||||
v = self._defaults[opt]
|
|
||||||
except KeyError: # pragma: no cover
|
|
||||||
continue
|
|
||||||
else:
|
else:
|
||||||
# get raw value of opt in section:
|
# get raw value of opt in section:
|
||||||
v = self.get(sec, opt, raw=True)
|
try:
|
||||||
|
# if section not found - ignore:
|
||||||
|
try:
|
||||||
|
sec = self._sections[sec]
|
||||||
|
except KeyError: # pragma: no cover
|
||||||
|
continue
|
||||||
|
v = sec[opt]
|
||||||
|
except KeyError: # pragma: no cover
|
||||||
|
# fallback to default:
|
||||||
|
usedef = 1
|
||||||
else:
|
else:
|
||||||
|
usedef = 1
|
||||||
|
if usedef:
|
||||||
try:
|
try:
|
||||||
v = self._defaults[opt]
|
v = self._defaults[opt]
|
||||||
except KeyError: # pragma: no cover
|
except KeyError: # pragma: no cover
|
||||||
continue
|
continue
|
||||||
self._defaults[sopt] = v
|
# replacement found:
|
||||||
try: # for some python versions need to duplicate it in map-vars also:
|
rplcmnt = 1
|
||||||
map[sopt] = v
|
try: # set it in map-vars (consider different python versions):
|
||||||
except: pass
|
defaults[sopt] = v
|
||||||
return 1
|
except:
|
||||||
|
# try to set in first default map (corresponding vars):
|
||||||
|
try:
|
||||||
|
defaults._maps[0][sopt] = v
|
||||||
|
except: # pragma: no cover
|
||||||
|
# no way to update vars chain map - overwrite defaults:
|
||||||
|
self._defaults[sopt] = v
|
||||||
|
return rplcmnt
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def share_config(self):
|
def share_config(self):
|
||||||
|
|
|
@ -351,7 +351,7 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||||
return self._defCache[optname]
|
return self._defCache[optname]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
try:
|
try:
|
||||||
v = self.get("Definition", optname, vars=self._pOpts)
|
v = self._cfg.get_ex("Definition", optname, vars=self._pOpts)
|
||||||
except (NoSectionError, NoOptionError, ValueError):
|
except (NoSectionError, NoOptionError, ValueError):
|
||||||
v = None
|
v = None
|
||||||
self._defCache[optname] = v
|
self._defCache[optname] = v
|
||||||
|
|
|
@ -188,7 +188,7 @@ y = %(jail/y)s
|
||||||
self.assertEqual(self.c.get('jail', 'c'), 'def-c,b:"jail-b-test-b-def-b,a:`jail-a-test-a-def-a`"')
|
self.assertEqual(self.c.get('jail', 'c'), 'def-c,b:"jail-b-test-b-def-b,a:`jail-a-test-a-def-a`"')
|
||||||
self.assertEqual(self.c.get('jail', 'd'), 'def-d-b:"def-b,a:`jail-a-test-a-def-a`"')
|
self.assertEqual(self.c.get('jail', 'd'), 'def-d-b:"def-b,a:`jail-a-test-a-def-a`"')
|
||||||
self.assertEqual(self.c.get('test', 'c'), 'def-c,b:"test-b-def-b,a:`test-a-def-a`"')
|
self.assertEqual(self.c.get('test', 'c'), 'def-c,b:"test-b-def-b,a:`test-a-def-a`"')
|
||||||
self.assertEqual(self.c.get('test', 'd'), 'def-d-b:"def-b,a:`test-a-def-a`"')
|
self.assertEqual(self.c.get('test', 'd'), 'def-d-b:"def-b,a:`test-a-def-a`"')
|
||||||
self.assertEqual(self.c.get('DEFAULT', 'c'), 'def-c,b:"def-b,a:`def-a`"')
|
self.assertEqual(self.c.get('DEFAULT', 'c'), 'def-c,b:"def-b,a:`def-a`"')
|
||||||
self.assertEqual(self.c.get('DEFAULT', 'd'), 'def-d-b:"def-b,a:`def-a`"')
|
self.assertEqual(self.c.get('DEFAULT', 'd'), 'def-d-b:"def-b,a:`def-a`"')
|
||||||
self.assertRaises(Exception, self.c.get, 'test', 'x')
|
self.assertRaises(Exception, self.c.get, 'test', 'x')
|
||||||
|
@ -437,9 +437,20 @@ class FilterReaderTest(unittest.TestCase):
|
||||||
self.assertSortedEqual(c, output)
|
self.assertSortedEqual(c, output)
|
||||||
|
|
||||||
def testFilterReaderSubstitionKnown(self):
|
def testFilterReaderSubstitionKnown(self):
|
||||||
output = [['set', 'jailname', 'addfailregex', 'to=test,sweet@example.com,test2,sweet@example.com fromip=<IP>']]
|
output = [['set', 'jailname', 'addfailregex', '^to=test,sweet@example.com,test2,sweet@example.com fromip=<IP>$']]
|
||||||
filterName, filterOpt = extractOptions(
|
filterName, filterOpt = extractOptions(
|
||||||
'substition[honeypot="<sweet>,<known/honeypot>", sweet="test,<known/honeypot>,test2"]')
|
'substition[failregex="^<known/failregex>$", honeypot="<sweet>,<known/honeypot>", sweet="test,<known/honeypot>,test2"]')
|
||||||
|
filterReader = FilterReader('substition', "jailname", filterOpt,
|
||||||
|
share_config=TEST_FILES_DIR_SHARE_CFG, basedir=TEST_FILES_DIR)
|
||||||
|
filterReader.read()
|
||||||
|
filterReader.getOptions(None)
|
||||||
|
c = filterReader.convert()
|
||||||
|
self.assertSortedEqual(c, output)
|
||||||
|
|
||||||
|
def testFilterReaderSubstitionSection(self):
|
||||||
|
output = [['set', 'jailname', 'addfailregex', '^\s*to=fail2ban@localhost fromip=<IP>\s*$']]
|
||||||
|
filterName, filterOpt = extractOptions(
|
||||||
|
'substition[failregex="^\s*<Definition/failregex>\s*$", honeypot="<default/honeypot>"]')
|
||||||
filterReader = FilterReader('substition', "jailname", filterOpt,
|
filterReader = FilterReader('substition', "jailname", filterOpt,
|
||||||
share_config=TEST_FILES_DIR_SHARE_CFG, basedir=TEST_FILES_DIR)
|
share_config=TEST_FILES_DIR_SHARE_CFG, basedir=TEST_FILES_DIR)
|
||||||
filterReader.read()
|
filterReader.read()
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
[DEFAULT]
|
||||||
|
|
||||||
|
honeypot = fail2ban@localhost
|
||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue