Merge pull request #949 from leeclemens/enh/configSyslogSocket

Configure Syslog Socket Path (closes #814)
pull/952/head
Yaroslav Halchenko 2015-02-06 20:08:15 -05:00
commit 3fb2becddb
9 changed files with 91 additions and 9 deletions

View File

@ -70,6 +70,7 @@ ver. 0.9.2 (2014/XX/XXX) - wanna-be-released
timezone not UTC time/zone. Closes gh-911 timezone not UTC time/zone. Closes gh-911
* Conditionally log Ignore IP with reason (dns, ip, command). Closes gh-916 * Conditionally log Ignore IP with reason (dns, ip, command). Closes gh-916
* Absorbed DNSUtils.cidr into addr2bin in filter.py, added unittests * Absorbed DNSUtils.cidr into addr2bin in filter.py, added unittests
* Added syslogsocket configuration to fail2ban.conf
ver. 0.9.1 (2014/10/29) - better, faster, stronger ver. 0.9.1 (2014/10/29) - better, faster, stronger

View File

@ -34,6 +34,12 @@ loglevel = INFO
# #
logtarget = /var/log/fail2ban.log logtarget = /var/log/fail2ban.log
# Option: syslogsocket
# Notes: Set the syslog socket file. Only used when logtarget is SYSLOG
# auto uses platform.system() to determine predefined paths
# Values: [ auto | FILE ] Default: auto
syslogsocket = auto
# Option: socket # Option: socket
# Notes.: Set the socket file. This is used to communicate with the daemon. Do # Notes.: Set the socket file. This is used to communicate with the daemon. Do
# not remove this file when Fail2ban runs. It will not be possible to # not remove this file when Fail2ban runs. It will not be possible to

View File

@ -85,6 +85,9 @@ class Beautifier:
val = " ".join(res1[1]) if isinstance(res1[1], list) else res1[1] val = " ".join(res1[1]) if isinstance(res1[1], list) else res1[1]
msg.append("%s %s:\t%s" % (prefix1, res1[0], val)) msg.append("%s %s:\t%s" % (prefix1, res1[0], val))
msg = "\n".join(msg) msg = "\n".join(msg)
elif inC[1] == "syslogsocket":
msg = "Current syslog socket is:\n"
msg = msg + "`- " + response
elif inC[1] == "logtarget": elif inC[1] == "logtarget":
msg = "Current logging target is:\n" msg = "Current logging target is:\n"
msg = msg + "`- " + response msg = msg + "`- " + response

View File

@ -46,6 +46,7 @@ class Fail2banReader(ConfigReader):
def getOptions(self): def getOptions(self):
opts = [["string", "loglevel", "INFO" ], opts = [["string", "loglevel", "INFO" ],
["string", "logtarget", "STDERR"], ["string", "logtarget", "STDERR"],
["string", "syslogsocket", "auto"],
["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"], ["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"],
["int", "dbpurgeage", 86400]] ["int", "dbpurgeage", 86400]]
self.__opts = ConfigReader.getOptions(self, "Definition", opts) self.__opts = ConfigReader.getOptions(self, "Definition", opts)
@ -57,6 +58,8 @@ class Fail2banReader(ConfigReader):
stream.append(["set", "loglevel", self.__opts[opt]]) stream.append(["set", "loglevel", self.__opts[opt]])
elif opt == "logtarget": elif opt == "logtarget":
stream.append(["set", "logtarget", self.__opts[opt]]) stream.append(["set", "logtarget", self.__opts[opt]])
elif opt == "syslogsocket":
stream.append(["set", "syslogsocket", self.__opts[opt]])
elif opt == "dbfile": elif opt == "dbfile":
stream.append(["set", "dbfile", self.__opts[opt]]) stream.append(["set", "dbfile", self.__opts[opt]])
elif opt == "dbpurgeage": elif opt == "dbpurgeage":

View File

@ -44,6 +44,8 @@ protocol = [
["get loglevel", "gets the logging level"], ["get loglevel", "gets the logging level"],
["set logtarget <TARGET>", "sets logging target to <TARGET>. Can be STDOUT, STDERR, SYSLOG or a file"], ["set logtarget <TARGET>", "sets logging target to <TARGET>. Can be STDOUT, STDERR, SYSLOG or a file"],
["get logtarget", "gets logging target"], ["get logtarget", "gets logging target"],
["set syslogsocket auto|<SOCKET>", "sets the syslog socket path to auto or <SOCKET>. Only used if logtarget is SYSLOG"],
["get syslogsocket", "gets syslog socket path"],
["flushlogs", "flushes the logtarget if a file and reopens it. For log rotation."], ["flushlogs", "flushes the logtarget if a file and reopens it. For log rotation."],
['', "DATABASE", ""], ['', "DATABASE", ""],
["set dbfile <FILE>", "set the location of fail2ban persistent datastore. Set to \"None\" to disable"], ["set dbfile <FILE>", "set the location of fail2ban persistent datastore. Set to \"None\" to disable"],

View File

@ -25,7 +25,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
from threading import Lock, RLock from threading import Lock, RLock
import logging, logging.handlers, sys, os, signal import logging, logging.handlers, sys, os, signal, stat
from .jails import Jails from .jails import Jails
from .filter import FileFilter, JournalFilter from .filter import FileFilter, JournalFilter
@ -55,10 +55,18 @@ class Server:
self.__asyncServer = AsyncServer(self.__transm) self.__asyncServer = AsyncServer(self.__transm)
self.__logLevel = None self.__logLevel = None
self.__logTarget = None self.__logTarget = None
self.__syslogSocket = None
self.__autoSyslogSocketPaths = {
'Darwin': '/var/run/syslog',
'FreeBSD': '/var/run/log',
'Linux': '/dev/log',
}
# Set logging level # Set logging level
self.setLogLevel("INFO") self.setLogLevel("INFO")
self.setLogTarget("STDOUT") self.setLogTarget("STDOUT")
self.setSyslogSocket("auto")
def __sigTERMhandler(self, signum, frame): def __sigTERMhandler(self, signum, frame):
logSys.debug("Caught signal %d. Exiting" % signum) logSys.debug("Caught signal %d. Exiting" % signum)
self.quit() self.quit()
@ -360,7 +368,7 @@ class Server:
return self.__logLevel return self.__logLevel
finally: finally:
self.__loggingLock.release() self.__loggingLock.release()
## ##
# Sets the logging target. # Sets the logging target.
# #
@ -376,7 +384,21 @@ class Server:
# Syslog daemons already add date to the message. # Syslog daemons already add date to the message.
formatter = logging.Formatter("%(name)s[%(process)d]: %(levelname)s %(message)s") formatter = logging.Formatter("%(name)s[%(process)d]: %(levelname)s %(message)s")
facility = logging.handlers.SysLogHandler.LOG_DAEMON facility = logging.handlers.SysLogHandler.LOG_DAEMON
hdlr = logging.handlers.SysLogHandler("/dev/log", facility=facility) if self.__syslogSocket == "auto":
import platform
self.__syslogSocket = self.__autoSyslogSocketPaths.get(
platform.system())
if self.__syslogSocket is not None\
and os.path.exists(self.__syslogSocket)\
and stat.S_ISSOCK(os.stat(
self.__syslogSocket).st_mode):
hdlr = logging.handlers.SysLogHandler(
self.__syslogSocket, facility=facility)
else:
logSys.error(
"Syslog socket file: %s does not exists"
" or is not a socket" % self.__syslogSocket)
return False
elif target == "STDOUT": elif target == "STDOUT":
hdlr = logging.StreamHandler(sys.stdout) hdlr = logging.StreamHandler(sys.stdout)
elif target == "STDERR": elif target == "STDERR":
@ -412,21 +434,44 @@ class Server:
logger.addHandler(hdlr) logger.addHandler(hdlr)
# Does not display this message at startup. # Does not display this message at startup.
if not self.__logTarget is None: if not self.__logTarget is None:
logSys.info("Changed logging target to %s for Fail2ban v%s" % logSys.info(
(target, version.version)) "Changed logging target to %s for Fail2ban v%s"
% ((target
if target != "SYSLOG"
else "%s (%s)"
% (target, self.__syslogSocket)),
version.version))
# Sets the logging target. # Sets the logging target.
self.__logTarget = target self.__logTarget = target
return True return True
finally: finally:
self.__loggingLock.release() self.__loggingLock.release()
##
# Sets the syslog socket.
#
# syslogsocket is the full path to the syslog socket
# @param syslogsocket the syslog socket path
def setSyslogSocket(self, syslogsocket):
self.__syslogSocket = syslogsocket
# Conditionally reload, logtarget depends on socket path when SYSLOG
return self.__logTarget != "SYSLOG"\
or self.setLogTarget(self.__logTarget)
def getLogTarget(self): def getLogTarget(self):
try: try:
self.__loggingLock.acquire() self.__loggingLock.acquire()
return self.__logTarget return self.__logTarget
finally: finally:
self.__loggingLock.release() self.__loggingLock.release()
def getSyslogSocket(self):
try:
self.__loggingLock.acquire()
return self.__syslogSocket
finally:
self.__loggingLock.release()
def flushLogs(self): def flushLogs(self):
if self.__logTarget not in ['STDERR', 'STDOUT', 'SYSLOG']: if self.__logTarget not in ['STDERR', 'STDOUT', 'SYSLOG']:
for handler in getLogger("fail2ban").handlers: for handler in getLogger("fail2ban").handlers:

View File

@ -121,6 +121,12 @@ class Transmitter:
return self.__server.getLogTarget() return self.__server.getLogTarget()
else: else:
raise Exception("Failed to change log target") raise Exception("Failed to change log target")
elif name == "syslogsocket":
value = command[1]
if self.__server.setSyslogSocket(value):
return self.__server.getSyslogSocket()
else:
raise Exception("Failed to change syslog socket")
#Database #Database
elif name == "dbfile": elif name == "dbfile":
self.__server.setDatabase(command[1]) self.__server.setDatabase(command[1])
@ -264,6 +270,8 @@ class Transmitter:
return self.__server.getLogLevel() return self.__server.getLogLevel()
elif name == "logtarget": elif name == "logtarget":
return self.__server.getLogTarget() return self.__server.getLogTarget()
elif name == "syslogsocket":
return self.__server.getSyslogSocket()
#Database #Database
elif name == "dbfile": elif name == "dbfile":
db = self.__server.getDatabase() db = self.__server.getDatabase()

View File

@ -623,7 +623,8 @@ class JailsReaderTest(LogCaptureTestCase):
'/var/lib/fail2ban/fail2ban.sqlite3'], '/var/lib/fail2ban/fail2ban.sqlite3'],
['set', 'dbpurgeage', 86400], ['set', 'dbpurgeage', 86400],
['set', 'loglevel', "INFO"], ['set', 'loglevel', "INFO"],
['set', 'logtarget', '/var/log/fail2ban.log']]) ['set', 'logtarget', '/var/log/fail2ban.log'],
['set', 'syslogsocket', 'auto']])
# and if we force change configurator's fail2ban's baseDir # and if we force change configurator's fail2ban's baseDir
# there should be an error message (test visually ;) -- # there should be an error message (test visually ;) --

View File

@ -739,6 +739,7 @@ class TransmitterLogging(TransmitterBase):
self.server = Server() self.server = Server()
self.server.setLogTarget("/dev/null") self.server.setLogTarget("/dev/null")
self.server.setLogLevel("CRITICAL") self.server.setLogLevel("CRITICAL")
self.server.setSyslogSocket("auto")
super(TransmitterLogging, self).setUp() super(TransmitterLogging, self).setUp()
def testLogTarget(self): def testLogTarget(self):
@ -766,6 +767,18 @@ class TransmitterLogging(TransmitterBase):
raise unittest.SkipTest("'/dev/log' not present") raise unittest.SkipTest("'/dev/log' not present")
elif not os.path.exists("/dev/log"): elif not os.path.exists("/dev/log"):
return return
self.assertTrue(self.server.getSyslogSocket(), "auto")
self.setGetTest("logtarget", "SYSLOG")
self.assertTrue(self.server.getSyslogSocket(), "/dev/log")
def testSyslogSocket(self):
self.setGetTest("syslogsocket", "/dev/log/NEW/PATH")
def testSyslogSocketNOK(self):
self.setGetTest("syslogsocket", "/this/path/should/not/exist")
self.setGetTestNOK("logtarget", "SYSLOG")
# set back for other tests
self.setGetTest("syslogsocket", "/dev/log")
self.setGetTest("logtarget", "SYSLOG") self.setGetTest("logtarget", "SYSLOG")
def testLogLevel(self): def testLogLevel(self):