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