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>
151 lines
6.5 KiB
Markdown
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` |
|