Files
guard/docs/configuration.md
abrown38andClaude Opus 5.5 ecf4129877 Reload guard.yaml without losing the bounties
systemctl reload guard, guard reload or SIGHUP reads the configuration
again: a watch keeps its bounties and counts under the same name and
takes its new settings at once; its source starts again only if it
changed; watches gone stop, new ones start; a file with a mistake is
refused. The Housecarl connection, state, socket and log wait for a
restart, and guard reload says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 01:32:20 +00:00

151 lines
6.5 KiB
Markdown

# Configuration
`/etc/guard/guard.yaml` is Guard's whole configuration. `guard config
--example` prints a commented one. Unknown settings are an error;
`guard check` checks the file, compiles every pattern and asks Housecarl
for the ban lists. After a change, `systemctl reload guard` (or `guard
reload`) puts it to work without losing what Guard has counted (see
[Reloading](#reloading)).
## housecarl
| Setting | | |
|---|---|---|
| `url` | | Housecarl's API: `https://router.lan:8070` |
| `socket` | | or its local socket, on the firewall: `/run/housecarl/housecarl.sock` |
| `token_file` (or `token`) | | the API token: one for the ban lists Guard bans into (`bans:write:LIST`) |
| `ca_cert` | | a PEM file with the CA of Housecarl's certificate, if private |
| `insecure` | `false` | don't check Housecarl's certificate |
| `list` | | the ban list a watch bans into unless it names another |
## never_ban
Addresses, prefixes and host names never banned, whatever the logs say:
```yaml
never_ban: [198.51.100.7, 2001:db8:ffff::/48, vps.example.org]
```
Host names are looked up when Guard starts and every ten minutes; if a
lookup fails, the last addresses are kept. Loopback, link-local and
multicast addresses are never banned either. Housecarl has its own list
too: it never bans the inside networks, the firewall's own addresses,
the machine asking, or a ban list's `never`.
## defaults
| Setting | Default | |
|---|---|---|
| `hits` | 5 | an address is banned once it matches this many times… |
| `within` | 10m | …within this long |
| `ban` | 1h | how long a first ban lasts |
| `repeat.factor` | 2 | each earlier ban of the address multiplies the time by this (1: bans don't grow) |
| `repeat.max` | 1w | the longest ban |
| `repeat.forget` | 1w | earlier bans older than this don't count |
Times are written `30s`, `10m`, `1h`, `1d`, `1w`, or combined (`1d12h`).
Housecarl cuts a ban longer than its list's longest.
## watch
Each watch is a log, its patterns, and what a ban is:
```yaml
watch:
- name: ssh
preset: sshd
ban: {protocol: tcp, port: 22}
- name: web-login
file: /var/log/nginx/access.log
match:
- '^<IP> \S+ \S+ \[[^\]]+\] "POST /login[^"]*" 401 '
ignore: ['/healthz']
hits: 10
within: 5m
ban: {list: web, time: 6h}
```
| Setting | | |
|---|---|---|
| `name` | | the watch's name: letters, digits, `-`, `_`, `.` |
| `preset` | | built-in patterns and their usual source ([presets.md](presets.md)) |
| `file` | | a log file, or a glob (`/var/log/nginx/*access.log`) |
| `journal` | | `{unit: ssh.service}` and/or `{identifier: sshd}`; each a name or a list. Entries of any of the units and of any of the identifiers |
| `source` | | `housecarl-drops`: Housecarl's log of dropped packets |
| `match` | | patterns (Go regular expressions) with one `<IP>` each; added to a preset's |
| `ignore` | | lines matching any of these are skipped, even when they match |
| `hits`, `within` | defaults | |
| `ban.list` | `housecarl.list` | the ban list |
| `ban.protocol`, `ban.port` | every port | `tcp` or `udp`, and a port or service name, or a list (`[http, https]`) |
| `ban.time` | `defaults.ban` | a first ban's length |
| `protocol` | `tcp` | `housecarl-drops` only: `tcp`, `udp` or `any`. UDP sources can be faked, so a scanner could get someone else banned |
| `shadow` | `false` | `housecarl-drops` only: count what shadow mode would have dropped |
| `disabled` | `false` | skip the watch |
A watch has one source: `file`, `journal` or `source`. A preset brings
its own, which a `file` or `journal` given replaces.
### How lines are read
* **Files** are followed from their end when Guard starts: only new
lines count. A file that appears later is read from its start. A
rotated file (renamed, and a new one made) is read to its end, then
the new one from its start; a truncated one (logrotate's
`copytruncate`) is noticed while it is shorter than what was read, and
read again from its start. Each line counts at the time Guard reads
it: Guard parses no dates.
* **The journal** is read through `journalctl --follow`, from now on,
with each entry's own time. If journalctl ends, Guard starts it again
after the last entry it saw. The guard user needs the
`systemd-journal` group (the package's unit gives it).
* **Housecarl's log** is read every two seconds (`GET /log`): each
packet a logged rule (or a policy, with `log_policy_drops`) dropped or
rejected counts once. The token needs `log:read`.
### Bounties and bans
Each match adds one to the address's bounty in that watch; matches older
than `within` drop out. When the bounty reaches `hits`, Guard bans the
address and its bounty starts again from nothing. IPv6 addresses are
counted, and banned, by the prefix the ban list says (its `group_v6`,
/64 by default), so an attacker can't step around a ban inside their
/64.
A ban goes to Housecarl at once. If Housecarl can't be reached, it waits
(in the state file, so a restart doesn't lose it) and is sent again
every 30 seconds, for the time it has left. A ban Housecarl refuses (an
address it never bans, a list the token doesn't cover) isn't sent again:
`guard status` and the log say why.
## Reloading
`systemctl reload guard`, `guard reload` or a SIGHUP makes the running
Guard read guard.yaml again. `guard reload` checks the file first and
shows what changed; a file with a mistake is refused, and Guard goes on
as it was.
* A watch is known by its **name**: one that keeps its name keeps its
bounties and counts, and takes its new patterns, hits, window and ban
at once. If its source changed (another file, journal unit, …), the
source starts again; a file is then followed from its end, as when
Guard starts.
* Watches gone (or disabled) stop; new ones start.
* `never_ban` and `defaults.repeat` apply to the next matches and bans.
Bans already made stay as they are, in Housecarl.
* `housecarl`'s connection (`url`, `socket`, the token, `ca_cert`,
`insecure`), `state`, `socket` and `log` only change on a restart:
`guard reload` says so. `housecarl.list` reloads (it only fills in the
watches' lists).
Changing `hits` doesn't ban at once: an address over the new hits is
banned on its next match.
## state, socket, log
| Setting | Default | |
|---|---|---|
| `state` | `/var/lib/guard/state.json` | repeat offenders, recent bans, bans waiting for Housecarl |
| `socket` | `/run/guard/guard.sock` | the local socket `guard status` asks; root and the guard user |
| `log.level` | `info` | `debug` also logs every match with its bounty |
| `log.format` | `text` | or `json` |