action_log: log to systemd, aka. enable recidive detection

master
Y 2018-02-26 18:55:45 +01:00 committed by Yves G
parent c5135e847a
commit 0140a934c2
8 changed files with 89 additions and 22 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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 (jsons `object_pairs_hook`); * Python version ≥ 3.1 is required for loading the configuration (jsons `object_pairs_hook`);
* Python version ≥ 3.2 is required for the daily report and emails (strings `format_map`); * Python version ≥ 3.2 is required for the daily report and emails (strings `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`).

View File

@ -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 RedHats [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 RedHats [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.

View File

@ -0,0 +1,22 @@
# pyruse is intended as a replacement to both fail2ban and epylog
# Copyright © 20172018 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)

View File

@ -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 © 20172018 Y. Gablin # Copyright © 20172018 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)

15
tests/action_log.py Normal file
View File

@ -0,0 +1,15 @@
# pyruse is intended as a replacement to both fail2ban and epylog
# Copyright © 20172018 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()

View File

@ -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