Files and the journal, patterns with one <IP>, bounties within a window, bans in a Housecarl ban list with repeat offenders' bans growing; presets for sshd, jarl, housecarl, steward, bind-xfr, postfix-sasl, dovecot, nginx-auth and housecarl-drops; guard run/check/test/status/bans/ban/unban; a Debian package with a sandboxed unit; an end-to-end test with Housecarl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3.7 KiB
Presets and patterns
A preset is a watch Guard knows: its patterns, and where its program's
lines usually are. guard presets lists them; guard presets sshd
shows one's patterns.
watch:
- name: ssh
preset: sshd
ban: {protocol: tcp, port: 22}
- name: ssh-file # the same, from a syslog file
preset: sshd
file: /var/log/auth.log
A watch's own file or journal replaces the preset's source, and its
match and ignore lines are added to the preset's.
| Preset | Source | Matches |
|---|---|---|
sshd |
journal, identifiers sshd, sshd-session |
failed passwords and keys, unknown users, connections closed before login, too many attempts, failed key exchanges, garbage banners |
jarl, housecarl, steward |
journal, units jarl.service, housecarl.service, steward.service |
their failed logins and refused API tokens (msg="login failed" ip=…, text or JSON logs) |
bind-xfr |
journal, identifier named |
zone transfers BIND refused (TCP, so the address is real) |
postfix-sasl |
journal, identifiers postfix/smtpd, postfix/submission/smtpd, postfix/smtps/smtpd |
failed SMTP logins |
dovecot |
journal, identifier dovecot |
failed IMAP, POP3, submission and ManageSieve logins |
nginx-auth |
file /var/log/nginx/error.log |
failed HTTP basic authentication |
housecarl-drops |
Housecarl's log of dropped packets | every TCP packet a logged rule dropped (on the firewall, for scans) |
OpenSSH 9.8 and later (Debian 13) logs logins from sshd-session, not
sshd; the preset reads both. Debian 13 has no /var/log/auth.log
unless rsyslog is installed: the journal is the place.
There's deliberately no preset for BIND's refused queries: they come over UDP, whose source address anyone can fake, so banning on them would let a stranger get your users' resolvers banned. Use BIND's response rate limiting for floods.
Writing patterns
A pattern is a Go regular expression (RE2 syntax)
with <IP> once, where the address is:
match:
- '^<IP> \S+ \S+ \[[^\]]+\] "POST /login[^"]*" 401 '
- 'authentication failure; .* rhost=<IP>(?: |$)'
<IP> matches IPv4 and IPv6 addresses, bracketed ([2001:db8::9]),
IPv4-mapped (::ffff:203.0.113.9, counted as 203.0.113.9) or with a
zone. The text it matches must be a real address: 12:34:56 isn't one.
Patterns match anywhere in a line unless anchored. A line from a syslog
file starts with its date, host and program (Oct 22 10:00:01 web sshd[123]: ); a journal entry is the message alone. The presets begin
with (?:^|: ) so they work on both.
Where the address is matters. Many log lines carry text a client chose: a user name, a URL, a reverse DNS name. If a pattern lets that text supply the address, a client can get any address banned:
Failed password for invalid user x from 198.51.100.1 port 22 from 203.0.113.9 port 4444 ssh2
Anchor patterns at the end of the line, or put <IP> where the
program writes the address after the client's text, so it is the
program's address that matches. The presets do; guard test shows
what yours find. Programs that log the address first (Jarl, Housecarl,
Steward log msg="login failed" ip=… user=…) are the easiest to match
safely.
Trying patterns: guard test
guard test web-login /var/log/nginx/access.log.1
guard test ssh # a journal watch: its last 5000 entries
zcat /var/log/nginx/access.log.2.gz | guard test web-login -
It prints each matching line with its address, then the addresses that would be banned. Nothing is banned. Lines from a file carry no time Guard reads, so every match counts however far apart; the journal's entries have times, and the watch's window is used.