mirror of https://github.com/fail2ban/fail2ban
Merge pull request #189 from kwirk/multiaction
Allow creation of multiple of the same action for a filter -- use actname option. Close #37pull/214/merge
commit
e019ab784c
|
@ -457,23 +457,15 @@ ignoreip = 168.192.0.1
|
||||||
# Miscelaneous
|
# Miscelaneous
|
||||||
#
|
#
|
||||||
|
|
||||||
# Multiple jails, 1 per protocol, are necessary ATM:
|
[asterisk]
|
||||||
# see https://github.com/fail2ban/fail2ban/issues/37
|
|
||||||
[asterisk-tcp]
|
|
||||||
|
|
||||||
filter = asterisk
|
|
||||||
port = 5060,5061
|
port = 5060,5061
|
||||||
protocol = tcp
|
|
||||||
logpath = /var/log/asterisk/messages
|
|
||||||
maxretry = 10
|
|
||||||
|
|
||||||
[asterisk-udp]
|
|
||||||
|
|
||||||
filter = asterisk
|
|
||||||
port = 5060,5061
|
|
||||||
protocol = udp
|
|
||||||
logpath = /var/log/asterisk/messages
|
logpath = /var/log/asterisk/messages
|
||||||
maxretry = 10
|
maxretry = 10
|
||||||
|
# Astrix requires both tcp and udp
|
||||||
|
action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
|
||||||
|
%(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
|
||||||
|
%(mta)s-whois[name=%(__name__)s, dest="%(destemail)s"]
|
||||||
|
|
||||||
# To log wrong MySQL access attempts add to /etc/my.cnf:
|
# To log wrong MySQL access attempts add to /etc/my.cnf:
|
||||||
# log-error=/var/log/mysqld.log
|
# log-error=/var/log/mysqld.log
|
||||||
|
|
|
@ -43,27 +43,38 @@ class ActionReader(DefinitionInitConfigReader):
|
||||||
["string", "actionunban", ""],
|
["string", "actionunban", ""],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __init__(self, file_, jailName, initOpts, **kwargs):
|
||||||
|
self._name = initOpts.get("actname", file_)
|
||||||
|
DefinitionInitConfigReader.__init__(
|
||||||
|
self, file_, jailName, initOpts, **kwargs)
|
||||||
|
|
||||||
|
def setName(self, name):
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
def read(self):
|
def read(self):
|
||||||
return ConfigReader.read(self, os.path.join("action.d", self._file))
|
return ConfigReader.read(self, os.path.join("action.d", self._file))
|
||||||
|
|
||||||
def convert(self):
|
def convert(self):
|
||||||
head = ["set", self._name]
|
head = ["set", self._jailName]
|
||||||
stream = list()
|
stream = list()
|
||||||
stream.append(head + ["addaction", self._file])
|
stream.append(head + ["addaction", self._name])
|
||||||
for opt in self._opts:
|
for opt in self._opts:
|
||||||
if opt == "actionstart":
|
if opt == "actionstart":
|
||||||
stream.append(head + ["actionstart", self._file, self._opts[opt]])
|
stream.append(head + ["actionstart", self._name, self._opts[opt]])
|
||||||
elif opt == "actionstop":
|
elif opt == "actionstop":
|
||||||
stream.append(head + ["actionstop", self._file, self._opts[opt]])
|
stream.append(head + ["actionstop", self._name, self._opts[opt]])
|
||||||
elif opt == "actioncheck":
|
elif opt == "actioncheck":
|
||||||
stream.append(head + ["actioncheck", self._file, self._opts[opt]])
|
stream.append(head + ["actioncheck", self._name, self._opts[opt]])
|
||||||
elif opt == "actionban":
|
elif opt == "actionban":
|
||||||
stream.append(head + ["actionban", self._file, self._opts[opt]])
|
stream.append(head + ["actionban", self._name, self._opts[opt]])
|
||||||
elif opt == "actionunban":
|
elif opt == "actionunban":
|
||||||
stream.append(head + ["actionunban", self._file, self._opts[opt]])
|
stream.append(head + ["actionunban", self._name, self._opts[opt]])
|
||||||
# cInfo
|
# cInfo
|
||||||
if self._initOpts:
|
if self._initOpts:
|
||||||
for p in self._initOpts:
|
for p in self._initOpts:
|
||||||
stream.append(head + ["setcinfo", self._file, p, self._initOpts[p]])
|
stream.append(head + ["setcinfo", self._name, p, self._initOpts[p]])
|
||||||
|
|
||||||
return stream
|
return stream
|
||||||
|
|
|
@ -132,6 +132,12 @@ class Beautifier:
|
||||||
msg = msg + "|- [" + str(c) + "]: " + ip + "\n"
|
msg = msg + "|- [" + str(c) + "]: " + ip + "\n"
|
||||||
c += 1
|
c += 1
|
||||||
msg = msg + "`- [" + str(c) + "]: " + response[len(response)-1]
|
msg = msg + "`- [" + str(c) + "]: " + response[len(response)-1]
|
||||||
|
elif inC[2] == "actions":
|
||||||
|
if len(response) == 0:
|
||||||
|
msg = "No actions for jail %s" % inC[1]
|
||||||
|
else:
|
||||||
|
msg = "The jail %s has the following actions:\n" % inC[1]
|
||||||
|
msg += ", ".join(action.getName() for action in response)
|
||||||
except Exception:
|
except Exception:
|
||||||
logSys.warning("Beautifier error. Please report the error")
|
logSys.warning("Beautifier error. Please report the error")
|
||||||
logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` +
|
logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` +
|
||||||
|
|
|
@ -145,7 +145,7 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||||
def __init__(self, file_, jailName, initOpts, **kwargs):
|
def __init__(self, file_, jailName, initOpts, **kwargs):
|
||||||
ConfigReader.__init__(self, **kwargs)
|
ConfigReader.__init__(self, **kwargs)
|
||||||
self._file = file_
|
self._file = file_
|
||||||
self._name = jailName
|
self._jailName = jailName
|
||||||
self._initOpts = initOpts
|
self._initOpts = initOpts
|
||||||
|
|
||||||
def setFile(self, fileName):
|
def setFile(self, fileName):
|
||||||
|
@ -154,11 +154,11 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||||
def getFile(self):
|
def getFile(self):
|
||||||
return self.__file
|
return self.__file
|
||||||
|
|
||||||
def setName(self, name):
|
def setJailName(self, jailName):
|
||||||
self._name = name
|
self._jailName = jailName
|
||||||
|
|
||||||
def getName(self):
|
def getJailName(self):
|
||||||
return self._name
|
return self._jailName
|
||||||
|
|
||||||
def read(self):
|
def read(self):
|
||||||
return ConfigReader.read(self, self._file)
|
return ConfigReader.read(self, self._file)
|
||||||
|
|
|
@ -50,14 +50,14 @@ class FilterReader(DefinitionInitConfigReader):
|
||||||
for regex in self._opts[opt].split('\n'):
|
for regex in self._opts[opt].split('\n'):
|
||||||
# Do not send a command if the rule is empty.
|
# Do not send a command if the rule is empty.
|
||||||
if regex != '':
|
if regex != '':
|
||||||
stream.append(["set", self._name, "addfailregex", regex])
|
stream.append(["set", self._jailName, "addfailregex", regex])
|
||||||
elif opt == "ignoreregex":
|
elif opt == "ignoreregex":
|
||||||
for regex in self._opts[opt].split('\n'):
|
for regex in self._opts[opt].split('\n'):
|
||||||
# Do not send a command if the rule is empty.
|
# Do not send a command if the rule is empty.
|
||||||
if regex != '':
|
if regex != '':
|
||||||
stream.append(["set", self._name, "addignoreregex", regex])
|
stream.append(["set", self._jailName, "addignoreregex", regex])
|
||||||
if self._initOpts:
|
if self._initOpts:
|
||||||
if 'maxlines' in self._initOpts:
|
if 'maxlines' in self._initOpts:
|
||||||
stream.append(["set", self._name, "maxlines", self._initOpts["maxlines"]])
|
stream.append(["set", self._jailName, "maxlines", self._initOpts["maxlines"]])
|
||||||
return stream
|
return stream
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,7 @@ protocol = [
|
||||||
["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"],
|
["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"],
|
||||||
["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"],
|
["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"],
|
||||||
["get <JAIL> addaction", "gets the last action which has been added for <JAIL>"],
|
["get <JAIL> addaction", "gets the last action which has been added for <JAIL>"],
|
||||||
|
["get <JAIL> actions", "gets a list of actions for <JAIL>"],
|
||||||
["get <JAIL> actionstart <ACT>", "gets the start command for the action <ACT> for <JAIL>"],
|
["get <JAIL> actionstart <ACT>", "gets the start command for the action <ACT> for <JAIL>"],
|
||||||
["get <JAIL> actionstop <ACT>", "gets the stop command for the action <ACT> for <JAIL>"],
|
["get <JAIL> actionstop <ACT>", "gets the stop command for the action <ACT> for <JAIL>"],
|
||||||
["get <JAIL> actioncheck <ACT>", "gets the check command for the action <ACT> for <JAIL>"],
|
["get <JAIL> actioncheck <ACT>", "gets the check command for the action <ACT> for <JAIL>"],
|
||||||
|
|
|
@ -65,6 +65,9 @@ class Actions(JailThread):
|
||||||
# @param name The action name
|
# @param name The action name
|
||||||
|
|
||||||
def addAction(self, name):
|
def addAction(self, name):
|
||||||
|
# Check is action name already exists
|
||||||
|
if name in [action.getName() for action in self.__actions]:
|
||||||
|
raise ValueError("Action %s already exists" % name)
|
||||||
action = Action(name)
|
action = Action(name)
|
||||||
self.__actions.append(action)
|
self.__actions.append(action)
|
||||||
|
|
||||||
|
@ -104,6 +107,14 @@ class Actions(JailThread):
|
||||||
self.__actions.append(action)
|
self.__actions.append(action)
|
||||||
return action
|
return action
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the list of actions
|
||||||
|
#
|
||||||
|
# @return list of actions
|
||||||
|
|
||||||
|
def getActions(self):
|
||||||
|
return self.__actions
|
||||||
|
|
||||||
##
|
##
|
||||||
# Set the ban time.
|
# Set the ban time.
|
||||||
#
|
#
|
||||||
|
|
|
@ -236,6 +236,9 @@ class Server:
|
||||||
def getLastAction(self, name):
|
def getLastAction(self, name):
|
||||||
return self.__jails.getAction(name).getLastAction()
|
return self.__jails.getAction(name).getLastAction()
|
||||||
|
|
||||||
|
def getActions(self, name):
|
||||||
|
return self.__jails.getAction(name).getActions()
|
||||||
|
|
||||||
def delAction(self, name, value):
|
def delAction(self, name, value):
|
||||||
self.__jails.getAction(name).delAction(value)
|
self.__jails.getAction(name).delAction(value)
|
||||||
|
|
||||||
|
|
|
@ -265,6 +265,8 @@ class Transmitter:
|
||||||
# Action
|
# Action
|
||||||
elif command[1] == "bantime":
|
elif command[1] == "bantime":
|
||||||
return self.__server.getBanTime(name)
|
return self.__server.getBanTime(name)
|
||||||
|
elif command[1] == "actions":
|
||||||
|
return self.__server.getActions(name)
|
||||||
elif command[1] == "addaction":
|
elif command[1] == "addaction":
|
||||||
return self.__server.getLastAction(name).getName()
|
return self.__server.getLastAction(name).getName()
|
||||||
elif command[1] == "actionstart":
|
elif command[1] == "actionstart":
|
||||||
|
|
|
@ -302,3 +302,28 @@ class JailsReaderTest(unittest.TestCase):
|
||||||
configurator._Configurator__jails.setBaseDir('/tmp')
|
configurator._Configurator__jails.setBaseDir('/tmp')
|
||||||
self.assertEqual(configurator._Configurator__jails.getBaseDir(), '/tmp')
|
self.assertEqual(configurator._Configurator__jails.getBaseDir(), '/tmp')
|
||||||
self.assertEqual(configurator.getBaseDir(), CONFIG_DIR)
|
self.assertEqual(configurator.getBaseDir(), CONFIG_DIR)
|
||||||
|
|
||||||
|
def testMultipleSameAction(self):
|
||||||
|
basedir = tempfile.mkdtemp("fail2ban_conf")
|
||||||
|
os.mkdir(os.path.join(basedir, "filter.d"))
|
||||||
|
os.mkdir(os.path.join(basedir, "action.d"))
|
||||||
|
open(os.path.join(basedir, "action.d", "testaction1.conf"), 'w').close()
|
||||||
|
open(os.path.join(basedir, "filter.d", "testfilter1.conf"), 'w').close()
|
||||||
|
jailfd = open(os.path.join(basedir, "jail.conf"), 'w')
|
||||||
|
jailfd.write("""
|
||||||
|
[testjail1]
|
||||||
|
action = testaction1[actname=test1]
|
||||||
|
testaction1[actname=test2]
|
||||||
|
filter = testfilter1
|
||||||
|
""")
|
||||||
|
jailfd.close()
|
||||||
|
jails = JailsReader(basedir=basedir)
|
||||||
|
self.assertTrue(jails.read())
|
||||||
|
self.assertTrue(jails.getOptions())
|
||||||
|
comm_commands = jails.convert()
|
||||||
|
|
||||||
|
action_names = [comm[-1] for comm in comm_commands if comm[:3] == ['set', 'testjail1', 'addaction']]
|
||||||
|
|
||||||
|
self.assertNotEqual(len(set(action_names)), 1)
|
||||||
|
|
||||||
|
shutil.rmtree(basedir)
|
||||||
|
|
|
@ -440,8 +440,12 @@ class Transmitter(TransmitterBase):
|
||||||
self.transm.proceed(["set", self.jailName, "addaction", action]),
|
self.transm.proceed(["set", self.jailName, "addaction", action]),
|
||||||
(0, action))
|
(0, action))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(["get", self.jailName, "addaction", action]),
|
self.transm.proceed(["get", self.jailName, "addaction"]),
|
||||||
(0, action))
|
(0, action))
|
||||||
|
self.assertEqual(
|
||||||
|
self.transm.proceed(
|
||||||
|
["get", self.jailName, "actions"])[1][0].getName(),
|
||||||
|
action)
|
||||||
for cmd, value in zip(cmdList, cmdValueList):
|
for cmd, value in zip(cmdList, cmdValueList):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
|
|
|
@ -59,7 +59,15 @@ The following options are applicable to all jails. Their meaning is described in
|
||||||
\fBbackend\fR
|
\fBbackend\fR
|
||||||
.TP
|
.TP
|
||||||
\fBusedns\fR
|
\fBusedns\fR
|
||||||
|
.PP
|
||||||
|
Each jail can be configured with only a single filter, but may have multiple actions. By default, the name of a action is the action filename. In the case where multiple of the same action are to be used, the \fBactname\fR option can be assigned to the action to avoid duplicatione.g.:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
[ssh-iptables-ipset]
|
||||||
|
enabled = true
|
||||||
|
action = sendmail[name=ssh, dest=john@example.com, actname=mail-john]
|
||||||
|
sendmail[name=ssh, dest=paul@example.com, actname=mail-paul]
|
||||||
|
.fi
|
||||||
|
|
||||||
.SH "ACTION FILES"
|
.SH "ACTION FILES"
|
||||||
Action files specify which commands are executed to ban and unban an IP address. They are located under \fI/etc/fail2ban/action.d\fR.
|
Action files specify which commands are executed to ban and unban an IP address. They are located under \fI/etc/fail2ban/action.d\fR.
|
||||||
|
|
Loading…
Reference in New Issue