summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Sommerseth <dazo@users.sourceforge.net>2013-12-26 21:55:07 +0100
committerDavid Sommerseth <dazo@users.sourceforge.net>2013-12-26 21:55:07 +0100
commita6dd81ccb0c0bf2f0e3172b15b19274433df2d53 (patch)
treed63cf7736ac91edc02933e07d8b788e59278c6ff
parent3b96eeb9a932b55c9ef13f5a9649c30bde86ac14 (diff)
downloadlogactio-a6dd81ccb0c0bf2f0e3172b15b19274433df2d53.tar.gz
logactio-a6dd81ccb0c0bf2f0e3172b15b19274433df2d53.tar.xz
logactio-a6dd81ccb0c0bf2f0e3172b15b19274433df2d53.zip
Added another "reporter" module - IPTipset
This requires currently logactio to run as root. On matches, instead of reporting the match it will use the IP address extrated via the regex and add it to an ipset(8) set (hash:ip). This set can then be used in other iptables rules to f.ex block failing attempts. Signed-off-by: David Sommerseth <dazo@users.sourceforge.net>
-rw-r--r--LogActio/Reporters/IPTipset.py288
1 files changed, 288 insertions, 0 deletions
diff --git a/LogActio/Reporters/IPTipset.py b/LogActio/Reporters/IPTipset.py
new file mode 100644
index 0000000..3e47179
--- /dev/null
+++ b/LogActio/Reporters/IPTipset.py
@@ -0,0 +1,288 @@
+#
+# logactio - simple framework for doing configured action on certain
+# log file events
+#
+# Copyright 2013 David Sommerseth <dazo@users.sourceforge.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# For the avoidance of doubt the "preferred form" of this code is one which
+# is in an open unpatent encumbered format. Where cryptographic key signing
+# forms part of the process of creating an executable the information
+# including keys needed to generate an equivalently functional executable
+# are deemed to be part of the source code.
+#
+
+import sys, os, subprocess, tempfile, re
+import LogActio.Message, LogActio.ReporterQueue
+
+
+class IPTipset(LogActio.ReporterQueue.ReporterQueue):
+ """LogActio reporter modules which adds IP addresses to an iptalbes IP set chain
+
+ Example configuration to be used in /etc/logactio.cfg
+
+ [Reporter:QPID]
+ module: IPTipset
+ ipset-name: BlockList
+ ipset-create: True
+ ipset-hashsize: 2048
+ ipset-timeout: 3600
+ ipset-counters: True
+ iptables-chains: INPUT,OUTPUT,FORWARD
+ iptables-insert-points: INPUT:2,FORWARD:4
+ iptables-jump: DROP
+
+ ipset-name contains the reference string used when calling 'ipset'. This
+ field is mandatory.
+
+ If ipset-create is True, true or 1, it will attempt to create this ipset set
+ when starting up. In this case, the ipset-hashsize will be used, if set. See
+ the ipset(8) man page for more informaion. The ipset set created will be
+ of the 'hash:ip' type. The ipset-create is optional and defaults to False.
+ If the ipset set does not exist and ipset-create is not enabled, it will fail
+ when testing the ipset set at startup.
+
+ The ipset-timeout parameter adds the a default timeout for all added entries,
+ see the ipset(8) man page for more information about this feature.
+
+ The ipset-counters parameter adds a counter field for each added entry. This
+ must be True, true or 1 to be considered set. Otherwise it is disabled. See
+ the ipset(8) man page for more information about this feature too.
+
+ If iptables-chains is set, it will insert an iptables rule which checks the
+ ipset set. If iptables-insert-point is set (optinal), the rule will be inserted
+ at the given point, otherwise it will be inserted at the top. The iptables-jump
+ is mandatory with iptables-chains and adds the jump destiation when a match
+ against the ipset set is found.
+
+ The example config above will result in these commands being executed when
+ starting:
+
+ # ipset --exist create BlockList hash:ipset hashsize 2048 timeout 3600
+ # iptables -I INPUT 2 -m set --match-set BlockList src -j DROP
+ # iptables -I OUTPUT -m set --match-set BlockList src -j DROP
+ # iptables -I FORWARD 4 -m set --match-set BlockList src -j DROP
+ """
+
+ def __init__(self, config, logger = None):
+
+ # Configuration parsing
+ self.__create = False
+ self.__hashsize = "1024"
+ self.__timeout = 0
+ self.__counters = False
+ self.__iptchains = False
+ self.__iptchainsjump = False
+ self.__iptchaininserts = False
+
+ if not config.has_key("ipset-name"):
+ raise Exception("IPTipset is missing in ipset name")
+ else:
+ self.__ipsetname = config["ipset-name"]
+
+ if config.has_key("ipset-create"):
+ create = config["ipset-create"].lower()
+ self.__create = (create == "true" or create == "1") and True or False
+ if self.__create and config.has_key("ipset-hashsize"):
+ self.__hashsize = str(config["ipset-hashsize"])
+ if self.__create and config.has_key("ipset-timeout"):
+ self.__timeout = str(config["ipset-timeout"])
+ if self.__create and config.has_key("ipset-counters"):
+ counters = config["ipset-counters"].lower()
+ self.__counters = (counters == "true" or counters == "1") and True or False
+
+ if config.has_key("iptables-chains"):
+ self.__iptchains = [v.strip() for v in config["iptables-chains"].split(",")]
+ if not config.has_key("iptables-jump"):
+ raise Exception("IPTipset needs the iptables-jump variable when iptables-chains is set")
+ self.__iptchainsjump = config["iptables-jump"]
+ if config.has_key("iptables-insert-points"):
+ self.__iptchaininserts = {}
+ for inspoint in config["iptables-insert-points"].split(","):
+ (chain, point) = inspoint.split(":")
+ self.__iptchaininserts[chain.strip()] = str(point)
+
+ # Prepare this object, ipset and iptables
+ self.__log = logger and logger or self.__logfnc
+ if self.__create:
+ self.__prepare_ipset()
+ if self.__iptchains:
+ self.__prepare_iptables()
+
+ # Register this module as a reporter module
+ LogActio.ReporterQueue.ReporterQueue.__init__(self,
+ "IPTipset",
+ "IPTables IPset processor",
+ self.__processqueue)
+
+ def __logfnc(self, lvl, msg):
+ print "%s" % msg
+ sys.stdout.flush()
+
+
+ def __parse_cmd_log(self, cmd, logfp):
+ logfp.seek(0)
+ for line in logfp:
+ self.__log(2, "[IPTipset] %s: %s" % (cmd, line))
+
+
+ def __call_ipset(self, mode, args):
+ if mode == "create":
+ args = ["ipset", "--exist", "create", self.__ipsetname, "hash:ip"] + args
+ else:
+ args = ["ipset", mode, self.__ipsetname, args]
+
+ nullfp = os.open("/dev/null", os.O_RDWR)
+ tmplog = tempfile.SpooledTemporaryFile(mode="rw+b")
+ self.__log(4, "[IPTipset] Executing: %s" % " ".join(args))
+ cmd = subprocess.Popen(args, stdin=nullfp, stdout=tmplog, stderr=tmplog)
+ cmd.wait()
+ self.__parse_cmd_log("ipset:%s" % mode, tmplog)
+
+ # Clean up
+ tmplog.close()
+ del tmplog
+ os.close(nullfp);
+
+
+ def __prepare_ipset(self):
+ params = []
+ params += self.__hashsize and ["hashsize", self.__hashsize] or []
+ params += self.__timeout and ["timeout", self.__timeout] or []
+ params += self.__counters and ["counters"] or []
+ self.__call_ipset("create", params)
+
+
+ def __parse_already_registered(self):
+ args = ["ipset", "save", self.__ipsetname]
+ nullfp = os.open("/dev/null", os.O_RDWR)
+ tmplog = tempfile.SpooledTemporaryFile(mode="rw+b")
+ self.__log(4, "[IPTipset] Executing: %s" % " ".join(args))
+ cmd = subprocess.Popen(args, stdin=nullfp, stdout=tmplog, stderr=tmplog)
+ cmd.wait()
+
+ # Process all "add" lines which matches our ipset set name
+ tmplog.seek(0)
+ rg = re.compile("^add (.*) \b((?:[0-9]{1,3}\.){3}[0-9]{1,3})\b")
+ retlist = []
+ for line in tmplog:
+ m = rg.match(line.strip())
+ if m:
+ rgm = m.groups()
+ if rgm[0] == self.__ipsetname:
+ retlist.append(rgm[1])
+ tmplog.close()
+ del tmplog
+ os.close(nullfp)
+ del nullfp
+ return retlist
+
+
+ def __prepare_iptables(self):
+ nullfp = os.open("/dev/null", os.O_RDWR)
+
+ for chain in self.__iptchains:
+ # Prepare iptables command line
+ args = False
+ if self.__iptchaininserts and self.__iptchaininserts.has_key(chain):
+ args = ["iptables", "-I", chain, self.__iptchaininserts[chain],
+ "-m", "set", "--match-set", self.__ipsetname,
+ "-j", self.__iptchainsjump]
+ else:
+ args = ["iptables", "-I", chain,
+ "-m", "set", "--match-set", self.__ipsetname, "src",
+ "-j", self.__iptchainsjump]
+
+ # Call iptables and wait for it to complete and log the output
+ tmplog = tempfile.SpooledTemporaryFile(mode="rw+b")
+ self.__log(4, "[IPTipset] Executing: %s" % " ".join(args))
+ cmd = subprocess.Popen(args, stdin=nullfp, stdout=tmplog, stderr=tmplog)
+ cmd.wait()
+ self.__parse_cmd_log("iptables:%s" % chain, tmplog)
+ tmplog.close()
+ del tmplog
+
+ # Clean up
+ os.close(nullfp)
+ del nullfp
+
+
+ def __cleanup_iptables(self):
+ nullfp = os.open("/dev/null", os.O_RDWR)
+
+ for chain in self.__iptchains:
+ # Prepare iptables command line
+ args = ["iptables", "-D", chain,
+ "-m", "set", "--match-set", self.__ipsetname, "src",
+ "-j", self.__iptchainsjump]
+
+ # Call iptables and wait for it to complete and log the output
+ tmplog = tempfile.SpooledTemporaryFile(mode="rw+b")
+ self.__log(4, "[IPTipset] Executing: %s" % " ".join(args))
+ cmd = subprocess.Popen(args, stdin=nullfp, stdout=tmplog, stderr=tmplog)
+ cmd.wait()
+ self.__parse_cmd_log("iptables:%s" % chain, tmplog)
+ tmplog.close()
+ del tmplog
+
+ # Clean up
+ os.close(nullfp)
+ del nullfp
+
+
+ def __processqueue(self):
+ self.__log(1, "[IPTipset] Ready.")
+ registered = self.__parse_already_registered()
+
+ # Process the internal message queue
+ done = False
+ while not done:
+ msg = self._QueueGet()
+
+ if( msg.MessageType() == LogActio.Message.MSG_SHUTDOWN ):
+ # Prepare for shutdown
+ done = True
+
+ elif( msg.MessageType() == LogActio.Message.MSG_SEND ):
+ m = msg.Message()
+
+ try:
+ registered.index(m["ipaddress"])
+ except ValueError:
+ self.__log(2, "[IPTipset] {Rule %s} Adding IP address %s to ipset '%s' based on entry in log file '%s' with the threshold %i after %i hits" %
+ (m["rulename"], m["ipaddress"], self.__ipsetname, m["logfile"], m["threshold"], m["count"]))
+ self.__call_ipset("add", m["ipaddress"])
+ registered.append(m["ipaddress"])
+
+
+ # self.__cleanup_iptables() # Not working - not getting a match ... iptables bug?
+
+ self.__log(3, "[IPTipset] Module shut down")
+
+
+ def ProcessEvent(self, logfile, rulename, msg, count, threshold):
+ # FIXME: Ensure the IP address is infact an IP address (regex check)
+
+ # Format the report message
+ msg = {"rulename": rulename, "threshold": threshold,
+ "ipaddress": msg, "logfile": logfile,
+ "count": count}
+
+ # Queue the message for sending
+ self._QueueMsg(0, msg)
+
+
+def InitReporter(config, logger = None):
+ return IPTipset(config, logger)