mirror of https://github.com/fail2ban/fail2ban
Exposes filter group captures in actions (non-recursive interpolation of tags `<F-...>`);
Closes gh-1110pull/1698/head
parent
6d878f3a43
commit
61c8cd11b8
|
@ -23,6 +23,7 @@ __license__ = "GPL"
|
|||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
@ -31,6 +32,7 @@ import time
|
|||
from abc import ABCMeta
|
||||
from collections import MutableMapping
|
||||
|
||||
from .failregex import mapTag2Opt
|
||||
from .ipdns import asip
|
||||
from .mytime import MyTime
|
||||
from .utils import Utils
|
||||
|
@ -45,6 +47,10 @@ _cmd_lock = threading.Lock()
|
|||
# Todo: make it configurable resp. automatically set, ex.: `[ -f /proc/net/if_inet6 ] && echo 'yes' || echo 'no'`:
|
||||
allowed_ipv6 = True
|
||||
|
||||
# capture groups from filter for map to ticket data:
|
||||
FCUSTAG_CRE = re.compile(r'<F-([A-Z0-9_\-]+)>'); # currently uppercase only
|
||||
|
||||
|
||||
|
||||
class CallingMap(MutableMapping):
|
||||
"""A Mapping type which returns the result of callable values.
|
||||
|
@ -72,9 +78,9 @@ class CallingMap(MutableMapping):
|
|||
def __getitem__(self, key):
|
||||
value = self.data[key]
|
||||
if callable(value):
|
||||
return value()
|
||||
else:
|
||||
return value
|
||||
value = value()
|
||||
self.data[key] = value
|
||||
return value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.data[key] = value
|
||||
|
@ -546,6 +552,18 @@ class CommandAction(ActionBase):
|
|||
# Replace dynamical tags (don't use cache here)
|
||||
if aInfo is not None:
|
||||
realCmd = self.replaceTag(realCmd, aInfo, conditional=conditional)
|
||||
# Replace ticket options (filter capture groups) non-recursive:
|
||||
if '<' in realCmd:
|
||||
tickData = aInfo.get("F-*")
|
||||
if not tickData: tickData = {}
|
||||
def substTag(m):
|
||||
tn = mapTag2Opt(m.groups()[0])
|
||||
try:
|
||||
return str(tickData[tn])
|
||||
except KeyError:
|
||||
return ""
|
||||
|
||||
realCmd = FCUSTAG_CRE.sub(substTag, realCmd)
|
||||
else:
|
||||
realCmd = cmd
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ from .ipdns import IPAddr
|
|||
|
||||
FTAG_CRE = re.compile(r'</?[\w\-]+/?>')
|
||||
|
||||
FCUSTAG_CRE = re.compile(r'^(/?)F-([A-Z0-9_\-]+)$'); # currently uppercase only
|
||||
FCUSTNAME_CRE = re.compile(r'^(/?)F-([A-Z0-9_\-]+)$'); # currently uppercase only
|
||||
|
||||
R_HOST = [
|
||||
# separated ipv4:
|
||||
|
@ -83,6 +83,12 @@ R_MAP = {
|
|||
"PORT": "fport",
|
||||
}
|
||||
|
||||
def mapTag2Opt(tag):
|
||||
try: # if should be mapped:
|
||||
return R_MAP[tag]
|
||||
except KeyError:
|
||||
return tag.lower()
|
||||
|
||||
##
|
||||
# Regular expression class.
|
||||
#
|
||||
|
@ -144,7 +150,7 @@ class Regex:
|
|||
|
||||
# (begin / end tag) for customizable expressions, additionally used as
|
||||
# user custom tags (match will be stored in ticket data, can be used in actions):
|
||||
m = FCUSTAG_CRE.match(tn)
|
||||
m = FCUSTNAME_CRE.match(tn)
|
||||
if m: # match F-...
|
||||
m = m.groups()
|
||||
tn = m[1]
|
||||
|
@ -156,10 +162,8 @@ class Regex:
|
|||
return tag; # tag not opened, use original
|
||||
# open tag:
|
||||
openTags[tn] = 1
|
||||
try: # if should be mapped:
|
||||
tn = R_MAP[tn]
|
||||
except KeyError:
|
||||
tn = tn.lower()
|
||||
# if should be mapped:
|
||||
tn = mapTag2Opt(tn)
|
||||
return "(?P<%s>" % (tn,)
|
||||
|
||||
# original, no replacement:
|
||||
|
|
|
@ -56,7 +56,9 @@ class Ticket(object):
|
|||
self._time = time if time is not None else MyTime.time()
|
||||
self._data = {'matches': matches or [], 'failures': 0}
|
||||
if data is not None:
|
||||
self._data.update(data)
|
||||
for k,v in data.iteritems():
|
||||
if v is not None:
|
||||
self._data[k] = v
|
||||
if ticket:
|
||||
# ticket available - copy whole information from ticket:
|
||||
self.__dict__.update(i for i in ticket.__dict__.iteritems() if i[0] in self.__dict__)
|
||||
|
|
|
@ -329,13 +329,24 @@ class CommandActionTest(LogCaptureTestCase):
|
|||
self.assertEqual(self.__action.ROST,"192.0.2.0")
|
||||
|
||||
def testExecuteActionUnbanAinfo(self):
|
||||
aInfo = {
|
||||
aInfo = CallingMap({
|
||||
'ABC': "123",
|
||||
}
|
||||
self.__action.actionban = "touch /tmp/fail2ban.test.123"
|
||||
self.__action.actionunban = "rm /tmp/fail2ban.test.<ABC>"
|
||||
'ip': '192.0.2.1',
|
||||
'F-*': lambda: {
|
||||
'fid': 111,
|
||||
'fport': 222,
|
||||
'user': "tester"
|
||||
}
|
||||
})
|
||||
self.__action.actionban = "touch /tmp/fail2ban.test.123; echo 'failure <F-ID> of <F-USER> -<F-TEST>- from <ip>:<F-PORT>'"
|
||||
self.__action.actionunban = "rm /tmp/fail2ban.test.<ABC>; echo 'user <F-USER> unbanned'"
|
||||
self.__action.ban(aInfo)
|
||||
self.__action.unban(aInfo)
|
||||
self.assertLogged(
|
||||
" -- stdout: 'failure 111 of tester -- from 192.0.2.1:222'",
|
||||
" -- stdout: 'user tester unbanned'",
|
||||
all=True
|
||||
)
|
||||
|
||||
def testExecuteActionStartEmpty(self):
|
||||
self.__action.actionstart = ""
|
||||
|
|
|
@ -761,9 +761,10 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
|||
"[Definition]",
|
||||
"norestored = %(_exec_once)s",
|
||||
"restore = ",
|
||||
"info = ",
|
||||
"actionstart = echo '[%(name)s] %(actname)s: ** start'", start,
|
||||
"actionreload = echo '[%(name)s] %(actname)s: .. reload'", reload,
|
||||
"actionban = echo '[%(name)s] %(actname)s: ++ ban <ip> %(restore)s'", ban,
|
||||
"actionban = echo '[%(name)s] %(actname)s: ++ ban <ip> %(restore)s%(info)s'", ban,
|
||||
"actionunban = echo '[%(name)s] %(actname)s: -- unban <ip>'", unban,
|
||||
"actionstop = echo '[%(name)s] %(actname)s: __ stop'", stop,
|
||||
)
|
||||
|
@ -777,28 +778,28 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
|||
"usedns = no",
|
||||
"maxretry = 3",
|
||||
"findtime = 10m",
|
||||
"failregex = ^\s*failure (401|403) from <HOST>",
|
||||
"failregex = ^\s*failure <F-ERRCODE>401|403</F-ERRCODE> from <HOST>",
|
||||
"datepattern = {^LN-BEG}EPOCH",
|
||||
"",
|
||||
"[test-jail1]", "backend = " + backend, "filter =",
|
||||
"action = ",
|
||||
" test-action1[name='%(__name__)s']" \
|
||||
if 1 in actions else "",
|
||||
" test-action2[name='%(__name__)s', restore='restored: <restored>']" \
|
||||
" test-action2[name='%(__name__)s', restore='restored: <restored>', info=', err-code: <F-ERRCODE>']" \
|
||||
if 2 in actions else "",
|
||||
" test-action2[name='%(__name__)s', actname=test-action3, _exec_once=1, restore='restored: <restored>']" \
|
||||
if 3 in actions else "",
|
||||
"logpath = " + test1log,
|
||||
" " + test2log if 2 in enabled else "",
|
||||
" " + test3log if 2 in enabled else "",
|
||||
"failregex = ^\s*failure (401|403) from <HOST>",
|
||||
" ^\s*error (401|403) from <HOST>" \
|
||||
"failregex = ^\s*failure <F-ERRCODE>401|403</F-ERRCODE> from <HOST>",
|
||||
" ^\s*error <F-ERRCODE>401|403</F-ERRCODE> from <HOST>" \
|
||||
if 2 in enabled else "",
|
||||
"enabled = true" if 1 in enabled else "",
|
||||
"",
|
||||
"[test-jail2]", "backend = " + backend, "filter =",
|
||||
"action = ",
|
||||
" test-action2[name='%(__name__)s', restore='restored: <restored>']" \
|
||||
" test-action2[name='%(__name__)s', restore='restored: <restored>', info=', err-code: <F-ERRCODE>']" \
|
||||
if 2 in actions else "",
|
||||
" test-action2[name='%(__name__)s', actname=test-action3, _exec_once=1, restore='restored: <restored>']" \
|
||||
if 3 in actions else "",
|
||||
|
@ -837,7 +838,7 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
|||
"stdout: '[test-jail1] test-action2: ** start'", all=True)
|
||||
# test restored is 0 (both actions available):
|
||||
self.assertLogged(
|
||||
"stdout: '[test-jail1] test-action2: ++ ban 192.0.2.1 restored: 0'",
|
||||
"stdout: '[test-jail1] test-action2: ++ ban 192.0.2.1 restored: 0, err-code: 401'",
|
||||
"stdout: '[test-jail1] test-action3: ++ ban 192.0.2.1 restored: 0'",
|
||||
all=True, wait=MID_WAITTIME)
|
||||
|
||||
|
@ -958,8 +959,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
|||
)
|
||||
# test restored is 1 (only test-action2):
|
||||
self.assertLogged(
|
||||
"stdout: '[test-jail2] test-action2: ++ ban 192.0.2.4 restored: 1'",
|
||||
"stdout: '[test-jail2] test-action2: ++ ban 192.0.2.8 restored: 1'",
|
||||
"stdout: '[test-jail2] test-action2: ++ ban 192.0.2.4 restored: 1, err-code: 401'",
|
||||
"stdout: '[test-jail2] test-action2: ++ ban 192.0.2.8 restored: 1, err-code: 401'",
|
||||
all=True, wait=MID_WAITTIME)
|
||||
# test test-action3 not executed at all (norestored check):
|
||||
self.assertNotLogged(
|
||||
|
|
Loading…
Reference in New Issue