mirror of https://github.com/fail2ban/fail2ban
Merge branch '0.11'
commit
8bc7623388
|
@ -28,6 +28,8 @@ ver. 1.0.1-dev-1 (20??/??/??) - development nightly edition
|
||||||
* ensure we've unique action name per jail (also if parameter `actname` is not set but name deviates from standard name, gh-2686)
|
* ensure we've unique action name per jail (also if parameter `actname` is not set but name deviates from standard name, gh-2686)
|
||||||
* don't use `%(banaction)s` interpolation because it can be complex value (containing `[...]` and/or quotes),
|
* don't use `%(banaction)s` interpolation because it can be complex value (containing `[...]` and/or quotes),
|
||||||
so would bother the action interpolation
|
so would bother the action interpolation
|
||||||
|
* fixed type conversion in config readers (take place after all interpolations get ready), that allows to
|
||||||
|
specify typed parameters variable (as substitutions) as well as to supply it in other sections or as init parameters.
|
||||||
* `action.d/*-ipset*.conf`: several ipset actions fixed (no timeout per default anymore), so no discrepancy
|
* `action.d/*-ipset*.conf`: several ipset actions fixed (no timeout per default anymore), so no discrepancy
|
||||||
between ipset and fail2ban (removal from ipset will be managed by fail2ban only, gh-2703)
|
between ipset and fail2ban (removal from ipset will be managed by fail2ban only, gh-2703)
|
||||||
* `action.d/cloudflare.conf`: fixed `actionunban` (considering new-line chars and optionally real json-parsing
|
* `action.d/cloudflare.conf`: fixed `actionunban` (considering new-line chars and optionally real json-parsing
|
||||||
|
@ -45,6 +47,7 @@ ver. 1.0.1-dev-1 (20??/??/??) - development nightly edition
|
||||||
|
|
||||||
### New Features and Enhancements
|
### New Features and Enhancements
|
||||||
* new filter and jail for GitLab recognizing failed application logins (gh-2689)
|
* new filter and jail for GitLab recognizing failed application logins (gh-2689)
|
||||||
|
* `filter.d/guacamole.conf` extended with `logging` parameter to follow webapp-logging if it's configured (gh-2631)
|
||||||
* introduced new prefix `{UNB}` for `datepattern` to disable word boundaries in regex;
|
* introduced new prefix `{UNB}` for `datepattern` to disable word boundaries in regex;
|
||||||
* datetemplate: improved anchor detection for capturing groups `(^...)`;
|
* datetemplate: improved anchor detection for capturing groups `(^...)`;
|
||||||
* datepattern: improved handling with wrong recognized timestamps (timezones, no datepattern, etc)
|
* datepattern: improved handling with wrong recognized timestamps (timezones, no datepattern, etc)
|
||||||
|
|
|
@ -5,21 +5,47 @@
|
||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
|
|
||||||
# Option: failregex
|
logging = catalina
|
||||||
# Notes.: regex to match the password failures messages in the logfile.
|
failregex = <L_<logging>/failregex>
|
||||||
# Values: TEXT
|
maxlines = <L_<logging>/maxlines>
|
||||||
#
|
datepattern = <L_<logging>/datepattern>
|
||||||
|
|
||||||
|
[L_catalina]
|
||||||
|
|
||||||
failregex = ^.*\nWARNING: Authentication attempt from <HOST> for user "[^"]*" failed\.$
|
failregex = ^.*\nWARNING: Authentication attempt from <HOST> for user "[^"]*" failed\.$
|
||||||
|
|
||||||
# Option: ignoreregex
|
|
||||||
# Notes.: regex to ignore. If this regex matches, the line is ignored.
|
|
||||||
# Values: TEXT
|
|
||||||
#
|
|
||||||
ignoreregex =
|
|
||||||
|
|
||||||
# "maxlines" is number of log lines to buffer for multi-line regex searches
|
|
||||||
maxlines = 2
|
maxlines = 2
|
||||||
|
|
||||||
datepattern = ^%%b %%d, %%ExY %%I:%%M:%%S %%p
|
datepattern = ^%%b %%d, %%ExY %%I:%%M:%%S %%p
|
||||||
^WARNING:()**
|
^WARNING:()**
|
||||||
{^LN-BEG}
|
{^LN-BEG}
|
||||||
|
|
||||||
|
[L_webapp]
|
||||||
|
|
||||||
|
failregex = ^ \[\S+\] WARN \S+ - Authentication attempt from <HOST> for user "<F-USER>[^"]+</F-USER>" failed.
|
||||||
|
|
||||||
|
maxlines = 1
|
||||||
|
|
||||||
|
datepattern = ^%%H:%%M:%%S.%%f
|
||||||
|
|
||||||
|
# DEV Notes:
|
||||||
|
#
|
||||||
|
# failregex is based on the default pattern given in Guacamole documentation :
|
||||||
|
# https://guacamole.apache.org/doc/gug/configuring-guacamole.html#webapp-logging
|
||||||
|
#
|
||||||
|
# The following logback.xml Guacamole configuration file can then be used accordingly :
|
||||||
|
# <configuration>
|
||||||
|
# <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
# <file>/var/log/guacamole.log</file>
|
||||||
|
# <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
# <fileNamePattern>/var/log/guacamole.%d.log.gz</fileNamePattern>
|
||||||
|
# <maxHistory>32</maxHistory>
|
||||||
|
# </rollingPolicy>
|
||||||
|
# <encoder>
|
||||||
|
# <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
# </encoder>
|
||||||
|
# </appender>
|
||||||
|
# <root level="info">
|
||||||
|
# <appender-ref ref="FILE" />
|
||||||
|
# </root>
|
||||||
|
# </configuration>
|
||||||
|
|
|
@ -478,6 +478,7 @@ backend = %(syslog_backend)s
|
||||||
|
|
||||||
port = http,https
|
port = http,https
|
||||||
logpath = /var/log/tomcat*/catalina.out
|
logpath = /var/log/tomcat*/catalina.out
|
||||||
|
#logpath = /var/log/guacamole.log
|
||||||
|
|
||||||
[monit]
|
[monit]
|
||||||
#Ban clients brute-forcing the monit gui login
|
#Ban clients brute-forcing the monit gui login
|
||||||
|
|
|
@ -38,18 +38,18 @@ class ActionReader(DefinitionInitConfigReader):
|
||||||
|
|
||||||
_configOpts = {
|
_configOpts = {
|
||||||
"actionstart": ["string", None],
|
"actionstart": ["string", None],
|
||||||
"actionstart_on_demand": ["string", None],
|
"actionstart_on_demand": ["bool", None],
|
||||||
"actionstop": ["string", None],
|
"actionstop": ["string", None],
|
||||||
"actionflush": ["string", None],
|
"actionflush": ["string", None],
|
||||||
"actionreload": ["string", None],
|
"actionreload": ["string", None],
|
||||||
"actioncheck": ["string", None],
|
"actioncheck": ["string", None],
|
||||||
"actionrepair": ["string", None],
|
"actionrepair": ["string", None],
|
||||||
"actionrepair_on_unban": ["string", None],
|
"actionrepair_on_unban": ["bool", None],
|
||||||
"actionban": ["string", None],
|
"actionban": ["string", None],
|
||||||
"actionprolong": ["string", None],
|
"actionprolong": ["string", None],
|
||||||
"actionreban": ["string", None],
|
"actionreban": ["string", None],
|
||||||
"actionunban": ["string", None],
|
"actionunban": ["string", None],
|
||||||
"norestored": ["string", None],
|
"norestored": ["bool", None],
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, file_, jailName, initOpts, **kwargs):
|
def __init__(self, file_, jailName, initOpts, **kwargs):
|
||||||
|
@ -84,11 +84,6 @@ class ActionReader(DefinitionInitConfigReader):
|
||||||
def convert(self):
|
def convert(self):
|
||||||
opts = self.getCombined(
|
opts = self.getCombined(
|
||||||
ignore=CommandAction._escapedTags | set(('timeout', 'bantime')))
|
ignore=CommandAction._escapedTags | set(('timeout', 'bantime')))
|
||||||
# type-convert only after combined (otherwise boolean converting prevents substitution):
|
|
||||||
for o in ('norestored', 'actionstart_on_demand', 'actionrepair_on_unban'):
|
|
||||||
if opts.get(o):
|
|
||||||
opts[o] = self._convert_to_boolean(opts[o])
|
|
||||||
|
|
||||||
# stream-convert:
|
# stream-convert:
|
||||||
head = ["set", self._jailName]
|
head = ["set", self._jailName]
|
||||||
stream = list()
|
stream = list()
|
||||||
|
|
|
@ -29,7 +29,7 @@ import re
|
||||||
import sys
|
import sys
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger
|
||||||
|
|
||||||
if sys.version_info >= (3,2):
|
if sys.version_info >= (3,): # pragma: 2.x no cover
|
||||||
|
|
||||||
# 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, \
|
||||||
|
@ -61,7 +61,7 @@ if sys.version_info >= (3,2):
|
||||||
return super(BasicInterpolationWithName, self)._interpolate_some(
|
return super(BasicInterpolationWithName, self)._interpolate_some(
|
||||||
parser, option, accum, rest, section, map, *args, **kwargs)
|
parser, option, accum, rest, section, map, *args, **kwargs)
|
||||||
|
|
||||||
else: # pragma: no cover
|
else: # pragma: 3.x no cover
|
||||||
from ConfigParser import SafeConfigParser, \
|
from ConfigParser import SafeConfigParser, \
|
||||||
InterpolationMissingOptionError, NoOptionError, NoSectionError
|
InterpolationMissingOptionError, NoOptionError, NoSectionError
|
||||||
|
|
||||||
|
@ -372,7 +372,8 @@ after = 1.conf
|
||||||
s2 = alls.get(n)
|
s2 = alls.get(n)
|
||||||
if isinstance(s2, dict):
|
if isinstance(s2, dict):
|
||||||
# save previous known values, for possible using in local interpolations later:
|
# save previous known values, for possible using in local interpolations later:
|
||||||
self.merge_section('KNOWN/'+n, s2, '')
|
self.merge_section('KNOWN/'+n,
|
||||||
|
dict(filter(lambda i: i[0] in s, s2.iteritems())), '')
|
||||||
# merge section
|
# merge section
|
||||||
s2.update(s)
|
s2.update(s)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -34,6 +34,30 @@ from ..helpers import getLogger, _as_bool, _merge_dicts, substituteRecursiveTags
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = getLogger(__name__)
|
logSys = getLogger(__name__)
|
||||||
|
|
||||||
|
CONVERTER = {
|
||||||
|
"bool": _as_bool,
|
||||||
|
"int": int,
|
||||||
|
}
|
||||||
|
def _OptionsTemplateGen(options):
|
||||||
|
"""Iterator over the options template with default options.
|
||||||
|
|
||||||
|
Each options entry is composed of an array or tuple with:
|
||||||
|
[[type, name, ?default?], ...]
|
||||||
|
Or it is a dict:
|
||||||
|
{name: [type, default], ...}
|
||||||
|
"""
|
||||||
|
if isinstance(options, (list,tuple)):
|
||||||
|
for optname in options:
|
||||||
|
if len(optname) > 2:
|
||||||
|
opttype, optname, optvalue = optname
|
||||||
|
else:
|
||||||
|
(opttype, optname), optvalue = optname, None
|
||||||
|
yield opttype, optname, optvalue
|
||||||
|
else:
|
||||||
|
for optname in options:
|
||||||
|
opttype, optvalue = options[optname]
|
||||||
|
yield opttype, optname, optvalue
|
||||||
|
|
||||||
|
|
||||||
class ConfigReader():
|
class ConfigReader():
|
||||||
"""Generic config reader class.
|
"""Generic config reader class.
|
||||||
|
@ -228,31 +252,22 @@ class ConfigReaderUnshared(SafeConfigParserWithIncludes):
|
||||||
# Or it is a dict:
|
# Or it is a dict:
|
||||||
# {name: [type, default], ...}
|
# {name: [type, default], ...}
|
||||||
|
|
||||||
def getOptions(self, sec, options, pOptions=None, shouldExist=False):
|
def getOptions(self, sec, options, pOptions=None, shouldExist=False, convert=True):
|
||||||
values = dict()
|
values = dict()
|
||||||
if pOptions is None:
|
if pOptions is None:
|
||||||
pOptions = {}
|
pOptions = {}
|
||||||
# Get only specified options:
|
# Get only specified options:
|
||||||
for optname in options:
|
for opttype, optname, optvalue in _OptionsTemplateGen(options):
|
||||||
if isinstance(options, (list,tuple)):
|
|
||||||
if len(optname) > 2:
|
|
||||||
opttype, optname, optvalue = optname
|
|
||||||
else:
|
|
||||||
(opttype, optname), optvalue = optname, None
|
|
||||||
else:
|
|
||||||
opttype, optvalue = options[optname]
|
|
||||||
if optname in pOptions:
|
if optname in pOptions:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
if opttype == "bool":
|
v = self.get(sec, optname, vars=pOptions)
|
||||||
v = self.getboolean(sec, optname)
|
|
||||||
if v is None: continue
|
|
||||||
elif opttype == "int":
|
|
||||||
v = self.getint(sec, optname)
|
|
||||||
if v is None: continue
|
|
||||||
else:
|
|
||||||
v = self.get(sec, optname, vars=pOptions)
|
|
||||||
values[optname] = v
|
values[optname] = v
|
||||||
|
if convert:
|
||||||
|
conv = CONVERTER.get(opttype)
|
||||||
|
if conv:
|
||||||
|
if v is None: continue
|
||||||
|
values[optname] = conv(v)
|
||||||
except NoSectionError as e:
|
except NoSectionError as e:
|
||||||
if shouldExist:
|
if shouldExist:
|
||||||
raise
|
raise
|
||||||
|
@ -324,8 +339,9 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||||
pOpts = dict()
|
pOpts = dict()
|
||||||
if self._initOpts:
|
if self._initOpts:
|
||||||
pOpts = _merge_dicts(pOpts, self._initOpts)
|
pOpts = _merge_dicts(pOpts, self._initOpts)
|
||||||
|
# type-convert only in combined (otherwise int/bool converting prevents substitution):
|
||||||
self._opts = ConfigReader.getOptions(
|
self._opts = ConfigReader.getOptions(
|
||||||
self, "Definition", self._configOpts, pOpts)
|
self, "Definition", self._configOpts, pOpts, convert=False)
|
||||||
self._pOpts = pOpts
|
self._pOpts = pOpts
|
||||||
if self.has_section("Init"):
|
if self.has_section("Init"):
|
||||||
# get only own options (without options from default):
|
# get only own options (without options from default):
|
||||||
|
@ -346,10 +362,21 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||||
if opt == '__name__' or opt in self._opts: continue
|
if opt == '__name__' or opt in self._opts: continue
|
||||||
self._opts[opt] = self.get("Definition", opt)
|
self._opts[opt] = self.get("Definition", opt)
|
||||||
|
|
||||||
|
def convertOptions(self, opts, configOpts):
|
||||||
|
"""Convert interpolated combined options to expected type.
|
||||||
|
"""
|
||||||
|
for opttype, optname, optvalue in _OptionsTemplateGen(configOpts):
|
||||||
|
conv = CONVERTER.get(opttype)
|
||||||
|
if conv:
|
||||||
|
v = opts.get(optname)
|
||||||
|
if v is None: continue
|
||||||
|
try:
|
||||||
|
opts[optname] = conv(v)
|
||||||
|
except ValueError:
|
||||||
|
logSys.warning("Wrong %s value %r for %r. Using default one: %r",
|
||||||
|
opttype, v, optname, optvalue)
|
||||||
|
opts[optname] = optvalue
|
||||||
|
|
||||||
def _convert_to_boolean(self, value):
|
|
||||||
return _as_bool(value)
|
|
||||||
|
|
||||||
def getCombOption(self, optname):
|
def getCombOption(self, optname):
|
||||||
"""Get combined definition option (as string) using pre-set and init
|
"""Get combined definition option (as string) using pre-set and init
|
||||||
options as preselection (values with higher precedence as specified in section).
|
options as preselection (values with higher precedence as specified in section).
|
||||||
|
@ -384,6 +411,8 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||||
ignore=ignore, addrepl=self.getCombOption)
|
ignore=ignore, addrepl=self.getCombOption)
|
||||||
if not opts:
|
if not opts:
|
||||||
raise ValueError('recursive tag definitions unable to be resolved')
|
raise ValueError('recursive tag definitions unable to be resolved')
|
||||||
|
# convert options after all interpolations:
|
||||||
|
self.convertOptions(opts, self._configOpts)
|
||||||
return opts
|
return opts
|
||||||
|
|
||||||
def convert(self):
|
def convert(self):
|
||||||
|
|
|
@ -398,8 +398,8 @@ def splitWithOptions(option):
|
||||||
# tags (<tag>) in tagged options.
|
# tags (<tag>) in tagged options.
|
||||||
#
|
#
|
||||||
|
|
||||||
# max tag replacement count:
|
# max tag replacement count (considering tag X in tag Y repeat):
|
||||||
MAX_TAG_REPLACE_COUNT = 10
|
MAX_TAG_REPLACE_COUNT = 25
|
||||||
|
|
||||||
# compiled RE for tag name (replacement name)
|
# compiled RE for tag name (replacement name)
|
||||||
TAG_CRE = re.compile(r'<([^ <>]+)>')
|
TAG_CRE = re.compile(r'<([^ <>]+)>')
|
||||||
|
@ -433,6 +433,7 @@ def substituteRecursiveTags(inptags, conditional='',
|
||||||
done = set()
|
done = set()
|
||||||
noRecRepl = hasattr(tags, "getRawItem")
|
noRecRepl = hasattr(tags, "getRawItem")
|
||||||
# repeat substitution while embedded-recursive (repFlag is True)
|
# repeat substitution while embedded-recursive (repFlag is True)
|
||||||
|
repCounts = {}
|
||||||
while True:
|
while True:
|
||||||
repFlag = False
|
repFlag = False
|
||||||
# substitute each value:
|
# substitute each value:
|
||||||
|
@ -444,7 +445,7 @@ def substituteRecursiveTags(inptags, conditional='',
|
||||||
value = orgval = uni_string(tags[tag])
|
value = orgval = uni_string(tags[tag])
|
||||||
# search and replace all tags within value, that can be interpolated using other tags:
|
# search and replace all tags within value, that can be interpolated using other tags:
|
||||||
m = tre_search(value)
|
m = tre_search(value)
|
||||||
refCounts = {}
|
rplc = repCounts.get(tag, {})
|
||||||
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
|
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
|
||||||
while m:
|
while m:
|
||||||
# found replacement tag:
|
# found replacement tag:
|
||||||
|
@ -454,13 +455,13 @@ def substituteRecursiveTags(inptags, conditional='',
|
||||||
m = tre_search(value, m.end())
|
m = tre_search(value, m.end())
|
||||||
continue
|
continue
|
||||||
#logSys.log(5, 'found: %s' % rtag)
|
#logSys.log(5, 'found: %s' % rtag)
|
||||||
if rtag == tag or refCounts.get(rtag, 1) > MAX_TAG_REPLACE_COUNT:
|
if rtag == tag or rplc.get(rtag, 1) > MAX_TAG_REPLACE_COUNT:
|
||||||
# recursive definitions are bad
|
# recursive definitions are bad
|
||||||
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
|
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"properties contain self referencing definitions "
|
"properties contain self referencing definitions "
|
||||||
"and cannot be resolved, fail tag: %s, found: %s in %s, value: %s" %
|
"and cannot be resolved, fail tag: %s, found: %s in %s, value: %s" %
|
||||||
(tag, rtag, refCounts, value))
|
(tag, rtag, rplc, value))
|
||||||
repl = None
|
repl = None
|
||||||
if conditional:
|
if conditional:
|
||||||
repl = tags.get(rtag + '?' + conditional)
|
repl = tags.get(rtag + '?' + conditional)
|
||||||
|
@ -480,7 +481,7 @@ def substituteRecursiveTags(inptags, conditional='',
|
||||||
value = value.replace('<%s>' % rtag, repl)
|
value = value.replace('<%s>' % rtag, repl)
|
||||||
#logSys.log(5, 'value now: %s' % value)
|
#logSys.log(5, 'value now: %s' % value)
|
||||||
# increment reference count:
|
# increment reference count:
|
||||||
refCounts[rtag] = refCounts.get(rtag, 0) + 1
|
rplc[rtag] = rplc.get(rtag, 0) + 1
|
||||||
# the next match for replace:
|
# the next match for replace:
|
||||||
m = tre_search(value, m.start())
|
m = tre_search(value, m.start())
|
||||||
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
|
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
|
||||||
|
@ -488,6 +489,7 @@ def substituteRecursiveTags(inptags, conditional='',
|
||||||
if orgval != value:
|
if orgval != value:
|
||||||
# check still contains any tag - should be repeated (possible embedded-recursive substitution):
|
# check still contains any tag - should be repeated (possible embedded-recursive substitution):
|
||||||
if tre_search(value):
|
if tre_search(value):
|
||||||
|
repCounts[tag] = rplc
|
||||||
repFlag = True
|
repFlag = True
|
||||||
# copy return tags dict to prevent modifying of inptags:
|
# copy return tags dict to prevent modifying of inptags:
|
||||||
if id(tags) == id(inptags):
|
if id(tags) == id(inptags):
|
||||||
|
|
|
@ -121,8 +121,11 @@ class MyTime:
|
||||||
|
|
||||||
@return ISO-capable string representation of given unixTime
|
@return ISO-capable string representation of given unixTime
|
||||||
"""
|
"""
|
||||||
return datetime.datetime.fromtimestamp(
|
# consider end of 9999th year (in GMT+23 to avoid year overflow in other TZ)
|
||||||
unixTime).replace(microsecond=0).strftime(format)
|
dt = datetime.datetime.fromtimestamp(
|
||||||
|
unixTime).replace(microsecond=0
|
||||||
|
) if unixTime < 253402214400 else datetime.datetime(9999, 12, 31, 23, 59, 59)
|
||||||
|
return dt.strftime(format)
|
||||||
|
|
||||||
## precreate/precompile primitives used in str2seconds:
|
## precreate/precompile primitives used in str2seconds:
|
||||||
|
|
||||||
|
|
|
@ -252,7 +252,7 @@ class CommandActionTest(LogCaptureTestCase):
|
||||||
delattr(self.__action, 'ac')
|
delattr(self.__action, 'ac')
|
||||||
# produce self-referencing query except:
|
# produce self-referencing query except:
|
||||||
self.assertRaisesRegexp(ValueError, r"possible self referencing definitions in query",
|
self.assertRaisesRegexp(ValueError, r"possible self referencing definitions in query",
|
||||||
lambda: self.__action.replaceTag("<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x>>>>>>>>>>>>>>>>>>>>>",
|
lambda: self.__action.replaceTag("<x"*30+">"*30,
|
||||||
self.__action._properties, conditional="family=inet6")
|
self.__action._properties, conditional="family=inet6")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -154,6 +154,21 @@ class AddFailure(unittest.TestCase):
|
||||||
finally:
|
finally:
|
||||||
self.__banManager.setBanTime(btime)
|
self.__banManager.setBanTime(btime)
|
||||||
|
|
||||||
|
def testBanList(self):
|
||||||
|
tickets = [
|
||||||
|
BanTicket('192.0.2.1', 1167605999.0),
|
||||||
|
BanTicket('192.0.2.2', 1167605999.0),
|
||||||
|
]
|
||||||
|
tickets[1].setBanTime(-1)
|
||||||
|
for t in tickets:
|
||||||
|
self.__banManager.addBanTicket(t)
|
||||||
|
self.assertSortedEqual(self.__banManager.getBanList(ordered=True, withTime=True),
|
||||||
|
[
|
||||||
|
'192.0.2.1 \t2006-12-31 23:59:59 + 600 = 2007-01-01 00:09:59',
|
||||||
|
'192.0.2.2 \t2006-12-31 23:59:59 + -1 = 9999-12-31 23:59:59'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StatusExtendedCymruInfo(unittest.TestCase):
|
class StatusExtendedCymruInfo(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -87,6 +87,21 @@ option = %s
|
||||||
self.assertTrue(self.c.read(f)) # we got some now
|
self.assertTrue(self.c.read(f)) # we got some now
|
||||||
return self.c.getOptions('section', [("int", 'option')])['option']
|
return self.c.getOptions('section', [("int", 'option')])['option']
|
||||||
|
|
||||||
|
def testConvert(self):
|
||||||
|
self.c.add_section("Definition")
|
||||||
|
self.c.set("Definition", "a", "1")
|
||||||
|
self.c.set("Definition", "b", "1")
|
||||||
|
self.c.set("Definition", "c", "test")
|
||||||
|
opts = self.c.getOptions("Definition",
|
||||||
|
(('int', 'a', 0), ('bool', 'b', 0), ('int', 'c', 0)))
|
||||||
|
self.assertSortedEqual(opts, {'a': 1, 'b': True, 'c': 0})
|
||||||
|
opts = self.c.getOptions("Definition",
|
||||||
|
(('int', 'a'), ('bool', 'b'), ('int', 'c')))
|
||||||
|
self.assertSortedEqual(opts, {'a': 1, 'b': True, 'c': None})
|
||||||
|
opts = self.c.getOptions("Definition",
|
||||||
|
{'a': ('int', 0), 'b': ('bool', 0), 'c': ('int', 0)})
|
||||||
|
self.assertSortedEqual(opts, {'a': 1, 'b': True, 'c': 0})
|
||||||
|
|
||||||
def testInaccessibleFile(self):
|
def testInaccessibleFile(self):
|
||||||
f = os.path.join(self.d, "d.conf") # inaccessible file
|
f = os.path.join(self.d, "d.conf") # inaccessible file
|
||||||
self._write('d.conf', 0)
|
self._write('d.conf', 0)
|
||||||
|
@ -483,14 +498,12 @@ class JailReaderTest(LogCaptureTestCase):
|
||||||
self.assertRaises(NoSectionError, c.getOptions, 'test', {})
|
self.assertRaises(NoSectionError, c.getOptions, 'test', {})
|
||||||
|
|
||||||
|
|
||||||
class FilterReaderTest(unittest.TestCase):
|
class FilterReaderTest(LogCaptureTestCase):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(FilterReaderTest, self).__init__(*args, **kwargs)
|
|
||||||
self.__share_cfg = {}
|
|
||||||
|
|
||||||
def testConvert(self):
|
def testConvert(self):
|
||||||
output = [['multi-set', 'testcase01', 'addfailregex', [
|
output = [
|
||||||
|
['set', 'testcase01', 'maxlines', 1],
|
||||||
|
['multi-set', 'testcase01', 'addfailregex', [
|
||||||
"^\\s*(?:\\S+ )?(?:kernel: \\[\\d+\\.\\d+\\] )?(?:@vserver_\\S+ )"
|
"^\\s*(?:\\S+ )?(?:kernel: \\[\\d+\\.\\d+\\] )?(?:@vserver_\\S+ )"
|
||||||
"?(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|"
|
"?(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|"
|
||||||
"[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:)?\\s*(?:"
|
"[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:)?\\s*(?:"
|
||||||
|
@ -512,7 +525,6 @@ class FilterReaderTest(unittest.TestCase):
|
||||||
['set', 'testcase01', 'addjournalmatch',
|
['set', 'testcase01', 'addjournalmatch',
|
||||||
"FIELD= with spaces ", "+", "AFIELD= with + char and spaces"],
|
"FIELD= with spaces ", "+", "AFIELD= with + char and spaces"],
|
||||||
['set', 'testcase01', 'datepattern', "%Y %m %d %H:%M:%S"],
|
['set', 'testcase01', 'datepattern', "%Y %m %d %H:%M:%S"],
|
||||||
['set', 'testcase01', 'maxlines', 1], # Last for overide test
|
|
||||||
]
|
]
|
||||||
filterReader = FilterReader("testcase01", "testcase01", {})
|
filterReader = FilterReader("testcase01", "testcase01", {})
|
||||||
filterReader.setBaseDir(TEST_FILES_DIR)
|
filterReader.setBaseDir(TEST_FILES_DIR)
|
||||||
|
@ -529,9 +541,18 @@ class FilterReaderTest(unittest.TestCase):
|
||||||
filterReader.read()
|
filterReader.read()
|
||||||
#filterReader.getOptions(["failregex", "ignoreregex"])
|
#filterReader.getOptions(["failregex", "ignoreregex"])
|
||||||
filterReader.getOptions(None)
|
filterReader.getOptions(None)
|
||||||
output[-1][-1] = "5"
|
output[0][-1] = 5; # maxlines = 5
|
||||||
self.assertSortedEqual(filterReader.convert(), output)
|
self.assertSortedEqual(filterReader.convert(), output)
|
||||||
|
|
||||||
|
def testConvertOptions(self):
|
||||||
|
filterReader = FilterReader("testcase01", "testcase01", {'maxlines': '<test>', 'test': 'X'},
|
||||||
|
share_config=TEST_FILES_DIR_SHARE_CFG, basedir=TEST_FILES_DIR)
|
||||||
|
filterReader.read()
|
||||||
|
filterReader.getOptions(None)
|
||||||
|
opts = filterReader.getCombined();
|
||||||
|
self.assertNotEqual(opts['maxlines'], 'X'); # wrong int value 'X' for 'maxlines'
|
||||||
|
self.assertLogged("Wrong int value 'X' for 'maxlines'. Using default one:")
|
||||||
|
|
||||||
def testFilterReaderSubstitionDefault(self):
|
def testFilterReaderSubstitionDefault(self):
|
||||||
output = [['set', 'jailname', 'addfailregex', 'to=sweet@example.com fromip=<IP>']]
|
output = [['set', 'jailname', 'addfailregex', 'to=sweet@example.com fromip=<IP>']]
|
||||||
filterReader = FilterReader('substition', "jailname", {},
|
filterReader = FilterReader('substition', "jailname", {},
|
||||||
|
@ -541,6 +562,17 @@ class FilterReaderTest(unittest.TestCase):
|
||||||
c = filterReader.convert()
|
c = filterReader.convert()
|
||||||
self.assertSortedEqual(c, output)
|
self.assertSortedEqual(c, output)
|
||||||
|
|
||||||
|
def testFilterReaderSubstKnown(self):
|
||||||
|
# testcase02.conf + testcase02.local, test covering that known/option is not overridden
|
||||||
|
# with unmodified (not available) value of option from .local config file, so wouldn't
|
||||||
|
# cause self-recursion if option already has a reference to known/option in .conf file.
|
||||||
|
filterReader = FilterReader('testcase02', "jailname", {},
|
||||||
|
share_config=TEST_FILES_DIR_SHARE_CFG, basedir=TEST_FILES_DIR)
|
||||||
|
filterReader.read()
|
||||||
|
filterReader.getOptions(None)
|
||||||
|
opts = filterReader.getCombined()
|
||||||
|
self.assertTrue('sshd' in opts['failregex'])
|
||||||
|
|
||||||
def testFilterReaderSubstitionSet(self):
|
def testFilterReaderSubstitionSet(self):
|
||||||
output = [['set', 'jailname', 'addfailregex', 'to=sour@example.com fromip=<IP>']]
|
output = [['set', 'jailname', 'addfailregex', 'to=sour@example.com fromip=<IP>']]
|
||||||
filterReader = FilterReader('substition', "jailname", {'honeypot': 'sour@example.com'},
|
filterReader = FilterReader('substition', "jailname", {'honeypot': 'sour@example.com'},
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
[INCLUDES]
|
||||||
|
|
||||||
|
# Read common prefixes. If any customizations available -- read them from
|
||||||
|
# common.local
|
||||||
|
before = testcase-common.conf
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
|
||||||
|
_daemon = sshd
|
||||||
|
__prefix_line = %(known/__prefix_line)s(?:\w{14,20}: )?
|
||||||
|
|
||||||
|
failregex = %(__prefix_line)s test
|
|
@ -0,0 +1,4 @@
|
||||||
|
[Definition]
|
||||||
|
|
||||||
|
# no options here, coverage for testFilterReaderSubstKnown:
|
||||||
|
# avoid to overwrite known/option with unmodified (not available) value of option from .local config file
|
|
@ -10,3 +10,8 @@ WARNING: Authentication attempt from 192.0.2.0 for user "null" failed.
|
||||||
apr 16, 2013 8:32:28 AM org.slf4j.impl.JCLLoggerAdapter warn
|
apr 16, 2013 8:32:28 AM org.slf4j.impl.JCLLoggerAdapter warn
|
||||||
# failJSON: { "time": "2013-04-16T08:32:28", "match": true , "host": "192.0.2.0" }
|
# failJSON: { "time": "2013-04-16T08:32:28", "match": true , "host": "192.0.2.0" }
|
||||||
WARNING: Authentication attempt from 192.0.2.0 for user "pippo" failed.
|
WARNING: Authentication attempt from 192.0.2.0 for user "pippo" failed.
|
||||||
|
|
||||||
|
# filterOptions: {"logging": "webapp"}
|
||||||
|
|
||||||
|
# failJSON: { "time": "2005-08-13T12:57:32", "match": true , "host": "182.23.72.36" }
|
||||||
|
12:57:32.907 [http-nio-8080-exec-10] WARN o.a.g.r.auth.AuthenticationService - Authentication attempt from 182.23.72.36 for user "guacadmin" failed.
|
||||||
|
|
Loading…
Reference in New Issue