extend `ignorecommand` to use actions-similar replacement (ticket-based now, so capable to interpolate all possible tags)

pull/2176/head
sebres 2018-07-09 13:01:16 +02:00
parent 11c1bf0149
commit 9b6d17d07e
4 changed files with 42 additions and 9 deletions

View File

@ -50,6 +50,8 @@ ver. 0.10.4-dev-1 (20??/??/??) - development edition
* systemd: fixed type error on option `journalflags`: an integer is required (gh-2125); * systemd: fixed type error on option `journalflags`: an integer is required (gh-2125);
### New Features ### New Features
* `ignorecommand` extended to use actions-similar replacement (capable to interpolate
all possible tags like `<ip-host>`, `<family>`, `<fid>`, `F-USER` etc.)
### Enhancements ### Enhancements
* `filter.d/dovecot.conf`: extended with tags F-USER (and alternatives) to collect user-logins (gh-2168) * `filter.d/dovecot.conf`: extended with tags F-USER (and alternatives) to collect user-logins (gh-2168)

View File

@ -30,6 +30,7 @@ import re
import sys import sys
import time import time
from .actions import Actions
from .failmanager import FailManagerEmpty, FailManager from .failmanager import FailManagerEmpty, FailManager
from .ipdns import DNSUtils, IPAddr from .ipdns import DNSUtils, IPAddr
from .ticket import FailTicket from .ticket import FailTicket
@ -418,11 +419,12 @@ class Filter(JailThread):
def addBannedIP(self, ip): def addBannedIP(self, ip):
if not isinstance(ip, IPAddr): if not isinstance(ip, IPAddr):
ip = IPAddr(ip) ip = IPAddr(ip)
if self.inIgnoreIPList(ip, log_ignore=False):
logSys.warning('Requested to manually ban an ignored IP %s. User knows best. Proceeding to ban it.', ip)
unixTime = MyTime.time() unixTime = MyTime.time()
self.failManager.addFailure(FailTicket(ip, unixTime), self.failManager.getMaxRetry()) ticket = FailTicket(ip, unixTime)
if self._inIgnoreIPList(ip, ticket, log_ignore=False):
logSys.warning('Requested to manually ban an ignored IP %s. User knows best. Proceeding to ban it.', ip)
self.failManager.addFailure(ticket, self.failManager.getMaxRetry())
# Perform the banning of the IP now. # Perform the banning of the IP now.
try: # pragma: no branch - exception is the only way out try: # pragma: no branch - exception is the only way out
@ -487,13 +489,19 @@ class Filter(JailThread):
# #
# Check if the given IP address matches an IP address/DNS or a CIDR # Check if the given IP address matches an IP address/DNS or a CIDR
# mask in the ignore list. # mask in the ignore list.
# @param ip IP address object # @param ip IP address object or ticket
# @return True if IP address is in ignore list # @return True if IP address is in ignore list
def inIgnoreIPList(self, ip, log_ignore=True): def inIgnoreIPList(self, ip, log_ignore=True):
if not isinstance(ip, IPAddr): ticket = None
if isinstance(ip, FailTicket):
ticket = ip
ip = ticket.getIP()
elif not isinstance(ip, IPAddr):
ip = IPAddr(ip) ip = IPAddr(ip)
return self._inIgnoreIPList(ip, ticket, log_ignore)
def _inIgnoreIPList(self, ip, ticket, log_ignore=True):
# check own IPs should be ignored and 'ip' is self IP: # check own IPs should be ignored and 'ip' is self IP:
if self.__ignoreSelf and ip in DNSUtils.getSelfIPs(): if self.__ignoreSelf and ip in DNSUtils.getSelfIPs():
self.logIgnoreIp(ip, log_ignore, ignore_source="ignoreself rule") self.logIgnoreIp(ip, log_ignore, ignore_source="ignoreself rule")
@ -506,6 +514,10 @@ class Filter(JailThread):
return True return True
if self.__ignoreCommand: if self.__ignoreCommand:
if ticket:
aInfo = Actions.ActionInfo(ticket, self.jail)
command = CommandAction.replaceDynamicTags(self.__ignoreCommand, aInfo)
else:
command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip }) command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip })
logSys.debug('ignore command: %s', command) logSys.debug('ignore command: %s', command)
ret, ret_ignore = CommandAction.executeCmd(command, success_codes=(0, 1)) ret, ret_ignore = CommandAction.executeCmd(command, success_codes=(0, 1))
@ -549,12 +561,12 @@ class Filter(JailThread):
fail = element[3] fail = element[3]
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 self.inIgnoreIPList(ip): tick = FailTicket(ip, unixTime, data=fail)
if self._inIgnoreIPList(ip, tick):
continue continue
logSys.info( logSys.info(
"[%s] Found %s - %s", self.jailName, ip, datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S") "[%s] Found %s - %s", self.jailName, ip, datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S")
) )
tick = FailTicket(ip, unixTime, data=fail)
self.failManager.addFailure(tick) self.failManager.addFailure(tick)
# reset (halve) error counter (successfully processed line): # reset (halve) error counter (successfully processed line):
if self._errors: if self._errors:

View File

@ -38,7 +38,7 @@ except ImportError:
from ..server.jail import Jail from ..server.jail import Jail
from ..server.filterpoll import FilterPoll from ..server.filterpoll import FilterPoll
from ..server.filter import Filter, FileFilter, FileContainer from ..server.filter import FailTicket, Filter, FileFilter, FileContainer
from ..server.failmanager import FailManagerEmpty from ..server.failmanager import FailManagerEmpty
from ..server.ipdns import DNSUtils, IPAddr from ..server.ipdns import DNSUtils, IPAddr
from ..server.mytime import MyTime from ..server.mytime import MyTime
@ -409,6 +409,24 @@ class IgnoreIP(LogCaptureTestCase):
self.assertFalse(self.filter.inIgnoreIPList("")) self.assertFalse(self.filter.inIgnoreIPList(""))
self.assertLogged("usage: ignorecommand IP", "returned 10", all=True) self.assertLogged("usage: ignorecommand IP", "returned 10", all=True)
def testIgnoreCommandForTicket(self):
# by host of IP (2001:db8::1 and 2001:db8::ffff map to "test-host" and "test-other" in the test-suite):
self.filter.setIgnoreCommand('if [ "<ip-host>" = "test-host" ]; then exit 0; fi; exit 1')
self.pruneLog()
self.assertTrue(self.filter.inIgnoreIPList(FailTicket("2001:db8::1")))
self.assertLogged("returned successfully 0")
self.pruneLog()
self.assertFalse(self.filter.inIgnoreIPList(FailTicket("2001:db8::ffff")))
self.assertLogged("returned successfully 1")
# by user-name (ignore tester):
self.filter.setIgnoreCommand('if [ "<F-USER>" = "tester" ]; then exit 0; fi; exit 1')
self.pruneLog()
self.assertTrue(self.filter.inIgnoreIPList(FailTicket("tester", data={'user': 'tester'})))
self.assertLogged("returned successfully 0")
self.pruneLog()
self.assertFalse(self.filter.inIgnoreIPList(FailTicket("root", data={'user': 'root'})))
self.assertLogged("returned successfully 1", all=True)
def testIgnoreCauseOK(self): def testIgnoreCauseOK(self):
ip = "93.184.216.34" ip = "93.184.216.34"
for ignore_source in ["dns", "ip", "command"]: for ignore_source in ["dns", "ip", "command"]:

View File

@ -316,6 +316,7 @@ def initTests(opts):
c.set('203.0.113.%s' % i, None) c.set('203.0.113.%s' % i, None)
c.set('2001:db8::%s' %i, 'test-host') c.set('2001:db8::%s' %i, 'test-host')
# some legal ips used in our test cases (prevent slow dns-resolving and failures if will be changed later): # some legal ips used in our test cases (prevent slow dns-resolving and failures if will be changed later):
c.set('2001:db8::ffff', 'test-other')
c.set('87.142.124.10', 'test-host') c.set('87.142.124.10', 'test-host')
if unittest.F2B.no_network: # pragma: no cover if unittest.F2B.no_network: # pragma: no cover
# precache all wrong dns to ip's used in test cases: # precache all wrong dns to ip's used in test cases: