First persistance work

This commit is contained in:
ppom 2023-04-11 13:01:02 +02:00
parent 66c62b5e50
commit b393ca8252
7 changed files with 202 additions and 14 deletions

View file

@ -60,4 +60,24 @@ ExecStartPre=/path/to/iptables -w -I INPUT -p all -j reaction
ExecStopPost=/path/to/iptables -w -D INPUT -p all -j reaction
ExecStopPost=/path/to/iptables -w -F reaction
ExecStopPost=/path/to/iptables -w -X reaction
StateDirectory=reaction
WorkingDirectory=/var/lib/reaction
```
See [reaction.service](./reaction.service) and [reaction.yml](./reaction.yml) for the fully commented examples.
## documentation
### configuration reference
`cmd`: note that if program is not in environment's `PATH`, the full path to the command should be given.
`/etc/systemd/system/reaction.service` (again, commented)
```systemd
```
### implicit configuration
the working directory of `reaction` will be used to create and read from the embedded [lmdb](https://www.symas.com/lmdb) database.
if you don't know where to start it, `/var/lib/reaction` should be a sane choice.

119
app/db.go Normal file
View file

@ -0,0 +1,119 @@
package app
import (
"fmt"
"log"
"runtime"
"time"
"github.com/bmatsuo/lmdb-go/lmdb"
)
func numberOfFilters(conf *Conf) int {
n := 0
for _, s := range conf.Streams {
n += len(s.Filters)
}
return n
}
type CmdTime struct {
cmd []string
t time.Time
}
// Remove Cmd if last of its set
type CmdExecuted struct {
filter *Filter
pattern *string
value CmdTime
err chan error
}
// Append Cmd set
type AppendCmd struct {
filter *Filter
pattern *string
value []CmdTime
err chan error
}
// Append match, remove old ones and check match number
type AppendMatch struct {
filter *Filter
pattern *string
t time.Time
ret chan struct {
shouldExec bool
err error
}
}
func databaseHandler(env *lmdb.Env, chCE chan CmdExecuted, chAC chan AppendCmd, chAM chan AppendMatch) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
defer env.Close()
select {
case ce := <-chCE:
ce = ce
// TODO
case ac := <-chAC:
ac = ac
// TODO
case am := <-chAM:
am = am
// TODO
}
}
func initDatabase(conf *Conf) (chan CmdExecuted, chan AppendCmd, chan AppendMatch) {
env, err := lmdb.NewEnv()
if err != nil {
log.Fatalln("LMDB.NewEnv failed")
}
err = env.SetMapSize(1 << 30)
if err != nil {
log.Fatalln("LMDB.SetMapSize failed")
}
filterNumber := numberOfFilters(conf)
err = env.SetMaxDBs(filterNumber * 2)
if err != nil {
log.Fatalln("LMDB.SetMaxDBs failed")
}
matchDBs := make(map[*Filter]lmdb.DBI, filterNumber)
cmdDBs := make(map[*Filter]lmdb.DBI, filterNumber)
runtime.LockOSThread()
for _, stream := range conf.Streams {
for _, filter := range stream.Filters {
err = env.UpdateLocked(func(txn *lmdb.Txn) (err error) {
matchDBs[filter], err = txn.CreateDBI(fmt.Sprintln("%s.%s.match", stream.name, filter.name))
if err != nil {
return err
}
cmdDBs[filter], err = txn.CreateDBI(fmt.Sprintln("%s.%s.cmd", stream.name, filter.name))
return err
})
if err != nil {
log.Fatalln("LMDB.CreateDBI failed")
}
}
}
runtime.UnlockOSThread()
chCE := make(chan CmdExecuted)
chAC := make(chan AppendCmd)
chAM := make(chan AppendMatch)
go databaseHandler(env, chCE, chAC, chAM)
return chCE, chAC, chAM
}

5
go.mod
View file

@ -2,4 +2,7 @@ module reaction
go 1.19
require gopkg.in/yaml.v3 v3.0.1 // indirect
require (
github.com/bmatsuo/lmdb-go v1.8.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

2
go.sum
View file

@ -1,3 +1,5 @@
github.com/bmatsuo/lmdb-go v1.8.0 h1:ohf3Q4xjXZBKh4AayUY4bb2CXuhRAI8BYGlJq08EfNA=
github.com/bmatsuo/lmdb-go v1.8.0/go.mod h1:wWPZmKdOAZsl4qOqkowQ1aCrFie1HU8gWloHMCeAUdM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

25
reaction.service Normal file
View file

@ -0,0 +1,25 @@
# vim: ft=systemd
[Unit]
WantedBy=multi-user.target
[Service]
ExecStart=/path/to/reaction -c /etc/reaction.yml
# Create an iptables chain for reaction
ExecStartPre=/path/to/iptables -w -N reaction
# Set its default to ACCEPT
ExecStartPre=/path/to/iptables -w -A reaction -j ACCEPT
# Insert this chain as the first item of the INPUT chain (for incoming connections)
ExecStartPre=/path/to/iptables -w -I INPUT -p all -j reaction
# Remove the chain from the INPUT chain
ExecStopPost=/path/to/iptables -w -D INPUT -p all -j reaction
# Empty the chain
ExecStopPost=/path/to/iptables -w -F reaction
# Delete te chain
ExecStopPost=/path/to/iptables -w -X reaction
# Ask systemd to create /var/lib/reaction (/var/lib/ is implicit)
StateDirectory=reaction
# Start reaction in its state directory
WorkingDirectory=/var/lib/reaction

19
reaction.test.yml Normal file
View file

@ -0,0 +1,19 @@
---
patterns:
ip: '(([0-9]{1,3}\.){3}[0-9]{1,3})|([0-9a-fA-F:]{2,90})'
streams:
tailDown:
cmd: [ "sh", "-c", "echo 'found 1.1.1.1' && sleep 2s && echo 'found 1.1.1.2' && sleep 2s && echo 'found 1.1.1.1' && sleep 1s" ]
filters:
findIP:
regex:
- found <ip>
retry: 2
retry-period: 5s
actions:
damn:
cmd: [ "echo", "<ip>" ]
sleepdamn:
cmd: [ "echo", "sleep", "<ip>" ]
after: 1s

View file

@ -1,23 +1,23 @@
---
definitions:
- &iptablesban iptables -I reaction 1 -s <ip> -j block
- &iptablesunban iptables -D reaction 1 -s <ip> -j block
- &iptablesban [ "iptables" "-w" "-I" "reaction" "1" "-s" "<ip>" "-j" "block" ]
- &iptablesunban [ "iptables" "-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})'
streams:
tailDown:
cmd: [ "sh", "-c", "echo 'found 1.1.1.1' && sleep 2s && echo 'found 1.1.1.2' && sleep 2s && echo 'found 1.1.1.1' && sleep 1s" ]
ssh:
cmd: [ "journalctl" "-fu" "sshd.service" ]
filters:
findIP:
failedlogin:
regex:
- found <ip>
retry: 2
retry-period: 5s
- authentication failure;.*rhost=<ip>
retry: 3
retry-period: 6h
actions:
damn:
cmd: [ "echo", "<ip>" ]
sleepdamn:
cmd: [ "echo", "sleep", "<ip>" ]
after: 1s
ban:
cmd: *iptablesban
unban:
cmd: *iptablesunban
after: 2d