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))
|
||||
jail_stat(j, " " if i == len(jstat) else " | ")
|
||||
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:
|
||||
pass # to few cmd args for below
|
||||
elif inC[1] == "syslogsocket":
|
||||
|
|
|
@ -59,6 +59,7 @@ protocol = [
|
|||
["banned <IP> ... <IP>]", "return list(s) of jails where given IP(s) are banned"],
|
||||
["status", "gets the current status of the server"],
|
||||
["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"],
|
||||
["echo", "for internal usage, returns back and outputs a given string"],
|
||||
["help", "return this output"],
|
||||
|
|
|
@ -721,9 +721,11 @@ class Actions(JailThread, Mapping):
|
|||
"""Status of current and total ban counts and current banned IP list.
|
||||
"""
|
||||
# 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:
|
||||
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)
|
||||
if flavor != "short":
|
||||
banned = self.banManager.getBanList()
|
||||
|
|
|
@ -978,6 +978,8 @@ class Filter(JailThread):
|
|||
def status(self, flavor="basic"):
|
||||
"""Status of failures detected by filter.
|
||||
"""
|
||||
if flavor == "stats":
|
||||
return (self.failManager.size(), self.failManager.getFailTotal())
|
||||
ret = [("Currently failed", self.failManager.size()),
|
||||
("Total failed", self.failManager.getFailTotal())]
|
||||
return ret
|
||||
|
@ -1255,6 +1257,8 @@ class FileFilter(Filter):
|
|||
"""Status of Filter plus files being monitored.
|
||||
"""
|
||||
ret = super(FileFilter, self).status(flavor=flavor)
|
||||
if flavor == "stats":
|
||||
return ret
|
||||
path = list(self.__logs.keys())
|
||||
ret.append(("File list", path))
|
||||
return ret
|
||||
|
|
|
@ -429,6 +429,8 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
|||
|
||||
def status(self, flavor="basic"):
|
||||
ret = super(FilterSystemd, self).status(flavor=flavor)
|
||||
if flavor == "stats":
|
||||
return ret
|
||||
ret.append(("Journal matches",
|
||||
[" + ".join(" ".join(match) for match in self.__matches)]))
|
||||
return ret
|
||||
|
|
|
@ -81,8 +81,9 @@ class Jail(object):
|
|||
# Extra parameters for increase ban time
|
||||
self._banExtra = {};
|
||||
logSys.info("Creating new jail '%s'" % self.name)
|
||||
self._realBackend = None
|
||||
if backend is not None:
|
||||
self._setBackend(backend)
|
||||
self._realBackend = self._setBackend(backend)
|
||||
self.backend = backend
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -113,7 +114,7 @@ class Jail(object):
|
|||
else:
|
||||
logSys.info("Initiated %r backend" % b)
|
||||
self.__actions = Actions(self)
|
||||
return # we are done
|
||||
return b # we are done
|
||||
except ImportError as e: # pragma: no cover
|
||||
# Log debug if auto, but error if specific
|
||||
logSys.log(
|
||||
|
@ -185,10 +186,15 @@ class Jail(object):
|
|||
def status(self, flavor="basic"):
|
||||
"""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 [
|
||||
("Filter", self.filter.status(flavor=flavor)),
|
||||
("Actions", self.actions.status(flavor=flavor)),
|
||||
]
|
||||
("Filter", fstat),
|
||||
("Actions", astat),
|
||||
]
|
||||
|
||||
@property
|
||||
def hasFailTickets(self):
|
||||
|
|
|
@ -608,13 +608,18 @@ class Server:
|
|||
try:
|
||||
self.__lock.acquire()
|
||||
jails = sorted(self.__jails.items())
|
||||
jailList = [n for n, j in jails]
|
||||
ret = [("Number of jail", len(jailList)),
|
||||
("Jail list", ", ".join(jailList))]
|
||||
if flavor != "stats":
|
||||
jailList = [n for n, j in jails]
|
||||
ret = [
|
||||
("Number of jail", len(jailList)),
|
||||
("Jail list", ", ".join(jailList))
|
||||
]
|
||||
if name == '--all':
|
||||
jstat = dict(jails)
|
||||
for n, j in jails:
|
||||
jstat[n] = j.status(flavor=flavor)
|
||||
if flavor == "stats":
|
||||
return jstat
|
||||
ret.append(jstat)
|
||||
return ret
|
||||
finally:
|
||||
|
|
|
@ -144,6 +144,8 @@ class Transmitter:
|
|||
return self.__commandGet(command[1:])
|
||||
elif name == "status":
|
||||
return self.status(command[1:])
|
||||
elif name in ("stats", "statistic", "statistics"):
|
||||
return self.__server.status("--all", "stats")
|
||||
elif name == "version":
|
||||
return version.version
|
||||
elif name == "config-error":
|
||||
|
|
|
@ -168,6 +168,27 @@ class BeautifierTest(unittest.TestCase):
|
|||
)
|
||||
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):
|
||||
self.b.setInputCmd(["flushlogs"])
|
||||
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)),
|
||||
{"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):
|
||||
self.assertEqual(self.transm.proceed(["status", self.jailName]),
|
||||
|
|
Loading…
Reference in New Issue