action_log: log to systemd, aka. enable recidive detection
parent
c5135e847a
commit
0140a934c2
|
@ -27,4 +27,4 @@ For more in-depth documentation, please refer to these pages:
|
||||||
- [the `action_noop` module](doc/noop.md)
|
- [the `action_noop` module](doc/noop.md)
|
||||||
- [the `action_email` module](doc/action_email.md)
|
- [the `action_email` module](doc/action_email.md)
|
||||||
- [the `action_dailyReport` module](doc/action_dailyReport.md)
|
- [the `action_dailyReport` module](doc/action_dailyReport.md)
|
||||||
- [the `action_nftBan` module](doc/action_nftBan.md)
|
- [the `action_nftBan` and `action_log` modules](doc/logandban.md)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
The software requirements are:
|
The software requirements are:
|
||||||
|
|
||||||
* a modern systemd-based Linux operating system (eg. [Archlinux](https://archlinux.org/)- or [Fedora](https://getfedora.org/)-based distributions);
|
* a modern systemd-based Linux operating system (eg. [Archlinux](https://archlinux.org/)- or [Fedora](https://getfedora.org/)-based distributions);
|
||||||
* python, at least version 3.1 (or [more, depending on the modules](doc/intro_tech.md) being used);
|
* python, at least version 3.4 (or [more, depending on the modules](intro_tech.md) being used);
|
||||||
* [python-systemd](https://www.freedesktop.org/software/systemd/python-systemd/journal.html);
|
* [python-systemd](https://www.freedesktop.org/software/systemd/python-systemd/journal.html);
|
||||||
* [nftables](http://wiki.nftables.org/) _if_ IP address bans are to be managed;
|
* [nftables](http://wiki.nftables.org/) _if_ IP address bans are to be managed;
|
||||||
* a sendmail-like program _if_ emails are wanted.
|
* a sendmail-like program _if_ emails are wanted.
|
||||||
|
|
|
@ -15,7 +15,7 @@ It should be noted, that modern Python API are used. Thus:
|
||||||
* Python version ≥ 3.1 is required for managing modules (`importlib`);
|
* Python version ≥ 3.1 is required for managing modules (`importlib`);
|
||||||
* Python version ≥ 3.1 is required for loading the configuration (json’s `object_pairs_hook`);
|
* Python version ≥ 3.1 is required for loading the configuration (json’s `object_pairs_hook`);
|
||||||
* Python version ≥ 3.2 is required for the daily report and emails (string’s `format_map`);
|
* Python version ≥ 3.2 is required for the daily report and emails (string’s `format_map`);
|
||||||
* Python version ≥ 3.4 is required for the daily report (`enum`);
|
* Python version ≥ 3.4 is required for the daily report and logging, thus also the log action (`enum`);
|
||||||
* Python version ≥ 3.5 is required for IP address bans and emails, thus also the daily report (subprocess’ `run`);
|
* Python version ≥ 3.5 is required for IP address bans and emails, thus also the daily report (subprocess’ `run`);
|
||||||
* Python version ≥ 3.6 is required for emails, thus also the daily report (`headerregistry`, `EmailMessage`).
|
* Python version ≥ 3.6 is required for emails, thus also the daily report (`headerregistry`, `EmailMessage`).
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,33 @@
|
||||||
# Ban IP addresses after they misbehaved
|
# Log entries creation, and ban of IP addresses
|
||||||
|
|
||||||
|
## Log entries
|
||||||
|
|
||||||
|
The main purpose of creating new log entries, is to detect recidives in bad behaviour: after an IP address misbehaves, it gets banned, and we generate a log line for that; such log lines get counted, and eventually trigger a harsher, recidive, ban of the same IP address. Several levels of bans can thus be stacked, up to an unlimited ban, if such is wanted.
|
||||||
|
|
||||||
|
Action `action_log` takes a mandatory `message` argument, which is a template for the message to be sent.
|
||||||
|
Optionally, the log level can be changed from the default (which is “INFO”) by setting the `level` parameter; valid values are “EMERG”, “ALERT”, “CRIT”, “ERR”, “WARNING”, “NOTICE”, “INFO”, and “DEBUG” (see [Syslog severity levels](https://en.wikipedia.org/wiki/Syslog#Severity_level) for the definitions).
|
||||||
|
|
||||||
|
The `message` parameter is a Python [string format](https://docs.python.org/3/library/string.html#formatstrings).
|
||||||
|
This means that any key in the current entry may be referrenced by its name between curly braces.
|
||||||
|
This also means that literal curly braces must be doubled, lest they are read as the start of a template placeholder.
|
||||||
|
|
||||||
|
Here are some examples:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "action_log", "args": { "message": "Ban from SSH for {thatIP}." }
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"action": "action_log",
|
||||||
|
"args": {
|
||||||
|
"level": "NOTICE",
|
||||||
|
"message": "Recidive ban from SSH for {thatIP}."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ban IP addresses after they misbehaved
|
||||||
|
|
||||||
Linux provides a number of firewall solutions: [iptables](http://www.netfilter.org/), its successor [nftables](http://wiki.nftables.org/), and many iptables frontends like [Shorewall](http://www.shorewall.net/) or RedHat’s [firewalld](http://www.firewalld.org/).
|
Linux provides a number of firewall solutions: [iptables](http://www.netfilter.org/), its successor [nftables](http://wiki.nftables.org/), and many iptables frontends like [Shorewall](http://www.shorewall.net/) or RedHat’s [firewalld](http://www.firewalld.org/).
|
||||||
For Pyruse, **nftables** was chosen, because it is modern and light-weight, and provides interesting features.
|
For Pyruse, **nftables** was chosen, because it is modern and light-weight, and provides interesting features.
|
||||||
|
@ -59,7 +88,7 @@ Here are examples:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## List the currently banned addresses
|
### List the currently banned addresses
|
||||||
|
|
||||||
To see what IP addresses are currently banned, here is the `nft` command:
|
To see what IP addresses are currently banned, here is the `nft` command:
|
||||||
|
|
||||||
|
@ -85,7 +114,7 @@ table ip Inet4 {
|
||||||
|
|
||||||
_Note_: The un-rounded timeouts are post-reboot restored bans.
|
_Note_: The un-rounded timeouts are post-reboot restored bans.
|
||||||
|
|
||||||
## Un-ban an IP address
|
### Un-ban an IP address
|
||||||
|
|
||||||
It is bound to happen some day: you will want to un-ban a banned IP address.
|
It is bound to happen some day: you will want to un-ban a banned IP address.
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# pyruse is intended as a replacement to both fail2ban and epylog
|
||||||
|
# Copyright © 2017–2018 Y. Gablin
|
||||||
|
# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing.
|
||||||
|
import string
|
||||||
|
from pyruse import base, log
|
||||||
|
|
||||||
|
class Action(base.Action):
|
||||||
|
def __init__(self, args):
|
||||||
|
super().__init__()
|
||||||
|
self.level = log.Level[args.get("level", log.Level.INFO.name)]
|
||||||
|
self.template = args["message"]
|
||||||
|
values = {}
|
||||||
|
for (_void, name, _void, _void) in string.Formatter().parse(self.template):
|
||||||
|
if name:
|
||||||
|
values[name] = None
|
||||||
|
self.values = values
|
||||||
|
|
||||||
|
def act(self, entry):
|
||||||
|
for (name, _void) in self.values.items():
|
||||||
|
self.values[name] = entry.get(name, None)
|
||||||
|
msg = self.template.format_map(self.values)
|
||||||
|
log.log(self.level, msg)
|
|
@ -1,28 +1,28 @@
|
||||||
# pyruse is intended as a replacement to both fail2ban and epylog
|
# pyruse is intended as a replacement to both fail2ban and epylog
|
||||||
# Copyright © 2017–2018 Y. Gablin
|
# Copyright © 2017–2018 Y. Gablin
|
||||||
# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing.
|
# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing.
|
||||||
|
from enum import Enum, unique
|
||||||
from systemd import journal
|
from systemd import journal
|
||||||
|
|
||||||
EMERG = 0 # System is unusable.
|
@unique
|
||||||
ALERT = 1 # Action must be taken immediately.
|
class Level(Enum):
|
||||||
CRIT = 2 # Critical conditions, such as hard device errors.
|
EMERG = 0 # System is unusable.
|
||||||
ERR = 3 # Error conditions.
|
ALERT = 1 # Action must be taken immediately.
|
||||||
WARNING = 4 # Warning conditions.
|
CRIT = 2 # Critical conditions, such as hard device errors.
|
||||||
NOTICE = 5 # Normal but significant conditions.
|
ERR = 3 # Error conditions.
|
||||||
INFO = 6 # Informational messages.
|
WARNING = 4 # Warning conditions.
|
||||||
DEBUG = 7
|
NOTICE = 5 # Normal but significant conditions.
|
||||||
|
INFO = 6 # Informational messages.
|
||||||
|
DEBUG = 7
|
||||||
|
|
||||||
def log(level, string):
|
def log(level, string):
|
||||||
journal.send(string, PRIORITY = level)
|
journal.send(string, PRIORITY = level.value)
|
||||||
|
|
||||||
def debug(string):
|
def debug(string):
|
||||||
global DEBUG
|
log(Level.DEBUG, string)
|
||||||
log(DEBUG, string)
|
|
||||||
|
|
||||||
def notice(string):
|
def notice(string):
|
||||||
global NOTICE
|
log(Level.NOTICE, string)
|
||||||
log(NOTICE, string)
|
|
||||||
|
|
||||||
def error(string):
|
def error(string):
|
||||||
global ERR
|
log(Level.ERR, string)
|
||||||
log(ERR, string)
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
# pyruse is intended as a replacement to both fail2ban and epylog
|
||||||
|
# Copyright © 2017–2018 Y. Gablin
|
||||||
|
# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing.
|
||||||
|
from unittest.mock import patch
|
||||||
|
from pyruse import log
|
||||||
|
from pyruse.actions.action_log import Action
|
||||||
|
|
||||||
|
@patch('pyruse.actions.action_log.log.log')
|
||||||
|
def whenLogThenRightSystemdCall(mockLog):
|
||||||
|
for level in log.Level:
|
||||||
|
Action({"level": level.name, "message": "Test: {text}"}).act({"text": "test message"})
|
||||||
|
mockLog.assert_called_with(level, "Test: test message")
|
||||||
|
|
||||||
|
def unitTests():
|
||||||
|
whenLogThenRightSystemdCall()
|
|
@ -20,7 +20,7 @@ def main():
|
||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
import filter_equals, filter_greaterOrEquals, filter_in, filter_inNetworks, filter_lowerOrEquals, filter_pcre, filter_pcreAny, filter_userExists
|
import filter_equals, filter_greaterOrEquals, filter_in, filter_inNetworks, filter_lowerOrEquals, filter_pcre, filter_pcreAny, filter_userExists
|
||||||
import action_counterRaise, action_counterReset, action_dailyReport, action_email, action_nftBan
|
import action_counterRaise, action_counterReset, action_dailyReport, action_email, action_log, action_nftBan
|
||||||
|
|
||||||
filter_equals.unitTests()
|
filter_equals.unitTests()
|
||||||
filter_greaterOrEquals.unitTests()
|
filter_greaterOrEquals.unitTests()
|
||||||
|
@ -34,6 +34,7 @@ def main():
|
||||||
action_counterReset.unitTests()
|
action_counterReset.unitTests()
|
||||||
action_dailyReport.unitTests()
|
action_dailyReport.unitTests()
|
||||||
action_email.unitTests()
|
action_email.unitTests()
|
||||||
|
action_log.unitTests()
|
||||||
action_nftBan.unitTests()
|
action_nftBan.unitTests()
|
||||||
|
|
||||||
# Integration test
|
# Integration test
|
||||||
|
|
Loading…
Reference in New Issue