mirror of https://github.com/fail2ban/fail2ban
implemented `fail2ban-client stats` (or alias `fail2ban-client statistic[s]`) for tabulated output of fail2ban stats
amend to #2975pull/3641/merge
parent
bdba42edd9
commit
3ca3646472
|
@ -103,6 +103,46 @@ class Beautifier:
|
||||||
msg.append(" %s Jail: %s" % (prefix1, n))
|
msg.append(" %s Jail: %s" % (prefix1, n))
|
||||||
jail_stat(j, " " if i == len(jstat) else " | ")
|
jail_stat(j, " " if i == len(jstat) else " | ")
|
||||||
msg = "\n".join(msg)
|
msg = "\n".join(msg)
|
||||||
|
elif inC[0:1] == ['stats'] or inC[0:1] == ['statistics']:
|
||||||
|
def _statstable(response):
|
||||||
|
tophead = ["Jail", "Backend", "Filter", "Actions"]
|
||||||
|
headers = ["", "", "cur", "tot", "cur", "tot"]
|
||||||
|
minlens = [8, 8, 3, 3, 3, 3]
|
||||||
|
ralign = [0, 0, 1, 1, 1, 1]
|
||||||
|
rows = [[n, r[0], *r[1], *r[2]] for n, r in response.items()]
|
||||||
|
lens = []
|
||||||
|
for i in range(len(rows[0])):
|
||||||
|
col = (len(str(s[i])) for s in rows)
|
||||||
|
lens.append(max(minlens[i], max(col)))
|
||||||
|
rfmt = []
|
||||||
|
hfmt = []
|
||||||
|
for i in range(len(rows[0])):
|
||||||
|
f = "%%%ds" if ralign[i] else "%%-%ds"
|
||||||
|
rfmt.append(f % lens[i])
|
||||||
|
hfmt.append(f % lens[i])
|
||||||
|
rfmt = [rfmt[0], rfmt[1], "%s \u2502 %s" % (rfmt[2], rfmt[3]), "%s \u2502 %s" % (rfmt[4], rfmt[5])]
|
||||||
|
hfmt = [hfmt[0], hfmt[1], "%s \u2502 %s" % (hfmt[2], hfmt[3]), "%s \u2502 %s" % (hfmt[4], hfmt[5])]
|
||||||
|
tlens = [lens[0], lens[1], 3 + lens[2] + lens[3], 3 + lens[4] + lens[5]]
|
||||||
|
tfmt = [hfmt[0], hfmt[1], "%%-%ds" % (tlens[2],), "%%-%ds" % (tlens[3],)]
|
||||||
|
tsep = tfmt[0:2]
|
||||||
|
rfmt = " \u2551 ".join(rfmt)
|
||||||
|
hfmt = " \u2551 ".join(hfmt)
|
||||||
|
tfmt = " \u2551 ".join(tfmt)
|
||||||
|
tsep = " \u2551 ".join(tsep)
|
||||||
|
separator = ((tsep % tuple(tophead[0:2])) + " \u255F\u2500" +
|
||||||
|
("\u2500\u256B\u2500".join(['\u2500' * n for n in tlens[2:]])) + '\u2500')
|
||||||
|
ret = []
|
||||||
|
ret.append(tfmt % tuple(["", ""]+tophead[2:]))
|
||||||
|
ret.append(separator)
|
||||||
|
ret.append(hfmt % tuple(headers))
|
||||||
|
separator = "\u2550\u256C\u2550".join(['\u2550' * n for n in tlens]) + '\u2550'
|
||||||
|
ret.append(separator)
|
||||||
|
for row in rows:
|
||||||
|
ret.append(rfmt % tuple(row))
|
||||||
|
separator = "\u2550\u2569\u2550".join(['\u2550' * n for n in tlens]) + '\u2550'
|
||||||
|
ret.append(separator)
|
||||||
|
return ret
|
||||||
|
msg = "\n".join(_statstable(response))
|
||||||
elif len(inC) < 2:
|
elif len(inC) < 2:
|
||||||
pass # to few cmd args for below
|
pass # to few cmd args for below
|
||||||
elif inC[1] == "syslogsocket":
|
elif inC[1] == "syslogsocket":
|
||||||
|
|
|
@ -59,6 +59,7 @@ protocol = [
|
||||||
["banned <IP> ... <IP>]", "return list(s) of jails where given IP(s) are banned"],
|
["banned <IP> ... <IP>]", "return list(s) of jails where given IP(s) are banned"],
|
||||||
["status", "gets the current status of the server"],
|
["status", "gets the current status of the server"],
|
||||||
["status --all [FLAVOR]", "gets the current status of all jails, with optional flavor or extended info"],
|
["status --all [FLAVOR]", "gets the current status of all jails, with optional flavor or extended info"],
|
||||||
|
["stat[istic]s", "gets the current statistics of all jails as table"],
|
||||||
["ping", "tests if the server is alive"],
|
["ping", "tests if the server is alive"],
|
||||||
["echo", "for internal usage, returns back and outputs a given string"],
|
["echo", "for internal usage, returns back and outputs a given string"],
|
||||||
["help", "return this output"],
|
["help", "return this output"],
|
||||||
|
|
|
@ -721,9 +721,11 @@ class Actions(JailThread, Mapping):
|
||||||
"""Status of current and total ban counts and current banned IP list.
|
"""Status of current and total ban counts and current banned IP list.
|
||||||
"""
|
"""
|
||||||
# TODO: Allow this list to be printed as 'status' output
|
# TODO: Allow this list to be printed as 'status' output
|
||||||
supported_flavors = ["short", "basic", "cymru"]
|
supported_flavors = ["short", "basic", "stats", "cymru"]
|
||||||
if flavor is None or flavor not in supported_flavors:
|
if flavor is None or flavor not in supported_flavors:
|
||||||
logSys.warning("Unsupported extended jail status flavor %r. Supported: %s" % (flavor, supported_flavors))
|
logSys.warning("Unsupported extended jail status flavor %r. Supported: %s" % (flavor, supported_flavors))
|
||||||
|
if flavor == "stats":
|
||||||
|
return (self.banManager.size(), self.banManager.getBanTotal())
|
||||||
# Always print this information (basic)
|
# Always print this information (basic)
|
||||||
if flavor != "short":
|
if flavor != "short":
|
||||||
banned = self.banManager.getBanList()
|
banned = self.banManager.getBanList()
|
||||||
|
|
|
@ -978,6 +978,8 @@ class Filter(JailThread):
|
||||||
def status(self, flavor="basic"):
|
def status(self, flavor="basic"):
|
||||||
"""Status of failures detected by filter.
|
"""Status of failures detected by filter.
|
||||||
"""
|
"""
|
||||||
|
if flavor == "stats":
|
||||||
|
return (self.failManager.size(), self.failManager.getFailTotal())
|
||||||
ret = [("Currently failed", self.failManager.size()),
|
ret = [("Currently failed", self.failManager.size()),
|
||||||
("Total failed", self.failManager.getFailTotal())]
|
("Total failed", self.failManager.getFailTotal())]
|
||||||
return ret
|
return ret
|
||||||
|
@ -1255,6 +1257,8 @@ class FileFilter(Filter):
|
||||||
"""Status of Filter plus files being monitored.
|
"""Status of Filter plus files being monitored.
|
||||||
"""
|
"""
|
||||||
ret = super(FileFilter, self).status(flavor=flavor)
|
ret = super(FileFilter, self).status(flavor=flavor)
|
||||||
|
if flavor == "stats":
|
||||||
|
return ret
|
||||||
path = list(self.__logs.keys())
|
path = list(self.__logs.keys())
|
||||||
ret.append(("File list", path))
|
ret.append(("File list", path))
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -429,6 +429,8 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
|
|
||||||
def status(self, flavor="basic"):
|
def status(self, flavor="basic"):
|
||||||
ret = super(FilterSystemd, self).status(flavor=flavor)
|
ret = super(FilterSystemd, self).status(flavor=flavor)
|
||||||
|
if flavor == "stats":
|
||||||
|
return ret
|
||||||
ret.append(("Journal matches",
|
ret.append(("Journal matches",
|
||||||
[" + ".join(" ".join(match) for match in self.__matches)]))
|
[" + ".join(" ".join(match) for match in self.__matches)]))
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -81,8 +81,9 @@ class Jail(object):
|
||||||
# Extra parameters for increase ban time
|
# Extra parameters for increase ban time
|
||||||
self._banExtra = {};
|
self._banExtra = {};
|
||||||
logSys.info("Creating new jail '%s'" % self.name)
|
logSys.info("Creating new jail '%s'" % self.name)
|
||||||
|
self._realBackend = None
|
||||||
if backend is not None:
|
if backend is not None:
|
||||||
self._setBackend(backend)
|
self._realBackend = self._setBackend(backend)
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -113,7 +114,7 @@ class Jail(object):
|
||||||
else:
|
else:
|
||||||
logSys.info("Initiated %r backend" % b)
|
logSys.info("Initiated %r backend" % b)
|
||||||
self.__actions = Actions(self)
|
self.__actions = Actions(self)
|
||||||
return # we are done
|
return b # we are done
|
||||||
except ImportError as e: # pragma: no cover
|
except ImportError as e: # pragma: no cover
|
||||||
# Log debug if auto, but error if specific
|
# Log debug if auto, but error if specific
|
||||||
logSys.log(
|
logSys.log(
|
||||||
|
@ -185,10 +186,15 @@ class Jail(object):
|
||||||
def status(self, flavor="basic"):
|
def status(self, flavor="basic"):
|
||||||
"""The status of the jail.
|
"""The status of the jail.
|
||||||
"""
|
"""
|
||||||
|
fstat = self.filter.status(flavor=flavor)
|
||||||
|
astat = self.actions.status(flavor=flavor)
|
||||||
|
if flavor == "stats":
|
||||||
|
backend = type(self.filter).__name__.replace('Filter', '').lower()
|
||||||
|
return [self._realBackend or self.backend, fstat, astat]
|
||||||
return [
|
return [
|
||||||
("Filter", self.filter.status(flavor=flavor)),
|
("Filter", fstat),
|
||||||
("Actions", self.actions.status(flavor=flavor)),
|
("Actions", astat),
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hasFailTickets(self):
|
def hasFailTickets(self):
|
||||||
|
|
|
@ -608,13 +608,18 @@ class Server:
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
jails = sorted(self.__jails.items())
|
jails = sorted(self.__jails.items())
|
||||||
jailList = [n for n, j in jails]
|
if flavor != "stats":
|
||||||
ret = [("Number of jail", len(jailList)),
|
jailList = [n for n, j in jails]
|
||||||
("Jail list", ", ".join(jailList))]
|
ret = [
|
||||||
|
("Number of jail", len(jailList)),
|
||||||
|
("Jail list", ", ".join(jailList))
|
||||||
|
]
|
||||||
if name == '--all':
|
if name == '--all':
|
||||||
jstat = dict(jails)
|
jstat = dict(jails)
|
||||||
for n, j in jails:
|
for n, j in jails:
|
||||||
jstat[n] = j.status(flavor=flavor)
|
jstat[n] = j.status(flavor=flavor)
|
||||||
|
if flavor == "stats":
|
||||||
|
return jstat
|
||||||
ret.append(jstat)
|
ret.append(jstat)
|
||||||
return ret
|
return ret
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -144,6 +144,8 @@ class Transmitter:
|
||||||
return self.__commandGet(command[1:])
|
return self.__commandGet(command[1:])
|
||||||
elif name == "status":
|
elif name == "status":
|
||||||
return self.status(command[1:])
|
return self.status(command[1:])
|
||||||
|
elif name in ("stats", "statistic", "statistics"):
|
||||||
|
return self.__server.status("--all", "stats")
|
||||||
elif name == "version":
|
elif name == "version":
|
||||||
return version.version
|
return version.version
|
||||||
elif name == "config-error":
|
elif name == "config-error":
|
||||||
|
|
|
@ -168,6 +168,27 @@ class BeautifierTest(unittest.TestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(self.b.beautify(response), output)
|
self.assertEqual(self.b.beautify(response), output)
|
||||||
|
|
||||||
|
def testStatusStats(self):
|
||||||
|
self.b.setInputCmd(["stats"])
|
||||||
|
response = {
|
||||||
|
"ssh": ["systemd", (3, 6), (12, 24)],
|
||||||
|
"exim4": ["pyinotify", (6, 12), (20, 20)],
|
||||||
|
"jail-with-long-name": ["polling", (0, 0), (0, 0)]
|
||||||
|
}
|
||||||
|
output = (""
|
||||||
|
+ " ? ? Filter ? Actions \n"
|
||||||
|
+ "Jail ? Backend ????????????????????????\n"
|
||||||
|
+ " ? ? cur ? tot ? cur ? tot\n"
|
||||||
|
+ "????????????????????????????????????????????????????????\n"
|
||||||
|
+ "ssh ? systemd ? 3 ? 6 ? 12 ? 24\n"
|
||||||
|
+ "exim4 ? pyinotify ? 6 ? 12 ? 20 ? 20\n"
|
||||||
|
+ "jail-with-long-name ? polling ? 0 ? 0 ? 0 ? 0\n"
|
||||||
|
+ "????????????????????????????????????????????????????????"
|
||||||
|
)
|
||||||
|
response = self.b.beautify(response).encode('ascii', 'replace').decode('ascii')
|
||||||
|
self.assertEqual(response, output)
|
||||||
|
|
||||||
|
|
||||||
def testFlushLogs(self):
|
def testFlushLogs(self):
|
||||||
self.b.setInputCmd(["flushlogs"])
|
self.b.setInputCmd(["flushlogs"])
|
||||||
self.assertEqual(self.b.beautify("rolled over"), "logs: rolled over")
|
self.assertEqual(self.b.beautify("rolled over"), "logs: rolled over")
|
||||||
|
|
|
@ -645,6 +645,11 @@ class Transmitter(TransmitterBase):
|
||||||
(0, [('Number of jail', len(jails)), ('Jail list', ", ".join(jails)),
|
(0, [('Number of jail', len(jails)), ('Jail list', ", ".join(jails)),
|
||||||
{"TestJail1": self._JAIL_STATUS, "TestJail2": self._JAIL_STATUS}
|
{"TestJail1": self._JAIL_STATUS, "TestJail2": self._JAIL_STATUS}
|
||||||
]))
|
]))
|
||||||
|
self.assertEqual(self.transm.proceed(["stats"]),
|
||||||
|
(0, {
|
||||||
|
"TestJail1": [FAST_BACKEND, (0, 0), (0, 0)],
|
||||||
|
"TestJail2": [FAST_BACKEND, (0, 0), (0, 0)]
|
||||||
|
}))
|
||||||
|
|
||||||
def testJailStatus(self):
|
def testJailStatus(self):
|
||||||
self.assertEqual(self.transm.proceed(["status", self.jailName]),
|
self.assertEqual(self.transm.proceed(["status", self.jailName]),
|
||||||
|
|
Loading…
Reference in New Issue