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 -D INPUT -p all -j reaction
|
||||||
ExecStopPost=/path/to/iptables -w -F reaction
|
ExecStopPost=/path/to/iptables -w -F reaction
|
||||||
ExecStopPost=/path/to/iptables -w -X 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
|
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/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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:
|
definitions:
|
||||||
- &iptablesban iptables -I reaction 1 -s <ip> -j block
|
- &iptablesban [ "iptables" "-w" "-I" "reaction" "1" "-s" "<ip>" "-j" "block" ]
|
||||||
- &iptablesunban iptables -D reaction 1 -s <ip> -j block
|
- &iptablesunban [ "iptables" "-w" "-D" "reaction" "1" "-s" "<ip>" "-j" "block" ]
|
||||||
|
|
||||||
patterns:
|
patterns:
|
||||||
ip: '(([0-9]{1,3}\.){3}[0-9]{1,3})|([0-9a-fA-F:]{2,90})'
|
ip: '(([0-9]{1,3}\.){3}[0-9]{1,3})|([0-9a-fA-F:]{2,90})'
|
||||||
|
|
||||||
streams:
|
streams:
|
||||||
tailDown:
|
ssh:
|
||||||
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" ]
|
cmd: [ "journalctl" "-fu" "sshd.service" ]
|
||||||
filters:
|
filters:
|
||||||
findIP:
|
failedlogin:
|
||||||
regex:
|
regex:
|
||||||
- found <ip>
|
- authentication failure;.*rhost=<ip>
|
||||||
retry: 2
|
retry: 3
|
||||||
retry-period: 5s
|
retry-period: 6h
|
||||||
actions:
|
actions:
|
||||||
damn:
|
ban:
|
||||||
cmd: [ "echo", "<ip>" ]
|
cmd: *iptablesban
|
||||||
sleepdamn:
|
unban:
|
||||||
cmd: [ "echo", "sleep", "<ip>" ]
|
cmd: *iptablesunban
|
||||||
after: 1s
|
after: 2d
|
||||||
|
Loading…
Reference in New Issue
Block a user