Fork of https://framagit.org/ppom/reaction/ to implement multi-pattern match in the same filter
Go to file
2023-10-20 12:00:00 +02:00
app Fix persistence bug 2023-10-20 12:00:00 +02:00
config Fix persistence bug 2023-10-20 12:00:00 +02:00
ip46tables.d ip46tables 2023-10-05 12:00:00 +02:00
logger Implement start/stop commands 2023-10-18 12:00:00 +02:00
logo order 🧹 2023-04-26 17:11:03 +02:00
.gitignore ip46tables 2023-10-05 12:00:00 +02:00
default.nix Standardize go project structure 2023-05-05 12:53:10 +02:00
go.mod support json, jsonnet, yaml formats 2023-10-05 12:00:00 +02:00
go.sum support json, jsonnet, yaml formats 2023-10-05 12:00:00 +02:00
LICENSE Add AGPL LICENSE 2023-04-11 11:03:50 +00:00
Makefile ip46tables 2023-10-05 12:00:00 +02:00
reaction.go New unified CLI design 2023-09-03 12:13:18 +02:00
README.md Implement start/stop commands 2023-10-18 12:00:00 +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.

(adapted from fail2ban 's presentation 😄)

🚧 this program hasn't received external audit. however, it already works well on my servers 🚧

rationale

i was using fail2ban since quite a long time, but i was a bit frustrated by its cpu consumption and all its heavy default configuration.

in my view, a security-oriented program should be simple to configure (sudo is a very bad example!) and an always-running daemon should be implemented in a fast language.

📽️ french example

configuration

this configuration file is all that should be needed to prevent brute force attacks on an ssh server.

see reaction.service and reaction.yml for the fully explained examples.

/etc/reaction.yml

definitions:
  - &iptablesban [ "ip46tables" "-w" "-I" "reaction" "1" "-s" "<ip>" "-j" "block" ]
  - &iptablesunban [ "ip46tables" "-w" "-D" "reaction" "1" "-s" "<ip>" "-j" "block" ]

patterns:
  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" ]

streams:
  ssh:
    cmd: [ "journalctl" "-fu" "sshd.service" ]
    filters:
      failedlogin:
        regex:
          - 'authentication failure;.*rhost=<ip>'
        retry: 3
        retryperiod: '6h'
        actions:
          ban:
            cmd: *iptablesban
          unban:
            cmd: *iptablesunban
            after: '48h'

jsonnet is also supported:

/etc/reaction.jsonnet

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' ]);
{
  patterns: {
    ip: {
      regex: @'(?:(?:[ 0-9 ]{1,3}\.){3}[0-9]{1,3})|(?:[0-9a-fA-F:]{2,90})',
    },
  },
  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" ]),
  },
  streams: {
    ssh: {
      cmd: [ 'journalctl', '-fu', 'sshd.service' ],
      filters: {
        failedlogin: {
          regex: [ @'authentication failure;.*rhost=<ip>' ],
          retry: 3,
          retryperiod: '6h',
          actions: {
            ban: {
              cmd: iptablesban('<ip>'),
            },
            unban: {
              cmd: iptablesunban('<ip>'),
              after: '48h',
              onexit: true,
            },
          },
        },
      },
    },
  },
}

note that both yaml and jsonnet are extensions of json, so json is also inherently supported.

/etc/systemd/system/reaction.service

[ Unit ]
WantedBy=multi-user.target

[ Service ]
ExecStart=/path/to/reaction -c /etc/reaction.yml

StateDirectory=reaction
RuntimeDirectory=reaction
WorkingDirectory=/var/lib/reaction

database

the working directory of reaction will be used to create and read from the embedded database. if you don't know where to start it, /var/lib/reaction should be a sane choice.

socket

the socket allowing communication between the cli and server will be created at /run/reaction/reaction.socket.

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.

compilation

you'll need the go toolchain for reaction and a c compiler for ip46tables.

$ make

alternatively,

$ go build .
$ gcc ip46tables.d/ip46tables.c -o ip46tables

nixos

in addition to the package and module 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, consider reading and building upon my own building blocks , my own non-root reaction conf, including conf for SSH, port scanning & Nginx common attack URLS , and the configuration for nextcloud , vaultwarden , and maddy . see also an example where it does something else than banning IPs.