mirror of https://github.com/fail2ban/fail2ban
introduced new option `-t` or `--test` to test configuration resp. start server only if configuration is clean (not skip wrong configured jails if option `-t` specified);
parent
3e9852d4d2
commit
fdac44ca58
|
@ -72,9 +72,9 @@ class Configurator:
|
||||||
def getEarlyOptions(self):
|
def getEarlyOptions(self):
|
||||||
return self.__fail2ban.getEarlyOptions()
|
return self.__fail2ban.getEarlyOptions()
|
||||||
|
|
||||||
def getOptions(self, jail=None, updateMainOpt=None):
|
def getOptions(self, jail=None, updateMainOpt=None, ignoreWrong=True):
|
||||||
self.__fail2ban.getOptions(updateMainOpt)
|
self.__fail2ban.getOptions(updateMainOpt)
|
||||||
return self.__jails.getOptions(jail)
|
return self.__jails.getOptions(jail, ignoreWrong=ignoreWrong)
|
||||||
|
|
||||||
def convertToProtocol(self):
|
def convertToProtocol(self):
|
||||||
self.__streams["general"] = self.__fail2ban.convert()
|
self.__streams["general"] = self.__fail2ban.convert()
|
||||||
|
|
|
@ -47,6 +47,7 @@ class Fail2banCmdLine():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._argv = self._args = None
|
self._argv = self._args = None
|
||||||
self._configurator = None
|
self._configurator = None
|
||||||
|
self.cleanConfOnly = False
|
||||||
self.resetConf()
|
self.resetConf()
|
||||||
|
|
||||||
def resetConf(self):
|
def resetConf(self):
|
||||||
|
@ -101,6 +102,7 @@ class Fail2banCmdLine():
|
||||||
output(" --logtarget <FILE>|STDOUT|STDERR|SYSLOG")
|
output(" --logtarget <FILE>|STDOUT|STDERR|SYSLOG")
|
||||||
output(" --syslogsocket auto|<FILE>")
|
output(" --syslogsocket auto|<FILE>")
|
||||||
output(" -d dump configuration. For debugging")
|
output(" -d dump configuration. For debugging")
|
||||||
|
output(" -t, --test test configuration (can be also specified with start parameters)")
|
||||||
output(" -i interactive mode")
|
output(" -i interactive mode")
|
||||||
output(" -v increase verbosity")
|
output(" -v increase verbosity")
|
||||||
output(" -q decrease verbosity")
|
output(" -q decrease verbosity")
|
||||||
|
@ -136,6 +138,9 @@ class Fail2banCmdLine():
|
||||||
self._conf[ o[2:] ] = opt[1]
|
self._conf[ o[2:] ] = opt[1]
|
||||||
elif o == "-d":
|
elif o == "-d":
|
||||||
self._conf["dump"] = True
|
self._conf["dump"] = True
|
||||||
|
elif o == "-t" or o == "--test":
|
||||||
|
self.cleanConfOnly = True
|
||||||
|
self._conf["test"] = True
|
||||||
elif o == "-v":
|
elif o == "-v":
|
||||||
self._conf["verbose"] += 1
|
self._conf["verbose"] += 1
|
||||||
elif o == "-q":
|
elif o == "-q":
|
||||||
|
@ -173,8 +178,8 @@ class Fail2banCmdLine():
|
||||||
|
|
||||||
# Reads the command line options.
|
# Reads the command line options.
|
||||||
try:
|
try:
|
||||||
cmdOpts = 'hc:s:p:xfbdviqV'
|
cmdOpts = 'hc:s:p:xfbdtviqV'
|
||||||
cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'async', 'timeout=', 'help', 'version']
|
cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'test', 'async', 'timeout=', 'help', 'version']
|
||||||
optList, self._args = getopt.getopt(self._argv[1:], cmdOpts, cmdLongOpts)
|
optList, self._args = getopt.getopt(self._argv[1:], cmdOpts, cmdLongOpts)
|
||||||
except getopt.GetoptError:
|
except getopt.GetoptError:
|
||||||
self.dispUsage()
|
self.dispUsage()
|
||||||
|
@ -225,13 +230,30 @@ class Fail2banCmdLine():
|
||||||
logSys.info("Using pid file %s, [%s] logging to %s",
|
logSys.info("Using pid file %s, [%s] logging to %s",
|
||||||
self._conf["pidfile"], logging.getLevelName(llev), self._conf["logtarget"])
|
self._conf["pidfile"], logging.getLevelName(llev), self._conf["logtarget"])
|
||||||
|
|
||||||
|
readcfg = True
|
||||||
if self._conf.get("dump", False):
|
if self._conf.get("dump", False):
|
||||||
ret, stream = self.readConfig()
|
if readcfg:
|
||||||
|
ret, stream = self.readConfig()
|
||||||
|
readcfg = False
|
||||||
self.dumpConfig(stream)
|
self.dumpConfig(stream)
|
||||||
return ret
|
if not self._conf.get("test", False):
|
||||||
|
return ret
|
||||||
|
|
||||||
|
if self._conf.get("test", False):
|
||||||
|
if readcfg:
|
||||||
|
readcfg = False
|
||||||
|
ret, stream = self.readConfig()
|
||||||
|
if not ret:
|
||||||
|
raise ServerExecutionException("ERROR: test configuration failed")
|
||||||
|
# exit after test if no commands specified (test only):
|
||||||
|
if not len(self._args):
|
||||||
|
output("OK: configuration test is successful")
|
||||||
|
return ret
|
||||||
|
|
||||||
# Nothing to do here, process in client/server
|
# Nothing to do here, process in client/server
|
||||||
return None
|
return None
|
||||||
|
except ServerExecutionException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
output("ERROR: %s" % (e,))
|
output("ERROR: %s" % (e,))
|
||||||
if verbose > 2:
|
if verbose > 2:
|
||||||
|
@ -246,7 +268,8 @@ class Fail2banCmdLine():
|
||||||
try:
|
try:
|
||||||
self.configurator.Reload()
|
self.configurator.Reload()
|
||||||
self.configurator.readAll()
|
self.configurator.readAll()
|
||||||
ret = self.configurator.getOptions(jail, self._conf)
|
ret = self.configurator.getOptions(jail, self._conf,
|
||||||
|
ignoreWrong=not self.cleanConfOnly)
|
||||||
self.configurator.convertToProtocol()
|
self.configurator.convertToProtocol()
|
||||||
stream = self.configurator.getConfigStream()
|
stream = self.configurator.getConfigStream()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -144,27 +144,27 @@ class Fail2banServer(Fail2banCmdLine):
|
||||||
return cli
|
return cli
|
||||||
|
|
||||||
def start(self, argv):
|
def start(self, argv):
|
||||||
# Command line options
|
|
||||||
ret = self.initCmdLine(argv)
|
|
||||||
if ret is not None:
|
|
||||||
return ret
|
|
||||||
|
|
||||||
# Commands
|
|
||||||
args = self._args
|
|
||||||
|
|
||||||
cli = None
|
|
||||||
# Just start:
|
|
||||||
if len(args) == 1 and args[0] == 'start' and not self._conf.get("interactive", False):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# If client mode - whole processing over client:
|
|
||||||
if len(args) or self._conf.get("interactive", False):
|
|
||||||
cli = self._Fail2banClient()
|
|
||||||
return cli.start(argv)
|
|
||||||
|
|
||||||
# Start the server:
|
|
||||||
server = None
|
server = None
|
||||||
try:
|
try:
|
||||||
|
# Command line options
|
||||||
|
ret = self.initCmdLine(argv)
|
||||||
|
if ret is not None:
|
||||||
|
return ret
|
||||||
|
|
||||||
|
# Commands
|
||||||
|
args = self._args
|
||||||
|
|
||||||
|
cli = None
|
||||||
|
# Just start:
|
||||||
|
if len(args) == 1 and args[0] == 'start' and not self._conf.get("interactive", False):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# If client mode - whole processing over client:
|
||||||
|
if len(args) or self._conf.get("interactive", False):
|
||||||
|
cli = self._Fail2banClient()
|
||||||
|
return cli.start(argv)
|
||||||
|
|
||||||
|
# Start the server:
|
||||||
from ..server.utils import Utils
|
from ..server.utils import Utils
|
||||||
# background = True, if should be new process running in background, otherwise start in foreground
|
# background = True, if should be new process running in background, otherwise start in foreground
|
||||||
# process will be forked in daemonize, inside of Server module.
|
# process will be forked in daemonize, inside of Server module.
|
||||||
|
|
|
@ -54,7 +54,7 @@ class JailsReader(ConfigReader):
|
||||||
self.__jails = list()
|
self.__jails = list()
|
||||||
return ConfigReader.read(self, "jail")
|
return ConfigReader.read(self, "jail")
|
||||||
|
|
||||||
def getOptions(self, section=None):
|
def getOptions(self, section=None, ignoreWrong=True):
|
||||||
"""Reads configuration for jail(s) and adds enabled jails to __jails
|
"""Reads configuration for jail(s) and adds enabled jails to __jails
|
||||||
"""
|
"""
|
||||||
opts = []
|
opts = []
|
||||||
|
@ -66,7 +66,7 @@ class JailsReader(ConfigReader):
|
||||||
sections = [ section ]
|
sections = [ section ]
|
||||||
|
|
||||||
# Get the options of all jails.
|
# Get the options of all jails.
|
||||||
parse_status = None
|
parse_status = 0
|
||||||
for sec in sections:
|
for sec in sections:
|
||||||
if sec == 'INCLUDES':
|
if sec == 'INCLUDES':
|
||||||
continue
|
continue
|
||||||
|
@ -78,14 +78,15 @@ class JailsReader(ConfigReader):
|
||||||
if ret:
|
if ret:
|
||||||
if jail.isEnabled():
|
if jail.isEnabled():
|
||||||
# at least one jail was successful:
|
# at least one jail was successful:
|
||||||
parse_status = True
|
parse_status |= 1
|
||||||
# We only add enabled jails
|
# We only add enabled jails
|
||||||
self.__jails.append(jail)
|
self.__jails.append(jail)
|
||||||
else:
|
else:
|
||||||
logSys.error("Errors in jail %r. Skipping..." % sec)
|
logSys.error("Errors in jail %r.%s", sec, " Skipping..." if ignoreWrong else "")
|
||||||
self.__jails.append(jail)
|
self.__jails.append(jail)
|
||||||
if parse_status is None: parse_status = False
|
# at least one jail was invalid:
|
||||||
return True if parse_status != False else False
|
parse_status |= 2
|
||||||
|
return ((ignoreWrong and parse_status & 1) or not (parse_status & 2))
|
||||||
|
|
||||||
def convert(self, allow_no_files=False):
|
def convert(self, allow_no_files=False):
|
||||||
"""Convert read before __opts and jails to the commands stream
|
"""Convert read before __opts and jails to the commands stream
|
||||||
|
|
|
@ -675,6 +675,36 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
||||||
self.pruneLog()
|
self.pruneLog()
|
||||||
os.remove(pjoin(tmp, "f2b.sock"))
|
os.remove(pjoin(tmp, "f2b.sock"))
|
||||||
|
|
||||||
|
@with_tmpdir
|
||||||
|
@with_kill_srv
|
||||||
|
def testServerTestFailStart(self, tmp):
|
||||||
|
# started directly here, so prevent overwrite test cases logger with "INHERITED"
|
||||||
|
startparams = _start_params(tmp, logtarget="INHERITED")
|
||||||
|
cfg = pjoin(tmp, "config")
|
||||||
|
|
||||||
|
# test configuration is correct:
|
||||||
|
self.pruneLog("[test-phase 0]")
|
||||||
|
self.execSuccess(startparams, "--test")
|
||||||
|
self.assertLogged("OK: configuration test is successful")
|
||||||
|
|
||||||
|
# append one wrong configured jail:
|
||||||
|
_write_file(pjoin(cfg, "jail.conf"), "a", "", "[broken-jail]",
|
||||||
|
"", "filter = broken-jail-filter", "enabled = true")
|
||||||
|
|
||||||
|
# first try test config:
|
||||||
|
self.pruneLog("[test-phase 0a]")
|
||||||
|
self.execFailed(startparams, "--test")
|
||||||
|
self.assertLogged("Unable to read the filter 'broken-jail-filter'",
|
||||||
|
"Errors in jail 'broken-jail'.",
|
||||||
|
"ERROR: test configuration failed", all=True)
|
||||||
|
|
||||||
|
# failed to start with test config:
|
||||||
|
self.pruneLog("[test-phase 0b]")
|
||||||
|
self.execFailed(startparams, "-t", "start")
|
||||||
|
self.assertLogged("Unable to read the filter 'broken-jail-filter'",
|
||||||
|
"Errors in jail 'broken-jail'.",
|
||||||
|
"ERROR: test configuration failed", all=True)
|
||||||
|
|
||||||
@with_tmpdir
|
@with_tmpdir
|
||||||
def testKillAfterStart(self, tmp):
|
def testKillAfterStart(self, tmp):
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in New Issue