mirror of https://github.com/fail2ban/fail2ban
ENH+BF+TST: Filter now returns reference to failregex and ignoreregex
This avoids duplication of code across fail2ban-regex and samples test cases. This also now more neatly resolves the issue of double counting date templates matches in fail2ban-regex. In addition, the samples test cases now also print a warning message that not all regexs have samples for them, with future plan to change this to an assertion.pull/298/merge^2
parent
5bd186b854
commit
1a2b6442a0
|
@ -46,7 +46,6 @@ from client.configparserinc import SafeConfigParserWithIncludes
|
|||
from ConfigParser import NoOptionError, NoSectionError, MissingSectionHeaderError
|
||||
from server.filter import Filter
|
||||
from server.failregex import RegexException
|
||||
from server.datedetector import DateDetector
|
||||
|
||||
from testcases.utils import FormatterWithTraceBack
|
||||
# Gets the instance of the logger.
|
||||
|
@ -130,7 +129,7 @@ class RegexStat(object):
|
|||
return self._failregex
|
||||
|
||||
def appendIP(self, value):
|
||||
self._ipList.extend(value)
|
||||
self._ipList.append(value)
|
||||
|
||||
def getIPList(self):
|
||||
return self._ipList
|
||||
|
@ -173,8 +172,6 @@ class Fail2banRegex(object):
|
|||
self._ignoreregex = list()
|
||||
self._failregex = list()
|
||||
self._line_stats = LineStats()
|
||||
self._dateDetector = DateDetector()
|
||||
self._dateDetector.addDefaultTemplate()
|
||||
|
||||
|
||||
def readRegex(self, value, regextype):
|
||||
|
@ -204,53 +201,39 @@ class Fail2banRegex(object):
|
|||
regex_values = [RegexStat(value)]
|
||||
|
||||
setattr(self, "_" + regex, regex_values)
|
||||
for regex in regex_values:
|
||||
getattr(
|
||||
self._filter,
|
||||
'add%sRegex' % regextype.title())(regex.getFailRegex())
|
||||
return True
|
||||
|
||||
def testIgnoreRegex(self, line):
|
||||
found = False
|
||||
for regex in self._ignoreregex:
|
||||
try:
|
||||
self._filter.addIgnoreRegex(regex.getFailRegex())
|
||||
try:
|
||||
ret = self._filter.ignoreLine(line)
|
||||
if ret:
|
||||
found = True
|
||||
regex.inc()
|
||||
except RegexException, e:
|
||||
print e
|
||||
return False
|
||||
finally:
|
||||
self._filter.delIgnoreRegex(0)
|
||||
try:
|
||||
ret = self._filter.ignoreLine(line)
|
||||
if ret is not None:
|
||||
found = True
|
||||
regex = self._ignoreregex[ret].inc()
|
||||
except RegexException, e:
|
||||
print e
|
||||
return False
|
||||
return found
|
||||
|
||||
def testRegex(self, line):
|
||||
found = False
|
||||
for regex in self._ignoreregex:
|
||||
self._filter.addIgnoreRegex(regex.getFailRegex())
|
||||
for regex in self._failregex:
|
||||
try:
|
||||
self._filter.addFailRegex(regex.getFailRegex())
|
||||
try:
|
||||
ret = self._filter.processLine(line)
|
||||
if len(ret):
|
||||
if found == True:
|
||||
ret[0].append(True)
|
||||
else:
|
||||
found = True
|
||||
ret[0].append(False)
|
||||
regex.inc()
|
||||
regex.appendIP(ret)
|
||||
except RegexException, e:
|
||||
print e
|
||||
return False
|
||||
except IndexError:
|
||||
print "Sorry, but no <host> found in regex"
|
||||
return False
|
||||
finally:
|
||||
self._filter.delFailRegex(0)
|
||||
for regex in self._ignoreregex:
|
||||
self._filter.delIgnoreRegex(0)
|
||||
return found
|
||||
try:
|
||||
ret = self._filter.processLine(line, checkAllRegex=True)
|
||||
for match in ret:
|
||||
match.append(len(ret)>1)
|
||||
regex = self._failregex[match[0]]
|
||||
regex.inc()
|
||||
regex.appendIP(match)
|
||||
except RegexException, e:
|
||||
print e
|
||||
return False
|
||||
except IndexError:
|
||||
print "Sorry, but no <host> found in regex"
|
||||
return False
|
||||
return len(ret) > 0
|
||||
|
||||
|
||||
def process(self, test_lines):
|
||||
|
@ -259,9 +242,6 @@ class Fail2banRegex(object):
|
|||
if line.startswith('#') or not line.strip():
|
||||
# skip comment and empty lines
|
||||
continue
|
||||
|
||||
self._dateDetector.matchTime(line)
|
||||
|
||||
is_ignored = fail2banRegex.testIgnoreRegex(line)
|
||||
if is_ignored:
|
||||
self._line_stats.ignored_lines.append(line)
|
||||
|
@ -302,10 +282,13 @@ class Fail2banRegex(object):
|
|||
|
||||
if self._verbose and len(failregex.getIPList()):
|
||||
for ip in failregex.getIPList():
|
||||
timeTuple = time.localtime(ip[1])
|
||||
timeTuple = time.localtime(ip[2])
|
||||
timeString = time.strftime("%a %b %d %H:%M:%S %Y", timeTuple)
|
||||
out.append(" %s %s%s" % (
|
||||
ip[0], timeString, ip[2] and " (already matched)" or ""))
|
||||
out.append(
|
||||
" %s %s%s" % (
|
||||
ip[1],
|
||||
timeString,
|
||||
ip[3] and " (multiple regex matched)" or ""))
|
||||
|
||||
print "\n%s: %d total" % (title, total)
|
||||
pprint_list(out, " #) [# of hits] regular expression")
|
||||
|
@ -318,7 +301,7 @@ class Fail2banRegex(object):
|
|||
|
||||
print "\nDate template hits:"
|
||||
out = []
|
||||
for template in self._dateDetector.getTemplates():
|
||||
for template in self._filter.dateDetector.getTemplates():
|
||||
if self._verbose or template.getHits():
|
||||
out.append("[%d] %s" % (template.getHits(), template.getName()))
|
||||
pprint_list(out, "[# of hits] date format")
|
||||
|
|
|
@ -174,6 +174,7 @@ class DateDetector:
|
|||
match = template.matchDate(line)
|
||||
if not match is None:
|
||||
logSys.debug("Matched time template %s" % template.getName())
|
||||
template.incHits()
|
||||
return match
|
||||
return None
|
||||
finally:
|
||||
|
|
|
@ -59,11 +59,12 @@ class DateTemplate:
|
|||
|
||||
def getHits(self):
|
||||
return self.__hits
|
||||
|
||||
def incHits(self):
|
||||
self.__hits += 1
|
||||
|
||||
def matchDate(self, line):
|
||||
dateMatch = self.__cRegex.search(line)
|
||||
if not dateMatch is None:
|
||||
self.__hits += 1
|
||||
return dateMatch
|
||||
|
||||
def getDate(self, line):
|
||||
|
|
|
@ -284,7 +284,7 @@ class Filter(JailThread):
|
|||
return False
|
||||
|
||||
|
||||
def processLine(self, line, returnRawHost=False):
|
||||
def processLine(self, line, returnRawHost=False, checkAllRegex=False):
|
||||
"""Split the time portion from log msg and return findFailures on them
|
||||
"""
|
||||
try:
|
||||
|
@ -306,14 +306,15 @@ class Filter(JailThread):
|
|||
else:
|
||||
timeLine = l
|
||||
logLine = l
|
||||
return self.findFailure(timeLine, logLine, returnRawHost)
|
||||
return self.findFailure(timeLine, logLine, returnRawHost, checkAllRegex)
|
||||
|
||||
def processLineAndAdd(self, line):
|
||||
"""Processes the line for failures and populates failManager
|
||||
"""
|
||||
for element in self.processLine(line):
|
||||
ip = element[0]
|
||||
unixTime = element[1]
|
||||
failregex = element[0]
|
||||
ip = element[1]
|
||||
unixTime = element[2]
|
||||
logSys.debug("Processing line with time:%s and ip:%s"
|
||||
% (unixTime, ip))
|
||||
if unixTime < MyTime.time() - self.getFindTime():
|
||||
|
@ -335,11 +336,11 @@ class Filter(JailThread):
|
|||
# @return: a boolean
|
||||
|
||||
def ignoreLine(self, line):
|
||||
for ignoreRegex in self.__ignoreRegex:
|
||||
for ignoreRegexIndex, ignoreRegex in enumerate(self.__ignoreRegex):
|
||||
ignoreRegex.search(line)
|
||||
if ignoreRegex.hasMatched():
|
||||
return True
|
||||
return False
|
||||
return ignoreRegexIndex
|
||||
return None
|
||||
|
||||
##
|
||||
# Finds the failure in a line given split into time and log parts.
|
||||
|
@ -348,18 +349,19 @@ class Filter(JailThread):
|
|||
# to find the logging time.
|
||||
# @return a dict with IP and timestamp.
|
||||
|
||||
def findFailure(self, timeLine, logLine, returnRawHost=False):
|
||||
def findFailure(self, timeLine, logLine,
|
||||
returnRawHost=False, checkAllRegex=False):
|
||||
failList = list()
|
||||
# Checks if we must ignore this line.
|
||||
if self.ignoreLine(logLine):
|
||||
if self.ignoreLine(logLine) is not None:
|
||||
# The ignoreregex matched. Return.
|
||||
return failList
|
||||
date = self.dateDetector.getUnixTime(timeLine)
|
||||
# Iterates over all the regular expressions.
|
||||
for failRegex in self.__failRegex:
|
||||
for failRegexIndex, failRegex in enumerate(self.__failRegex):
|
||||
failRegex.search(logLine)
|
||||
if failRegex.hasMatched():
|
||||
# The failregex matched.
|
||||
date = self.dateDetector.getUnixTime(timeLine)
|
||||
logSys.log(7, "Date: %r, message: %r",
|
||||
timeLine, logLine)
|
||||
if date is None:
|
||||
|
@ -372,14 +374,16 @@ class Filter(JailThread):
|
|||
try:
|
||||
host = failRegex.getHost()
|
||||
if returnRawHost:
|
||||
failList.append([host, date])
|
||||
break
|
||||
ipMatch = DNSUtils.textToIp(host, self.__useDns)
|
||||
if ipMatch:
|
||||
for ip in ipMatch:
|
||||
failList.append([ip, date])
|
||||
# We matched a regex, it is enough to stop.
|
||||
break
|
||||
failList.append([failRegexIndex, host, date])
|
||||
if not checkAllRegex:
|
||||
break
|
||||
else:
|
||||
ipMatch = DNSUtils.textToIp(host, self.__useDns)
|
||||
if ipMatch:
|
||||
for ip in ipMatch:
|
||||
failList.append([failRegexIndex, ip, date])
|
||||
if not checkAllRegex:
|
||||
break
|
||||
except RegexException, e: # pragma: no cover - unsure if reachable
|
||||
logSys.error(e)
|
||||
return failList
|
||||
|
|
|
@ -82,6 +82,7 @@ def testSampleRegexsFactory(name):
|
|||
logFile = fileinput.FileInput(
|
||||
os.path.join(TEST_FILES_DIR, "logs", name))
|
||||
|
||||
regexsUsed = set()
|
||||
for line in logFile:
|
||||
jsonREMatch = re.match("^# ?failJSON:(.+)$", line)
|
||||
if jsonREMatch:
|
||||
|
@ -96,7 +97,8 @@ def testSampleRegexsFactory(name):
|
|||
else:
|
||||
faildata = {}
|
||||
|
||||
ret = self.filter.processLine(line, returnRawHost=True)
|
||||
ret = self.filter.processLine(
|
||||
line, returnRawHost=True, checkAllRegex=True)
|
||||
if not ret:
|
||||
# Check line is flagged as none match
|
||||
self.assertFalse(faildata.get('match', True),
|
||||
|
@ -107,15 +109,28 @@ def testSampleRegexsFactory(name):
|
|||
self.assertTrue(faildata.get('match', False),
|
||||
"Line matched when shouldn't have: %s:%i %r" %
|
||||
(logFile.filename(), logFile.filelineno(), line))
|
||||
self.assertEqual(len(ret), 1)
|
||||
self.assertEqual(len(ret), 1, "Multiple regexs matched")
|
||||
# Verify timestamp and host as expected
|
||||
host, time = ret[0]
|
||||
failregex, host, time = ret[0]
|
||||
self.assertEqual(host, faildata.get("host", None))
|
||||
self.assertEqual(
|
||||
datetime.datetime.fromtimestamp(time),
|
||||
datetime.datetime.strptime(
|
||||
faildata.get("time", None), "%Y-%m-%dT%H:%M:%S"))
|
||||
|
||||
regexsUsed.add(failregex)
|
||||
|
||||
# TODO: Remove exception handling once all regexs have samples
|
||||
for failRegexIndex, failRegex in enumerate(self.filter.getFailRegex()):
|
||||
try:
|
||||
self.assertTrue(
|
||||
failRegexIndex in regexsUsed,
|
||||
"Regex for filter '%s' has no samples: %i: %r" %
|
||||
(name, failRegexIndex, failRegex))
|
||||
except AssertionError:
|
||||
print "I: Regex for filter '%s' has no samples: %i: %r" % (
|
||||
name, failRegexIndex, failRegex)
|
||||
|
||||
return testFilter
|
||||
|
||||
for filter_ in os.listdir(os.path.join(CONFIG_DIR, "filter.d")):
|
||||
|
|
Loading…
Reference in New Issue