2023-04-09 21:42:38 +02:00
# reaction
a program that scans program outputs, such as logs,
for repeated patterns, such as failed login attempts,
and takes action, such as banning ips.
2023-10-18 12:00:00 +02:00
(adapted from [ fail2ban ](http://fail2ban.org )'s presentation 😄)
2023-04-09 21:42:38 +02:00
2023-05-26 13:53:59 +02:00
🚧 this program hasn't received external audit. however, it already works well on my servers 🚧
2023-04-09 21:42:38 +02:00
## rationale
2023-10-04 12:00:00 +02:00
i was using fail2ban since quite a long time, but i was a bit frustrated by its cpu consumption
2023-04-09 21:42:38 +02:00
and all its heavy default configuration.
2023-09-03 13:26:27 +02:00
in my view, a security-oriented program should be simple to configure (`sudo` is a very bad example!)
2023-04-12 09:55:39 +02:00
and an always-running daemon should be implemented in a fast language.
2023-04-09 21:42:38 +02:00
2023-10-01 12:00:00 +02:00
< a href = "https://u.ppom.me/reaction.webm" > 📽️ french example< / a >
2023-04-09 21:42:38 +02:00
## configuration
2023-10-04 12:00:00 +02:00
this configuration file is all that should be needed to prevent brute force attacks on an ssh server.
2023-04-09 21:42:38 +02:00
2023-10-18 12:00:00 +02:00
see [ reaction.service ](./config/reaction.service ) and [reaction.yml ](./app/reaction.yml ) for the fully explained examples.
2023-09-03 13:26:27 +02:00
2023-04-09 21:42:38 +02:00
`/etc/reaction.yml`
```yaml
definitions:
2023-10-18 12:00:00 +02:00
- & iptablesban [ "ip46tables" "-w" "-I" "reaction" "1" "-s" "< ip > " "-j" "block" ]
- & iptablesunban [ "ip46tables" "-w" "-D" "reaction" "1" "-s" "< ip > " "-j" "block" ]
2023-04-09 21:42:38 +02:00
patterns:
2023-10-18 12:00:00 +02:00
ip: '(([ 0-9 ]{1,3}\.){3}[0-9]{1,3})|([0-9a-fA-F:]{2,90})'
start:
- [ "ip46tables", "-w", "-N", "reaction" ]
- [ "ip46tables", "-w", "-A", "reaction", "-j", "ACCEPT" ]
- [ "ip46tables", "-w", "-I", "reaction", "1", "-s", "127.0.0.1", "-j", "ACCEPT" ]
- [ "ip46tables", "-w", "-I", "INPUT", "-p", "all", "-j", "reaction" ]
stop:
- [ "ip46tables", "-w," "-D," "INPUT", "-p", "all", "-j", "reaction" ]
- [ "ip46tables", "-w," "-F," "reaction" ]
- [ "ip46tables", "-w," "-X," "reaction" ]
2023-04-09 21:42:38 +02:00
streams:
ssh:
cmd: [ "journalctl" "-fu" "sshd.service" ]
filters:
failedlogin:
regex:
2023-10-04 12:00:00 +02:00
- 'authentication failure;.*rhost=< ip > '
2023-04-09 21:42:38 +02:00
retry: 3
2023-10-04 12:00:00 +02:00
retryperiod: '6h'
2023-04-09 21:42:38 +02:00
actions:
ban:
cmd: *iptablesban
unban:
2023-10-04 12:00:00 +02:00
cmd: *iptablesunban
after: '48h'
2023-04-09 21:42:38 +02:00
```
2023-10-04 12:00:00 +02:00
jsonnet is also supported:
`/etc/reaction.jsonnet`
```jsonnet
2023-10-18 12:00:00 +02:00
local iptables(args) = [ 'ip46tables', '-w' ] + args;
local iptablesban(ip) = iptables([ '-A', 'reaction', '1', '-s', ip, '-j', 'DROP' ]);
local iptablesunban(ip) = iptables([ '-D', 'reaction', '1', '-s', ip, '-j', 'DROP' ]);
2023-10-04 12:00:00 +02:00
{
patterns: {
ip: {
2023-10-18 12:00:00 +02:00
regex: @'(?:(?:[ 0-9 ]{1,3}\.){3}[0-9]{1,3})|(?:[0-9a-fA-F:]{2,90})',
2023-10-04 12:00:00 +02:00
},
},
2023-10-18 12:00:00 +02:00
start: {
iptables([ "-N", "reaction" ]),
iptables([ "-A", "reaction", "-j", "ACCEPT" ]),
iptables([ "-I", "reaction", "1", "-s", "127.0.0.1", "-j", "ACCEPT" ]),
iptables([ "-I", "INPUT", "-p", "all", "-j", "reaction" ]),
},
stop: {
iptables([ "-D,", "INPUT", "-p", "all", "-j", "reaction" ]),
iptables([ "-F,", "reaction" ]),
iptables([ "-X,", "reaction" ]),
},
2023-10-04 12:00:00 +02:00
streams: {
ssh: {
2023-10-18 12:00:00 +02:00
cmd: [ 'journalctl', '-fu', 'sshd.service' ],
2023-10-04 12:00:00 +02:00
filters: {
failedlogin: {
regex: [ @'authentication failure;.*rhost=< ip > ' ],
retry: 3,
retryperiod: '6h',
actions: {
ban: {
2023-10-18 12:00:00 +02:00
cmd: iptablesban('< ip > '),
2023-10-04 12:00:00 +02:00
},
unban: {
2023-10-18 12:00:00 +02:00
cmd: iptablesunban('< ip > '),
2023-10-04 12:00:00 +02:00
after: '48h',
onexit: true,
},
},
},
},
},
},
}
```
2023-10-05 12:00:00 +02:00
note that both yaml and jsonnet are extensions of json, so json is also inherently supported.
2023-10-04 12:00:00 +02:00
2023-04-09 21:42:38 +02:00
`/etc/systemd/system/reaction.service`
```systemd
2023-10-18 12:00:00 +02:00
[ Unit ]
2023-04-09 21:42:38 +02:00
WantedBy=multi-user.target
2023-10-18 12:00:00 +02:00
[ Service ]
2023-04-09 21:42:38 +02:00
ExecStart=/path/to/reaction -c /etc/reaction.yml
2023-04-11 13:01:02 +02:00
StateDirectory=reaction
2023-05-05 15:33:00 +02:00
RuntimeDirectory=reaction
2023-04-11 13:01:02 +02:00
WorkingDirectory=/var/lib/reaction
```
2023-04-11 13:14:46 +02:00
### database
2023-04-11 13:01:02 +02:00
2023-05-05 10:06:34 +02:00
the working directory of `reaction` will be used to create and read from the embedded database.
2023-04-11 13:14:46 +02:00
if you don't know where to start it, `/var/lib/reaction` should be a sane choice.
2023-04-11 13:01:02 +02:00
2023-05-05 15:33:00 +02:00
### socket
the socket allowing communication between the cli and server will be created at `/run/reaction/reaction.socket` .
2023-10-05 12:00:00 +02:00
### `ip46tables`
`ip46tables` is a minimal c program present in its own subdirectory with only standard posix dependencies.
it permits to configure `iptables` and `ip6tables` at the same time.
it will execute `iptables` when detecting ipv4, `ip6tables` when detecting ipv6 and both if no ip address is present on the command line.
2023-04-11 13:14:46 +02:00
### compilation
2023-04-11 13:01:02 +02:00
2023-10-05 12:00:00 +02:00
you'll need the go toolchain for reaction and a c compiler for ip46tables.
```shell
$ make
```
alternatively,
2023-04-11 13:14:46 +02:00
```shell
$ go build .
2023-10-05 12:00:00 +02:00
$ gcc ip46tables.d/ip46tables.c -o ip46tables
2023-04-09 21:42:38 +02:00
```
2023-05-26 13:53:59 +02:00
### nixos
2023-10-18 12:00:00 +02:00
in addition to the [ package ](https://framagit.org/ppom/nixos/-/blob/cf5448b21ae3386265485308a6cd077e8068ad77/pkgs/reaction/default.nix )
and [ module ](https://framagit.org/ppom/nixos/-/blob/cf5448b21ae3386265485308a6cd077e8068ad77/modules/common/reaction.nix )
2023-10-04 12:00:00 +02:00
that i didn't try to upstream to nixpkgs yet (although they are ready), i use extensively reaction on my servers. if you're using nixos,
2023-10-18 12:00:00 +02:00
consider reading and building upon [ my own building blocks ](https://framagit.org/ppom/nixos/-/blob/cf5448b21ae3386265485308a6cd077e8068ad77/modules/common/reaction-variables.nix ),
[ my own non-root reaction conf, including conf for SSH, port scanning & Nginx common attack URLS ](https://framagit.org/ppom/nixos/-/blob/cf5448b21ae3386265485308a6cd077e8068ad77/modules/common/reaction-custom.nix ),
and the configuration for [ nextcloud ](https://framagit.org/ppom/nixos/-/blob/cf5448b21ae3386265485308a6cd077e8068ad77/modules/musi/file.ppom.me.nix#L53 ),
[ vaultwarden ](https://framagit.org/ppom/nixos/-/blob/cf5448b21ae3386265485308a6cd077e8068ad77/modules/musi/vaultwarden.nix#L45 ),
and [ maddy ](https://framagit.org/ppom/nixos/-/blob/cf5448b21ae3386265485308a6cd077e8068ad77/modules/musi/mail.nix#L74 ). see also an [example ](https://framagit.org/ppom/nixos/-/blob/cf5448b21ae3386265485308a6cd077e8068ad77/modules/musi/mail.nix#L85 ) where it does something else than banning IPs.