Implement start/stop commands
fix #41 update README and configuration files accordingly
This commit is contained in:
parent
d35167b878
commit
345dd94b17
44
README.md
44
README.md
@ -27,12 +27,23 @@ see [reaction.service](./config/reaction.service) and [reaction.yml](./app/react
|
|||||||
`/etc/reaction.yml`
|
`/etc/reaction.yml`
|
||||||
```yaml
|
```yaml
|
||||||
definitions:
|
definitions:
|
||||||
- &iptablesban [ "iptables" "-w" "-I" "reaction" "1" "-s" "<ip>" "-j" "block" ]
|
- &iptablesban [ "ip46tables" "-w" "-I" "reaction" "1" "-s" "<ip>" "-j" "block" ]
|
||||||
- &iptablesunban [ "iptables" "-w" "-D" "reaction" "1" "-s" "<ip>" "-j" "block" ]
|
- &iptablesunban [ "ip46tables" "-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})'
|
||||||
|
|
||||||
|
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:
|
streams:
|
||||||
ssh:
|
ssh:
|
||||||
cmd: [ "journalctl" "-fu" "sshd.service" ]
|
cmd: [ "journalctl" "-fu" "sshd.service" ]
|
||||||
@ -54,14 +65,26 @@ jsonnet is also supported:
|
|||||||
|
|
||||||
`/etc/reaction.jsonnet`
|
`/etc/reaction.jsonnet`
|
||||||
```jsonnet
|
```jsonnet
|
||||||
local iptablesban = ['iptables', '-w', '-A', 'reaction', '1', '-s', '<ip>', '-j', 'DROP'];
|
local iptables(args) = [ 'ip46tables', '-w' ] + args;
|
||||||
local iptablesunban = ['iptables', '-w', '-D', 'reaction', '1', '-s', '<ip>', '-j', 'DROP'];
|
local iptablesban(ip) = iptables([ '-A', 'reaction', '1', '-s', ip, '-j', 'DROP' ]);
|
||||||
|
local iptablesunban(ip) = iptables([ '-D', 'reaction', '1', '-s', ip, '-j', 'DROP' ]);
|
||||||
{
|
{
|
||||||
patterns: {
|
patterns: {
|
||||||
ip: {
|
ip: {
|
||||||
regex: @'(?:(?:[ 0-9 ]{1,3}\.){3}[0-9]{1,3})|(?:[0-9a-fA-F:]{2,90})',
|
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: {
|
streams: {
|
||||||
ssh: {
|
ssh: {
|
||||||
cmd: [ 'journalctl', '-fu', 'sshd.service' ],
|
cmd: [ 'journalctl', '-fu', 'sshd.service' ],
|
||||||
@ -72,10 +95,10 @@ local iptablesunban = ['iptables', '-w', '-D', 'reaction', '1', '-s', '<ip>', '-
|
|||||||
retryperiod: '6h',
|
retryperiod: '6h',
|
||||||
actions: {
|
actions: {
|
||||||
ban: {
|
ban: {
|
||||||
cmd: iptablesban,
|
cmd: iptablesban('<ip>'),
|
||||||
},
|
},
|
||||||
unban: {
|
unban: {
|
||||||
cmd: iptablesunban,
|
cmd: iptablesunban('<ip>'),
|
||||||
after: '48h',
|
after: '48h',
|
||||||
onexit: true,
|
onexit: true,
|
||||||
},
|
},
|
||||||
@ -97,15 +120,6 @@ WantedBy=multi-user.target
|
|||||||
[ Service ]
|
[ Service ]
|
||||||
ExecStart=/path/to/reaction -c /etc/reaction.yml
|
ExecStart=/path/to/reaction -c /etc/reaction.yml
|
||||||
|
|
||||||
ExecStartPre=/path/to/iptables -w -N reaction
|
|
||||||
ExecStartPre=/path/to/iptables -w -A reaction -j ACCEPT
|
|
||||||
ExecStartPre=/path/to/iptables -w -I reaction 1 -s 127.0.0.1 -j ACCEPT
|
|
||||||
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
|
StateDirectory=reaction
|
||||||
RuntimeDirectory=reaction
|
RuntimeDirectory=reaction
|
||||||
WorkingDirectory=/var/lib/reaction
|
WorkingDirectory=/var/lib/reaction
|
||||||
|
@ -39,6 +39,15 @@ func cmdStdout(commandline []string) chan *string {
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runCommands(commands [][]string, moment string) {
|
||||||
|
for _, command := range commands {
|
||||||
|
cmd := exec.Command(command[0], command[1:]...)
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
logger.Printf(logger.ERROR, "couldn't execute %v command: %v", moment, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Pattern) notAnIgnore(match *string) bool {
|
func (p *Pattern) notAnIgnore(match *string) bool {
|
||||||
for _, ignore := range p.Ignore {
|
for _, ignore := range p.Ignore {
|
||||||
if ignore == *match {
|
if ignore == *match {
|
||||||
@ -323,6 +332,8 @@ func Daemon(confFilename string) {
|
|||||||
actions = make(ActionsMap)
|
actions = make(ActionsMap)
|
||||||
matches = make(MatchesMap)
|
matches = make(MatchesMap)
|
||||||
|
|
||||||
|
runCommands(conf.Start, "start")
|
||||||
|
|
||||||
go DatabaseManager(conf)
|
go DatabaseManager(conf)
|
||||||
go MatchesManager()
|
go MatchesManager()
|
||||||
go ActionsManager()
|
go ActionsManager()
|
||||||
@ -348,16 +359,16 @@ func Daemon(confFilename string) {
|
|||||||
logger.Printf(logger.ERROR, "%s stream finished", finishedStream.name)
|
logger.Printf(logger.ERROR, "%s stream finished", finishedStream.name)
|
||||||
nbStreamsInExecution--
|
nbStreamsInExecution--
|
||||||
if nbStreamsInExecution == 0 {
|
if nbStreamsInExecution == 0 {
|
||||||
quit()
|
quit(conf)
|
||||||
}
|
}
|
||||||
case <-sigs:
|
case <-sigs:
|
||||||
logger.Printf(logger.INFO, "Received SIGINT/SIGTERM, exiting")
|
logger.Printf(logger.INFO, "Received SIGINT/SIGTERM, exiting")
|
||||||
quit()
|
quit(conf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func quit() {
|
func quit(conf *Conf) {
|
||||||
// send stop to StreamManager·s
|
// send stop to StreamManager·s
|
||||||
close(stopStreams)
|
close(stopStreams)
|
||||||
logger.Println(logger.INFO, "Waiting for Streams to finish...")
|
logger.Println(logger.INFO, "Waiting for Streams to finish...")
|
||||||
@ -369,6 +380,8 @@ func quit() {
|
|||||||
// stop all actions
|
// stop all actions
|
||||||
logger.Println(logger.INFO, "Waiting for Actions to finish...")
|
logger.Println(logger.INFO, "Waiting for Actions to finish...")
|
||||||
wgActions.Wait()
|
wgActions.Wait()
|
||||||
|
// run stop commands
|
||||||
|
runCommands(conf.Stop, "stop")
|
||||||
// delete pipe
|
// delete pipe
|
||||||
err := os.Remove(*SocketPath)
|
err := os.Remove(*SocketPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
# using YAML anchors `&name` and pointers `*name`
|
# using YAML anchors `&name` and pointers `*name`
|
||||||
# definitions are not readed by reaction
|
# definitions are not readed by reaction
|
||||||
definitions:
|
definitions:
|
||||||
- &iptablesban [ "ip46tables" "-w" "-A" "reaction" "1" "-s" "<ip>" "-j" "DROP" ]
|
- &iptablesban [ "ip46tables", "-w", "-A", "reaction", "1", "-s", "<ip>", "-j", "DROP" ]
|
||||||
- &iptablesunban [ "ip46tables" "-w" "-D" "reaction" "1" "-s" "<ip>" "-j" "DROP" ]
|
- &iptablesunban [ "ip46tables", "-w", "-D", "reaction", "1", "-s", "<ip>", "-j", "DROP" ]
|
||||||
# ip46tables is a minimal C program (only POSIX dependencies) present as a subdirectory.
|
# ip46tables is a minimal C program (only POSIX dependencies) present as a subdirectory.
|
||||||
# it permits to handle both ipv4/iptables and ipv6/ip6tables commands
|
# it permits to handle both ipv4/iptables and ipv6/ip6tables commands
|
||||||
|
|
||||||
@ -18,6 +18,20 @@ patterns:
|
|||||||
- 127.0.0.1
|
- 127.0.0.1
|
||||||
- ::1
|
- ::1
|
||||||
|
|
||||||
|
# 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" ]
|
||||||
|
|
||||||
|
# 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" ]
|
||||||
|
|
||||||
|
|
||||||
# streams are commands
|
# streams are commands
|
||||||
# they're run and their ouptut is captured
|
# they're run and their ouptut is captured
|
||||||
# *example:* `tail -f /var/log/nginx/access.log`
|
# *example:* `tail -f /var/log/nginx/access.log`
|
||||||
@ -27,7 +41,7 @@ streams:
|
|||||||
ssh:
|
ssh:
|
||||||
# note that if the command is not in environment's `PATH`
|
# note that if the command is not in environment's `PATH`
|
||||||
# its full path must be given.
|
# 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 run actions when they match regexes on a stream
|
||||||
filters:
|
filters:
|
||||||
# filters have a user-defined name
|
# filters have a user-defined name
|
||||||
|
@ -10,6 +10,8 @@ import (
|
|||||||
type Conf struct {
|
type Conf struct {
|
||||||
Patterns map[string]*Pattern `json:"patterns"`
|
Patterns map[string]*Pattern `json:"patterns"`
|
||||||
Streams map[string]*Stream `json:"streams"`
|
Streams map[string]*Stream `json:"streams"`
|
||||||
|
Start [][]string `json:"start"`
|
||||||
|
Stop [][]string `json:"stop"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pattern struct {
|
type Pattern struct {
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
local directory = '~/.local/share/watch';
|
|
||||||
// Those strings will be substitued in each shell() call
|
// Those strings will be substitued in each shell() call
|
||||||
local substitutions = [
|
local substitutions = [
|
||||||
['OUTFILE', directory + '/logs-$(date %+F)'],
|
['OUTFILE', '"$HOME/.local/share/watch/logs-$(date +%F)"'],
|
||||||
['TMUXFILE', directory + '/tmux'],
|
|
||||||
['DATE', '"$(date "+%F %T")"'],
|
['DATE', '"$(date "+%F %T")"'],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -18,19 +16,17 @@ local shell(prg) = [
|
|||||||
sub(prg),
|
sub(prg),
|
||||||
];
|
];
|
||||||
|
|
||||||
{
|
local log(line) = shell('echo DATE ' + std.strReplace(line, '\n', ' ') + '>> OUTFILE');
|
||||||
// Startup is currently not implemented
|
|
||||||
startup: shell(|||
|
|
||||||
mkdir -p "$(dirname OUTFILE)"
|
|
||||||
echo DATE start >> OUTFILE
|
|
||||||
# tmux set-hook -g pane-focus-in[50] new-session -d 'echo tmux >> TMUXFILE'
|
|
||||||
|||),
|
|
||||||
|
|
||||||
// Stop is currently not implemented
|
{
|
||||||
stop: shell(|||
|
start: [
|
||||||
tmux set-hook -ug pane-focus-in[50]
|
shell('mkdir -p "$(dirname OUTFILE)"'),
|
||||||
echo DATE stop >> OUTFILE
|
log('start'),
|
||||||
|||),
|
],
|
||||||
|
|
||||||
|
stop: [
|
||||||
|
log('stop'),
|
||||||
|
],
|
||||||
|
|
||||||
patterns: {
|
patterns: {
|
||||||
all: { regex: '.*' },
|
all: { regex: '.*' },
|
||||||
@ -47,7 +43,7 @@ local shell(prg) = [
|
|||||||
send: {
|
send: {
|
||||||
regex: ['^<all>$'],
|
regex: ['^<all>$'],
|
||||||
actions: {
|
actions: {
|
||||||
send: { cmd: shell('echo DATE focus <all> >> OUTFILE') },
|
send: { cmd: log('focus <all>') },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -55,12 +51,13 @@ local shell(prg) = [
|
|||||||
|
|
||||||
// Be notified when user is away
|
// Be notified when user is away
|
||||||
swayidle: {
|
swayidle: {
|
||||||
cmd: ['swayidle', 'timeout', '60', 'echo sleep', 'resume', 'echo resume'],
|
// FIXME echo stop and start instead?
|
||||||
|
cmd: ['swayidle', 'timeout', '30', 'echo sleep', 'resume', 'echo resume'],
|
||||||
filters: {
|
filters: {
|
||||||
send: {
|
send: {
|
||||||
regex: ['^<all>$'],
|
regex: ['^<all>$'],
|
||||||
actions: {
|
actions: {
|
||||||
send: { cmd: shell('echo DATE <all> >> OUTFILE') },
|
send: { cmd: log('<all>') },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -92,7 +89,7 @@ local shell(prg) = [
|
|||||||
// send: {
|
// send: {
|
||||||
// regex: ['^tmux <all>$'],
|
// regex: ['^tmux <all>$'],
|
||||||
// actions: {
|
// actions: {
|
||||||
// send: { cmd: shell('echo DATE tmux <all> >> OUTFILE') },
|
// send: { cmd: log('tmux <all>') },
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
// This file is using JSONNET, a complete configuration language based on JSON
|
// This file is using JSONNET, a complete configuration language based on JSON
|
||||||
// See https://jsonnet.org
|
// See https://jsonnet.org
|
||||||
// JSONNET is a superset of JSON, so one can write plain JSON files if wanted.
|
// JSONNET is a superset of JSON, so one can write plain JSON files if wanted.
|
||||||
// Note that YAML is also supported.
|
// Note that YAML is also supported, see ./example.yml
|
||||||
|
|
||||||
|
// A JSONNET function
|
||||||
|
local iptables(args) = ['ip46tables', '-w'] + args;
|
||||||
// variables defined for later use.
|
// variables defined for later use.
|
||||||
local iptablesban = ['ip46tables', '-w', '-A', 'reaction', '1', '-s', '<ip>', '-j', 'DROP'];
|
local iptablesban = iptables(['-A', 'reaction', '1', '-s', '<ip>', '-j', 'drop']);
|
||||||
local iptablesunban = ['ip46tables', '-w', '-D', 'reaction', '1', '-s', '<ip>', '-j', 'DROP'];
|
local iptablesunban = iptables(['-D', 'reaction', '1', '-s', '<ip>', '-j', 'drop']);
|
||||||
// ip46tables is a minimal C program (only POSIX dependencies) present as a subdirectory.
|
// ip46tables is a minimal C program (only POSIX dependencies) present as a subdirectory.
|
||||||
// it permits to handle both ipv4/iptables and ipv6/ip6tables commands
|
// it permits to handle both ipv4/iptables and ipv6/ip6tables commands
|
||||||
|
|
||||||
@ -21,6 +23,30 @@ local iptablesunban = ['ip46tables', '-w', '-D', 'reaction', '1', '-s', '<ip>',
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Those commands will be executed in order at start, before everything else
|
||||||
|
start: [
|
||||||
|
// Create an iptables chain for reaction
|
||||||
|
iptables(['-N', 'reaction']),
|
||||||
|
// Set its default to ACCEPT
|
||||||
|
iptables(['-A', 'reaction', '-j', 'ACCEPT']),
|
||||||
|
// Always accept 127.0.0.1
|
||||||
|
iptables(['-I', 'reaction', '1', '-s', '127.0.0.1', '-j', 'ACCEPT']),
|
||||||
|
// Always accept ::1
|
||||||
|
iptables(['-I', 'reaction', '1', '-s', '::1', '-j', 'ACCEPT']),
|
||||||
|
// Insert this chain as the first item of the INPUT chain (for incoming connections)
|
||||||
|
iptables(['-I', 'INPUT', '-p', 'all', '-j', 'reaction']),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Those commands will be executed in order at stop, after everything else
|
||||||
|
stop: [
|
||||||
|
// Remove the chain from the INPUT chain
|
||||||
|
iptables(['-D,', 'INPUT', '-p', 'all', '-j', 'reaction']),
|
||||||
|
// Empty the chain
|
||||||
|
iptables(['-F,', 'reaction']),
|
||||||
|
// Delete the chain
|
||||||
|
iptables(['-X,', 'reaction']),
|
||||||
|
],
|
||||||
|
|
||||||
// streams are commands
|
// streams are commands
|
||||||
// they're run and their ouptut is captured
|
// they're run and their ouptut is captured
|
||||||
// *example:* `tail -f /var/log/nginx/access.log`
|
// *example:* `tail -f /var/log/nginx/access.log`
|
||||||
|
@ -6,24 +6,6 @@ WantedBy=multi-user.target
|
|||||||
[Service]
|
[Service]
|
||||||
ExecStart=/path/to/reaction -c /etc/reaction.yml
|
ExecStart=/path/to/reaction -c /etc/reaction.yml
|
||||||
|
|
||||||
# Create an iptables chain for reaction
|
|
||||||
ExecStartPre=/path/to/ip46tables -w -N reaction
|
|
||||||
# Set its default to ACCEPT
|
|
||||||
ExecStartPre=/path/to/ip46tables -w -A reaction -j ACCEPT
|
|
||||||
# Always accept 127.0.0.1
|
|
||||||
ExecStartPre=/path/to/ip46tables -w -I reaction 1 -s 127.0.0.1 -j ACCEPT
|
|
||||||
# Always accept ::1
|
|
||||||
ExecStartPre=/path/to/ip46tables -w -I reaction 1 -s ::1 -j ACCEPT
|
|
||||||
# Insert this chain as the first item of the INPUT chain (for incoming connections)
|
|
||||||
ExecStartPre=/path/to/ip46tables -w -I INPUT -p all -j reaction
|
|
||||||
|
|
||||||
# Remove the chain from the INPUT chain
|
|
||||||
ExecStopPost=/path/to/ip46tables -w -D INPUT -p all -j reaction
|
|
||||||
# Empty the chain
|
|
||||||
ExecStopPost=/path/to/ip46tables -w -F reaction
|
|
||||||
# Delete the chain
|
|
||||||
ExecStopPost=/path/to/ip46tables -w -X reaction
|
|
||||||
|
|
||||||
# Ask systemd to create /var/lib/reaction (/var/lib/ is implicit)
|
# Ask systemd to create /var/lib/reaction (/var/lib/ is implicit)
|
||||||
StateDirectory=reaction
|
StateDirectory=reaction
|
||||||
# Ask systemd to create /run/reaction at runtime (/run/ is implicit)
|
# Ask systemd to create /run/reaction at runtime (/run/ is implicit)
|
||||||
|
@ -69,8 +69,10 @@ func Printf(level Level, format string, args ...any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Fatalln(args ...any) {
|
func Fatalln(args ...any) {
|
||||||
level := FATAL
|
newargs := make([]any, 0)
|
||||||
log.Fatalln(level.String(), args)
|
newargs = append(newargs, FATAL)
|
||||||
|
newargs = append(newargs, args...)
|
||||||
|
log.Fatalln(newargs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Fatalf(format string, args ...any) {
|
func Fatalf(format string, args ...any) {
|
||||||
|
Loading…
Reference in New Issue
Block a user