diff --git a/README.md b/README.md
index 988cf3c..0188e88 100644
--- a/README.md
+++ b/README.md
@@ -1,52 +1,55 @@
# 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.
+A daemon that scans program outputs for repeated patterns, and takes action.
-(adapted from [ fail2ban ](http://fail2ban.org)'s presentation 😄)
+A common usage is to scan ssh and webserver logs, and to ban hosts that cause multiple authentication errors.
-🚧 this program hasn't received external audit. however, it already works well on my servers 🚧
+🚧 This program hasn't received external audit. however, it already works well on my servers 🚧
-## rationale
+## Rationale
-i was using fail2ban since quite a long time, but i was a bit frustrated by its cpu consumption
+I was using the honorable 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.
+In my view, a security-oriented program should be simple to configure
+and an always-running daemon should be implemented in a fast*er* language.
-📽️ french example
+reaction does not have all the features of the honorable fail2ban, but it's ~10x faster and has more manageable configuration.
-## configuration
+📽️ french quick explanation 😉
-this configuration file is all that should be needed to prevent brute force attacks on an ssh server.
+## Configuration
-see [ reaction.service ](./config/reaction.service) and [reaction.yml](./app/reaction.yml) for the fully explained examples.
+YAML and [JSONnet](https://jsonnet.org/) (more powerful) are supported.
+both are extensions of JSON, so JSON is transitively supported.
+
+- See [reaction.yml](./app/example.yml) or [reaction.jsonnet](./config/example.jsonnet) for a fully explained reference
+- See [server.jsonnet](./config/server.jsonnet) for a real-world configuration
+- See [reaction.service](./config/reaction.service) for a systemd service file
+- This quick example shows what's needed to prevent brute force attacks on an ssh server:
+
+
+
+/etc/reaction.yml
-`/etc/reaction.yml`
```yaml
-definitions:
- - &iptablesban [ "ip46tables" "-w" "-I" "reaction" "1" "-s" "" "-j" "block" ]
- - &iptablesunban [ "ip46tables" "-w" "-D" "reaction" "1" "-s" "" "-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" ]
+ - [ '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" ]
+ - [ 'ip46tables', '-w', '-D', 'INPUT', '-p', 'all', '-j', 'reaction' ]
+ - [ 'ip46tables', '-w', '-F', 'reaction' ]
+ - [ 'ip46tables', '-w', '-X', 'reaction' ]
streams:
ssh:
- cmd: [ "journalctl" "-fu" "sshd.service" ]
+ cmd: [ 'journalctl', '-fu', 'sshd.service' ]
filters:
failedlogin:
regex:
@@ -55,36 +58,46 @@ streams:
retryperiod: '6h'
actions:
ban:
- cmd: *iptablesban
+ cmd: [ 'ip46tables', '-w', '-I', 'reaction', '1', '-s', '', '-j', 'block' ]
unban:
- cmd: *iptablesunban
+ cmd: [ 'ip46tables', '-w', '-D', 'reaction', '1', '-s', '', '-j', 'block' ]
after: '48h'
```
-jsonnet is also supported:
+
+
+
+
+/etc/reaction.jsonnet
-`/etc/reaction.jsonnet`
```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' ]);
+local banFor(time) = {
+ ban: {
+ cmd: iptables(['-A', 'reaction', '-s', '', '-j', 'reaction-log-refuse']),
+ },
+ unban: {
+ after: time,
+ cmd: iptables(['-D', 'reaction', '-s', '', '-j', 'reaction-log-refuse']),
+ },
+};
{
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" ]),
- },
+ 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' ],
@@ -93,16 +106,7 @@ local iptablesunban(ip) = iptables([ '-D', 'reaction', '1', '-s', ip, '-j', 'DRO
regex: [ @'authentication failure;.*rhost=' ],
retry: 3,
retryperiod: '6h',
- actions: {
- ban: {
- cmd: iptablesban(''),
- },
- unban: {
- cmd: iptablesunban(''),
- after: '48h',
- onexit: true,
- },
- },
+ actions: banFor('48h'),
},
},
},
@@ -110,57 +114,45 @@ local iptablesunban(ip) = iptables([ '-D', 'reaction', '1', '-s', ip, '-j', 'DRO
}
```
-note that both yaml and jsonnet are extensions of json, so json is also inherently supported.
+
-`/etc/systemd/system/reaction.service`
-```systemd
-[ Unit ]
-WantedBy=multi-user.target
-[ Service ]
-ExecStart=/path/to/reaction -c /etc/reaction.yml
+### Database
-StateDirectory=reaction
-RuntimeDirectory=reaction
-WorkingDirectory=/var/lib/reaction
-```
+The embedded database is stored in the working directory.
+If you don't know where to start reaction, `/var/lib/reaction` should be a sane choice.
-### database
+### CLI
-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`.
+- `reaction start` runs the server
+- `reaction show` show pending actions (ie. bans)
+- `reaction flush` permits to run pending actions (ie. clear bans)
+- `reaction test-regex` permits to test regexes
+- `reaction help` for full usage.
### `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.
+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
+### Compilation
-you'll need the go toolchain for reaction and a c compiler for ip46tables.
+You'll need the go toolchain for reaction and a c compiler for ip46tables.
```shell
$ make
```
-alternatively,
+Alternatively,
```shell
+# creates ./reaction
$ go build .
+# creates ./ip46tables
$ gcc ip46tables.d/ip46tables.c -o ip46tables
```
-### nixos
+### NixOS
-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)
-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 ](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.
+- [ package ](https://framagit.org/ppom/nixos/-/blob/main/pkgs/reaction/default.nix)
+- [ module ](https://framagit.org/ppom/nixos/-/blob/main/modules/common/reaction.nix)
diff --git a/app/example.yml b/app/example.yml
index 95a8220..2c00f23 100644
--- a/app/example.yml
+++ b/app/example.yml
@@ -3,8 +3,8 @@
# using YAML anchors `&name` and pointers `*name`
# definitions are not readed by reaction
definitions:
- - &iptablesban [ "ip46tables", "-w", "-A", "reaction", "1", "-s", "", "-j", "DROP" ]
- - &iptablesunban [ "ip46tables", "-w", "-D", "reaction", "1", "-s", "", "-j", "DROP" ]
+ - &iptablesban [ 'ip46tables', '-w', '-A', 'reaction', '1', '-s', '', '-j', 'DROP' ]
+ - &iptablesunban [ 'ip46tables', '-w', '-D', 'reaction', '1', '-s', '', '-j', 'DROP' ]
# ip46tables is a minimal C program (only POSIX dependencies) present as a subdirectory.
# it permits to handle both ipv4/iptables and ipv6/ip6tables commands
@@ -20,20 +20,20 @@ patterns:
# Those commands will be executed in order at start, before everything else
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" ]
+ - [ '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' ]
# Those commands will be executed in order at stop, after everything else
stop:
- - [ "ip46tables", "-w,", "-D", "INPUT", "-p", "all", "-j", "reaction" ]
- - [ "ip46tables", "-w", "-F", "reaction" ]
- - [ "ip46tables", "-w", "-X", "reaction" ]
+ - [ 'ip46tables', '-w,', '-D', 'INPUT', '-p', 'all', '-j', 'reaction' ]
+ - [ 'ip46tables', '-w', '-F', 'reaction' ]
+ - [ 'ip46tables', '-w', '-X', 'reaction' ]
# streams are commands
-# they're run and their ouptut is captured
+# they are run and their ouptut is captured
# *example:* `tail -f /var/log/nginx/access.log`
# their output will be used by one or more filters
streams:
@@ -41,7 +41,7 @@ streams:
ssh:
# note that if the command is not in environment's `PATH`
# its full path must be given.
- cmd: [ "journalctl", "-n0", "-fu", "sshd.service" ]
+ cmd: [ 'journalctl', '-n0', '-fu', 'sshd.service' ]
# filters run actions when they match regexes on a stream
filters:
# filters have a user-defined name
@@ -73,7 +73,7 @@ streams:
onexit: true
# (defaults to false)
# here it is not useful because we will flush the chain containing the bans anyway
- # (see /conf/reaction.service)
+ # (with the stop commands)
# persistence
# tldr; when an `after` action is set in a filter, such filter acts as a 'jail',
diff --git a/app/main.go b/app/main.go
index ba4c437..e5cca74 100644
--- a/app/main.go
+++ b/app/main.go
@@ -118,13 +118,10 @@ func Main() {
logger.Fatalln("No argument provided. Try `reaction help`")
basicUsage()
os.Exit(1)
- } else if os.Args[1] == "-h" || os.Args[1] == "--help" {
- basicUsage()
- os.Exit(0)
}
f := flag.NewFlagSet(os.Args[1], flag.ExitOnError)
switch os.Args[1] {
- case "help", "-h", "--help":
+ case "help", "-h", "-help", "--help":
basicUsage()
case "example-conf":
@@ -224,7 +221,7 @@ func Main() {
}
default:
- logger.Fatalln("subcommand not recognized")
+ logger.Fatalf("subcommand %v not recognized. Try `reaction help`", os.Args[1])
basicUsage()
os.Exit(1)
}
diff --git a/config/example.jsonnet b/config/example.jsonnet
index 598fab1..6a7881b 100644
--- a/config/example.jsonnet
+++ b/config/example.jsonnet
@@ -3,11 +3,8 @@
// JSONNET is a superset of JSON, so one can write plain JSON files if wanted.
// Note that YAML is also supported, see ./example.yml
-// A JSONNET function
+// JSONNET functions
local iptables(args) = ['ip46tables', '-w'] + args;
-// variables defined for later use.
-local iptablesban = iptables(['-A', 'reaction', '1', '-s', '', '-j', 'drop']);
-local iptablesunban = iptables(['-D', 'reaction', '1', '-s', '', '-j', 'drop']);
// ip46tables is a minimal C program (only POSIX dependencies) present as a subdirectory.
// it permits to handle both ipv4/iptables and ipv6/ip6tables commands
@@ -48,7 +45,7 @@ local iptablesunban = iptables(['-D', 'reaction', '1', '-s', '', '-j', 'drop
],
// streams are commands
- // they're run and their ouptut is captured
+ // they are run and their ouptut is captured
// *example:* `tail -f /var/log/nginx/access.log`
// their output will be used by one or more filters
streams: {
@@ -77,11 +74,10 @@ local iptablesunban = iptables(['-D', 'reaction', '1', '-s', '', '-j', 'drop
actions: {
// actions have a user-defined name
ban: {
- // JSONNET substitutes the variable (defined at the beginning of the file)
- cmd: iptablesban,
+ cmd: iptables(['-A', 'reaction', '-s', '', '-j', 'reaction-log-refuse']),
},
unban: {
- cmd: iptablesunban,
+ cmd: iptables(['-D', 'reaction', '-s', '', '-j', 'reaction-log-refuse']),
// if after is defined, the action will not take place immediately, but after a specified duration
// same format as retryperiod
after: '48h',
@@ -90,7 +86,7 @@ local iptablesunban = iptables(['-D', 'reaction', '1', '-s', '', '-j', 'drop
onexit: true,
// (defaults to false)
// here it is not useful because we will flush the chain containing the bans anyway
- // (see /conf/reaction.service)
+ // (with the stop commands)
},
},
},
diff --git a/config/heavy-load.yml b/config/heavy-load.yml
index a342a16..a63999a 100644
--- a/config/heavy-load.yml
+++ b/config/heavy-load.yml
@@ -9,7 +9,7 @@ patterns:
streams:
tailDown1:
- cmd: [ "sh", "-c", "sleep 2; seq 100010 | while read i; do echo found $(($i % 100)); done" ]
+ cmd: [ 'sh', '-c', 'sleep 2; seq 100010 | while read i; do echo found $(($i % 100)); done' ]
filters:
findIP:
regex:
@@ -18,13 +18,13 @@ streams:
retryperiod: 1m
actions:
damn:
- cmd: [ "echo", "" ]
+ cmd: [ 'echo', '' ]
undamn:
- cmd: [ "echo", "undamn", "" ]
+ cmd: [ 'echo', 'undamn', '' ]
after: 1m
onexit: false
tailDown2:
- cmd: [ "sh", "-c", "sleep 2; seq 100010 | while read i; do echo prout $(($i % 100)); done" ]
+ cmd: [ 'sh', '-c', 'sleep 2; seq 100010 | while read i; do echo prout $(($i % 100)); done' ]
filters:
findIP:
regex:
@@ -33,13 +33,13 @@ streams:
retryperiod: 1m
actions:
damn:
- cmd: [ "echo", "" ]
+ cmd: [ 'echo', '' ]
undamn:
- cmd: [ "echo", "undamn", "" ]
+ cmd: [ 'echo', 'undamn', '' ]
after: 1m
onexit: false
tailDown3:
- cmd: [ "sh", "-c", "sleep 2; seq 100010 | while read i; do echo nanana $(($i % 100)); done" ]
+ cmd: [ 'sh', '-c', 'sleep 2; seq 100010 | while read i; do echo nanana $(($i % 100)); done' ]
filters:
findIP:
regex:
@@ -48,13 +48,13 @@ streams:
retryperiod: 2m
actions:
damn:
- cmd: [ "true" ]
+ cmd: [ 'true' ]
undamn:
- cmd: [ "true" ]
+ cmd: [ 'true' ]
after: 1m
onexit: false
tailDown4:
- cmd: [ "sh", "-c", "sleep 2; seq 100010 | while read i; do echo nanana $(($i % 100)); done" ]
+ cmd: [ 'sh', '-c', 'sleep 2; seq 100010 | while read i; do echo nanana $(($i % 100)); done' ]
filters:
findIP:
regex:
@@ -63,8 +63,8 @@ streams:
retryperiod: 2m
actions:
damn:
- cmd: [ "echo", "" ]
+ cmd: [ 'echo', '' ]
undamn:
- cmd: [ "echo", "undamn", "" ]
+ cmd: [ 'echo', 'undamn', '' ]
after: 1m
onexit: false
diff --git a/config/reaction.service b/config/reaction.service
index 5232252..1cc83b5 100644
--- a/config/reaction.service
+++ b/config/reaction.service
@@ -4,7 +4,7 @@ WantedBy=multi-user.target
# See `man systemd.exec` and `man systemd.service` for most options below
[Service]
-ExecStart=/path/to/reaction -c /etc/reaction.yml
+ExecStart=/path/to/reaction start -c /etc/reaction.yml
# Ask systemd to create /var/lib/reaction (/var/lib/ is implicit)
StateDirectory=reaction
diff --git a/config/server.jsonnet b/config/server.jsonnet
new file mode 100644
index 0000000..71ecd3d
--- /dev/null
+++ b/config/server.jsonnet
@@ -0,0 +1,147 @@
+// This is the extensive configuration used on a **real** server!
+
+local banFor(time) = {
+ ban: {
+ cmd: ['ip46tables', '-w', '-A', 'reaction', '-s', '', '-j', 'reaction-log-refuse'],
+ },
+ unban: {
+ after: time,
+ cmd: ['ip46tables', '-w', '-D', 'reaction', '-s', '', '-j', 'reaction-log-refuse'],
+ },
+};
+
+{
+ patterns: {
+ // IPs can be IPv4 or IPv6
+ // ip46tables (C program also in this repo) handles running the good commands
+ ip: {
+ regex: @'(([0-9]{1,3}\.){3}[0-9]{1,3})|([0-9a-fA-F:]{2,90})',
+ },
+ },
+
+ streams: {
+ // Ban hosts failing to connect via ssh
+ ssh: {
+ cmd: [' journalctl', '-fn0', '-u', 'sshd.service'],
+ filters: {
+ failedlogin: {
+ regex: [
+ @'authentication failure;.*rhost=',
+ @'Connection reset by authenticating user .* ',
+ ],
+ retry: 3,
+ retryperiod: '6h',
+ actions: banFor('48h'),
+ },
+ },
+ },
+
+ // Ban hosts which knock on closed ports.
+ // It needs this iptables chain to be used to drop packets:
+ // ip46tables -N log-refuse
+ // ip46tables -A log-refuse -p tcp --syn -j LOG --log-level info --log-prefix 'refused connection: '
+ // ip46tables -A log-refuse -m pkttype ! --pkt-type unicast -j nixos-fw-refuse
+ // ip46tables -A log-refuse -j DROP
+ kernel: {
+ cmd: ['journalctl', '-fn0', '-k'],
+ filters: {
+ portscan: {
+ regex: ['refused connection: .*SRC='],
+ retry: 4,
+ retryperiod: '1h',
+ actions: banFor('720h'),
+ },
+ },
+ },
+ // Note: nextcloud and vaultwarden could also be filters on the nginx stream
+ // I did use their own logs instead because it's less logs to parse than the front webserver
+
+ // Ban hosts failing to connect to Nextcloud
+ nextcloud: {
+ cmd: ['journalctl', '-fn0', '-u', 'phpfpm-nextcloud.service'],
+ filters: {
+ failedLogin: {
+ regex: [
+ @'"remoteAddr":"".*"message":"Login failed:',
+ @'"remoteAddr":"".*"message":"Trusted domain error.',
+ ],
+ retry: 3,
+ retryperiod: '1h',
+ actions: banFor('1h'),
+ },
+ },
+ },
+
+ // Ban hosts failing to connect to vaultwarden
+ vaultwarden: {
+ cmd: ['journalctl', '-fn0', '-u', 'vaultwarden.service'],
+ filters: {
+ failedlogin: {
+ actions: banFor('2h'),
+ regex: [@'Username or password is incorrect\. Try again\. IP: \. Username:'],
+ retry: 3,
+ retryperiod: '1h',
+ },
+ },
+ },
+
+ // Used with this nginx log configuration:
+ // log_format withhost '$remote_addr - $remote_user [$time_local] $host "$request" $status $bytes_sent "$http_referer" "$http_user_agent"';
+ // access_log /var/log/nginx/access.log withhost;
+ nginx: {
+ cmd: ['tail', '-n0', '-f', '/var/log/nginx/access.log'],
+ filters: {
+ // Ban hosts failing to connect to Directus
+ directus: {
+ regex: [
+ @'^ .* "POST /auth/login HTTP/..." 401 [0-9]+ .https://directusdomain',
+ ],
+ retry: 6,
+ retryperiod: '4h',
+ actions: banFor('4h'),
+ },
+
+ // Ban hosts presenting themselves as bots of ChatGPT
+ gptbot: {
+ regex: [@'^.*GPTBot/1.0'],
+ action: banFor('720h'),
+ },
+
+ // Ban hosts failing to connect to slskd
+ slskd: {
+ regex: [
+ @'^ .* "POST /api/v0/session HTTP/..." 401 [0-9]+ .https://slskddomain',
+ ],
+ retry: 3,
+ retryperiod: '1h',
+ actions: banFor('6h'),
+ },
+
+ // Ban suspect HTTP requests
+ // Those are frequent malicious requests I got from bots
+ // Make sure you don't have honnest use cases for those requests, or your clients may be banned for 2 weeks!
+ suspectRequests: {
+ regex: [
+ // (?:[^/" ]*/)* is a "non-capturing group" regex that allow for subpath(s)
+ // example: /code/.env should be matched as well as /.env
+ // ^^^^^
+ @'^.*"GET /(?:[^/" ]*/)*\.env ',
+ @'^.*"GET /(?:[^/" ]*/)*info\.php ',
+ @'^.*"GET /(?:[^/" ]*/)*owa/auth/logon.aspx ',
+ @'^.*"GET /(?:[^/" ]*/)*auth.html ',
+ @'^.*"GET /(?:[^/" ]*/)*auth1.html ',
+ @'^.*"GET /(?:[^/" ]*/)*password.txt ',
+ @'^.*"GET /(?:[^/" ]*/)*passwords.txt ',
+ @'^.*"GET /(?:[^/" ]*/)*dns-query ',
+ // Do not include this if you have a Wordpress website ;)
+ @'^.*"GET /(?:[^/" ]*/)*wp-login\.php',
+ @'^.*"GET /(?:[^/" ]*/)*wp-includes',
+ // Do not include this if a client must retrieve a config.json file ;)
+ @'^.*"GET /(?:[^/" ]*/)*config\.json ',
+ ],
+ action: banFor('720h'),
+ },
+ },
+ },
+ },
+}
diff --git a/config/test.jsonnet b/config/test.jsonnet
index c3ccf97..468c831 100644
--- a/config/test.jsonnet
+++ b/config/test.jsonnet
@@ -39,7 +39,6 @@
},
tailDown2: {
cmd: ['sh', '-c', 'echo coucou; sleep 2m'],
- // cmd: ['sh', '-c', "echo 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 6 7 8 9 | tr ' ' '\n' | while read i; do sleep 3; echo found $(($i % 60)); done"],
filters: {
findIP: {
regex: ['^found $'],