First persistance work
This commit is contained in:
parent
66c62b5e50
commit
b393ca8252
20
README.md
20
README.md
@ -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
119
app/db.go
Normal 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
5
go.mod
@ -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
2
go.sum
@ -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
25
reaction.service
Normal 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
19
reaction.test.yml
Normal 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
|
26
reaction.yml
26
reaction.yml
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user