Skip to content

Commit

Permalink
fail2ban: Improve output, add unit-test
Browse files Browse the repository at this point in the history
  • Loading branch information
markuslf committed Jul 4, 2023
1 parent 58cc181 commit 588ed6b
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 57 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Project:

Monitoring Plugins:

* fail2ban: Improve output, add unit-test
* grafana-version: Add Grafana v9.5
* infomaniak-swiss-backup-devices: Improve column ordering in output
* mysql-logfile: Returns OK instead of UNKNOWN if logfile is found but empty
Expand Down
29 changes: 17 additions & 12 deletions check-plugins/fail2ban/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,48 +89,53 @@ Help

.. code-block:: text
usage: fail2ban [-h] [-V] [--always-ok] [-c CRIT] [-w WARN]
usage: fail2ban [-h] [-V] [--always-ok] [-c CRIT] [--test TEST] [-w WARN]
In fail2ban, checks the amount of banned IP addresses (for a list of jails).
In fail2ban, checks the amount of banned IP addresses per jail.
optional arguments:
options:
-h, --help show this help message and exit
-V, --version show program's version number and exit
--always-ok Always returns OK.
-c CRIT, --critical CRIT
Set the critical threshold for banned IPs. Default:
10000
Set the critical threshold for banned IPs per jail.
Default: 10000
--test TEST For unit tests. Needs "path-to-stdout-file,path-to-
stderr-file,expected-retc".
-w WARN, --warning WARN
Set the warning threshold for banned IPs. Default:
1000
Set the warning threshold for banned IPs per jail.
Default: 2500
Usage Examples
--------------

.. code-block:: bash
./fail2ban --warning 1000 --critical 10000
./fail2ban --warning 2500 --critical 10000
Output:

.. code-block:: text
787 IPs banned in jail "linuxfabrik-portscan" (acting on /var/log/messages), 0 IPs banned in jail "sshd"
IPs banned - apache-dos: 2, portscan: 0, sshd: 5432 [WARNING]
States
------

* WARN or CRIT if number of blocked IP addresses is above a given threshold.
* WARN or CRIT if number of blocked IP addresses is above a given threshold per jail.


Perfdata / Metrics
------------------

Per jail:
.. csv-table::
:widths: 25, 15, 60
:header-rows: 1

* Number of blocked IP addresses.
Name, Type, Description
<jail>, Number, Number of blocked IP addresses.


Credits, License
Expand Down
97 changes: 61 additions & 36 deletions check-plugins/fail2ban/fail2ban
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@
"""

import argparse # pylint: disable=C0413
import re # pylint: disable=C0413
import sys # pylint: disable=C0413

import lib.args # pylint: disable=C0413
import lib.base # pylint: disable=C0413
import lib.shell # pylint: disable=C0413
import lib.test # pylint: disable=C0413
import lib.txt # pylint: disable=C0413
from lib.globals import (STATE_CRIT, STATE_OK, # pylint: disable=C0413
STATE_UNKNOWN, STATE_WARN)

__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
__version__ = '2023051201'
__version__ = '2023070401'

DESCRIPTION = 'In fail2ban, checks the amount of banned IP addresses (for a list of jails).'

cmd = 'fail2ban-client'
DESCRIPTION = 'In fail2ban, checks the amount of banned IP addresses per jail.'

DEFAULT_CRIT = 10000
DEFAULT_WARN = 2500
Expand All @@ -52,15 +52,22 @@ def parse_args():

parser.add_argument(
'-c', '--critical',
help='Set the critical threshold for banned IPs. Default: %(default)s',
help='Set the critical threshold for banned IPs per jail. Default: %(default)s',
dest='CRIT',
type=int,
default=DEFAULT_CRIT,
)

parser.add_argument(
'--test',
help='For unit tests. Needs "path-to-stdout-file,path-to-stderr-file,expected-retc".',
dest='TEST',
type=lib.args.csv,
)

parser.add_argument(
'-w', '--warning',
help='Set the warning threshold for banned IPs. Default: %(default)s',
help='Set the warning threshold for banned IPs per jail. Default: %(default)s',
dest='WARN',
type=int,
default=DEFAULT_WARN,
Expand All @@ -81,58 +88,76 @@ def main():

# fetch data
# fail2ban-client ping
stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec(cmd + ' ping'))
if retc:
if args.TEST is None:
stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec('fail2ban-client ping'))
else:
# do not call the command, put in test data
stdout, stderr, retc = lib.test.test([args.TEST[0] + '-ping', args.TEST[1], args.TEST[2]])
if retc != 0:
lib.base.oao('Problem while testing if the fail2ban server is alive.', STATE_UNKNOWN)

# fail2ban-client status
stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec(cmd + ' status'))
if args.TEST is None:
stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec('fail2ban-client status'))
else:
# do not call the command, put in test data
stdout, stderr, retc = lib.test.test([args.TEST[0] + '-status', args.TEST[1], args.TEST[2]])
if retc != 0:
lib.base.oao('Problem while testing the status of the fail2ban server.', STATE_UNKNOWN)

# extract the jail list
jail_list = re.search(r'Jail list:\t(.*)\n', stdout)
jail_list = jail_list.group(1).strip().split(', ')
jail_list = lib.txt.extract_str(stdout, 'Jail list:\t', '\n')
if not jail_list:
lib.base.oao('No jails found.', STATE_UNKNOWN)
jail_list = jail_list.split(', ')

# init some vars
msg = ''
perfdata = ''
state = STATE_OK

# let's do the checks
# for each jail_name:
# fail2ban-client status jail_name
# analyze data
# for each jail_name:
# get status of jail_name
for jail in jail_list:

stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec(cmd + ' status {}'.format(jail)))
if args.TEST is None:
stdout, stderr, retc = lib.base.coe(
lib.shell.shell_exec('fail2ban-client status {}'.format(jail))
)
else:
# do not call the command, put in test data
stdout, stderr, retc = lib.test.test(
[args.TEST[0] + '-status-{}'.format(jail),
args.TEST[1],
args.TEST[2]],
)
if retc != 0:
lib.base.oao('Problem while testing the status of the jail "{}" on the fail2ban server.'.format(jail), STATE_UNKNOWN)

f2b_filelist = re.search(r'File list:\t(.*)\n', stdout)
if f2b_filelist:
f2b_filelist = f2b_filelist.group(1).strip()
f2b_currently_banned = re.search(r'Currently banned:\t(.*)\n', stdout)
# important to convert the result to an integer
# for the comparison later on
lib.base.oao(
'Problem while testing the status of the jail "{}" on the fail2ban server.'.format(
jail
),
STATE_UNKNOWN,
)

f2b_currently_banned = lib.txt.extract_str(stdout, 'Currently banned:\t', '\n')
# important to convert the result to an integer for the comparison later on
if f2b_currently_banned:
f2b_currently_banned = int(f2b_currently_banned.group(1).strip())
f2b_currently_banned = int(f2b_currently_banned)
else:
f2b_currently_banned = 0
jail_state = lib.base.get_state(f2b_currently_banned, args.WARN, args.CRIT)
state = lib.base.get_worst(state, jail_state)

msg += '{} {} banned in jail "{}"'.format(
f2b_currently_banned,
lib.txt.pluralize('IP', f2b_currently_banned),
msg += '{}: {}{}, '.format(
jail,
f2b_currently_banned,
lib.base.state2str(state, prefix=' '),
)
if f2b_filelist:
msg += ' (acting on {})'.format(f2b_filelist)
msg += ', '

perfdata += lib.base.get_perfdata(jail, f2b_currently_banned, None, args.WARN, args.CRIT, 0, None)
state = lib.base.get_state(f2b_currently_banned, args.WARN, args.CRIT)
perfdata += lib.base.get_perfdata(jail, f2b_currently_banned, None, args.WARN, args.CRIT, 0, None) # pylint: disable=C0301

# over and out
lib.base.oao(msg[:-2], state, perfdata, always_ok=args.ALWAYS_OK)
if msg:
lib.base.oao('IPs banned - ' + msg[:-2], state, perfdata, always_ok=args.ALWAYS_OK)


if __name__ == '__main__':
Expand Down
18 changes: 9 additions & 9 deletions check-plugins/fail2ban/icingaweb2-module-director/fail2ban.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@
"disabled": false,
"fields": [
{
"datafield_id": 1951,
"datafield_id": 1,
"is_required": "n",
"var_filter": null
},
{
"datafield_id": 1952,
"datafield_id": 2,
"is_required": "n",
"var_filter": null
},
{
"datafield_id": 1953,
"datafield_id": 3,
"is_required": "n",
"var_filter": null
}
Expand Down Expand Up @@ -72,7 +72,7 @@
"tpl-service-generic"
],
"max_check_attempts": 5,
"notes": "In fail2ban, checks the amount of banned IP addresses (for a list of jails).",
"notes": "In fail2ban, checks the amount of banned IP addresses per jail.",
"notes_url": "https://github.com/Linuxfabrik/monitoring-plugins/tree/main/check-plugins/fail2ban",
"object_name": "tpl-service-fail2ban",
"object_type": "template",
Expand All @@ -93,7 +93,7 @@
}
},
"Datafield": {
"1951": {
"1": {
"varname": "fail2ban_always_ok",
"caption": "Fail2ban: Always OK?",
"description": "Always returns OK.",
Expand All @@ -102,21 +102,21 @@
"settings": {},
"uuid": "fc4647f1-c1e0-46c4-9667-86452089dd69"
},
"1952": {
"2": {
"varname": "fail2ban_critical",
"caption": "Fail2ban: Critical",
"description": "Set the critical threshold for banned IPs.",
"description": "Set the critical threshold for banned IPs per jail.",
"datatype": "Icinga\\Module\\Director\\DataType\\DataTypeString",
"format": null,
"settings": {
"visibility": "visible"
},
"uuid": "e6fb221a-0a24-4c72-b50b-5da6429f4cc5"
},
"1953": {
"3": {
"varname": "fail2ban_warning",
"caption": "Fail2ban: Warning",
"description": "Set the warning threshold for banned IPs.",
"description": "Set the warning threshold for banned IPs per jail.",
"datatype": "Icinga\\Module\\Director\\DataType\\DataTypeString",
"format": null,
"settings": {
Expand Down
47 changes: 47 additions & 0 deletions check-plugins/fail2ban/unit-test/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
# -*- coding: utf-8; py-indent-offset: 4 -*-
#
# Author: Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
# https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.

# https://github.com/Linuxfabrik/monitoring-plugins/blob/main/CONTRIBUTING.rst

import sys
sys.path.append("..") # Adds higher directory to python modules path.



import unittest

from lib.globals import *
import lib.base
import lib.shell


class TestCheck(unittest.TestCase):

check = '../fail2ban'

def test_if_check_runs_EXAMPLE01_ping_nok(self):
stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec(self.check + ' --test=stdout/EXAMPLE01,,1'))
self.assertRegex(stdout, r'Problem while testing if the fail2ban server is alive.')
self.assertEqual(stderr, '')
self.assertEqual(retc, STATE_UNKNOWN)

def test_if_check_runs_EXAMPLE01(self):
stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec(self.check + ' --test=stdout/EXAMPLE01,,0'))
self.assertRegex(stdout, r'IPs banned - apache-dos: 2, portscan: 0, sshd: 5432 \[WARNING\]')
self.assertEqual(stderr, '')
self.assertEqual(retc, STATE_WARN)

def test_if_check_runs_EXAMPLE99(self):
stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec(self.check + ' --test=stdout/EXAMPLE99,,0'))
self.assertRegex(stdout, r'No jails found.')
self.assertEqual(stderr, '')
self.assertEqual(retc, STATE_UNKNOWN)


if __name__ == '__main__':
unittest.main()
1 change: 1 addition & 0 deletions check-plugins/fail2ban/unit-test/stdout/EXAMPLE01-ping
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Server replied: pong
3 changes: 3 additions & 0 deletions check-plugins/fail2ban/unit-test/stdout/EXAMPLE01-status
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Status
|- Number of jail: 3
`- Jail list: apache-dos, portscan, sshd
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Status for the jail: apache-dos
|- Filter
| |- Currently failed: 287
| |- Total failed: 169079
| `- File list: /var/log/httpd/192.0.2.74-access.log /var/log/httpd/www.example.com-access.log /var/log/httpd/reports.example.com-access.log /var/log/httpd/test.example.com-access.log /var/log/httpd/ssl_access_log /var/log/httpd/www.example.com-access.log /var/log/httpd/cloud.example.com-access.log /var/log/httpd/access_log /var/log/httpd/time.example.com-access.log
`- Actions
|- Currently banned: 2
|- Total banned: 34
`- Banned IP list:
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Status for the jail: portscan
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- Journal matches: _TRANSPORT=kernel
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
9 changes: 9 additions & 0 deletions check-plugins/fail2ban/unit-test/stdout/EXAMPLE01-status-sshd
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 5432
|- Total banned: 0
`- Banned IP list:

0 comments on commit 588ed6b

Please sign in to comment.