# 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. Guard reads it when it starts: `systemctl restart guard` after a change. ## 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: - '^ \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 `` 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. ## 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` |