Housecarl will be a firewall manager for nftables, built the way Jarl and Steward are: housecarld (a daemon with a REST API and web UI), hc (the CLI) and SQLite. - The logo: a carved Nordic doorway in Jarl's blue (artwork/logo.svg), with a small version without the carvings, lighter on dark backgrounds. scripts/logo.py builds the web UI's copies and scripts/banner.py the README banner. - The HOUSECARL wordmark in Jarl's and Steward's angular letters, with new H, O, U and C (assets/brand.js), and the UI's color tokens. - housecarld and hc print their version; the Makefile, version package, AGENTS.md, CHANGELOG and the design sketch (docs/design.md). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
87 lines
3.8 KiB
Markdown
87 lines
3.8 KiB
Markdown
# Housecarl's design
|
|
|
|
Housecarl is a firewall manager with the same shape as
|
|
[Jarl](https://gitea.adbraun.lol/adam/jarl) and
|
|
[Steward](https://gitea.adbraun.lol/adam/steward): one daemon,
|
|
`housecarld`, that serves a REST API and a web UI; one CLI, `hc`, that
|
|
talks to that API; SQLite for everything. As Jarl does for BIND, it keeps
|
|
a Linux machine's **nftables** ruleset: the database is the source of
|
|
truth, and the ruleset is built from it.
|
|
|
|
This page is the starting sketch. Nothing here is built yet; it records
|
|
what was decided and what is still open.
|
|
|
|
## How a change is made
|
|
|
|
As in Jarl, every change goes through one engine:
|
|
|
|
1. the request becomes a change to a copy of the configuration;
|
|
2. the whole result is validated, with errors that say what is wrong and
|
|
how to fix it;
|
|
3. the ruleset is rendered, and checked with `nft -c -f` (a dry run by
|
|
nftables itself);
|
|
4. it is applied with `nft -f`, which replaces Housecarl's table in one
|
|
transaction: the old rules stay in force until the new ones are all in;
|
|
5. the change, its history entry and the audit record are saved together.
|
|
|
|
Dry runs, history with undo, users, sessions and scoped API tokens come
|
|
from the same machinery, as in Jarl and Steward.
|
|
|
|
### Not locking yourself out
|
|
|
|
A firewall managed over the network can cut off its own manager. Housecarl
|
|
should guard against that from the start:
|
|
|
|
* **Confirm or roll back.** A risky change (one touching the rules for the
|
|
machine itself) is applied on probation: unless it is confirmed within a
|
|
minute or two, from the web UI or `hc confirm`, the previous ruleset comes
|
|
back by itself. Network equipment calls this "commit confirmed".
|
|
* **A management rule that can't be dropped by accident**: the API and SSH
|
|
stay reachable from the management network unless that rule is removed
|
|
on purpose.
|
|
|
|
## Its own table only
|
|
|
|
Housecarl writes one nftables table of its own (`inet housecarl`) and
|
|
leaves every other table alone, such as Docker's or libvirt's. It never
|
|
flushes the whole ruleset.
|
|
|
|
## Concepts (draft)
|
|
|
|
* **Zones**: interfaces or VLANs grouped by trust (`lan`, `iot`, `guest`,
|
|
`wan`), and the default policy between each pair.
|
|
* **Address sets**: named sets of addresses or prefixes, which rules refer
|
|
to. An entry may have a **timeout**, which nftables enforces by itself, so
|
|
an entry expires even if housecarld is down. Other programs can be given
|
|
a token that may only change one set.
|
|
* **Rules** between zones: allow or drop, by set, port and protocol, with
|
|
logging and counters.
|
|
* **NAT**: masquerading out of the WAN zone, and port forwards.
|
|
* **Services**: names for ports (`ssh`, `https`, `dns`).
|
|
|
|
## Working with Steward and Jarl
|
|
|
|
* **Steward's captive portal** (parked until Housecarl exists): a guest
|
|
network's zone lets clients that haven't signed in reach only DNS and
|
|
the portal, and sends their web traffic to it. When a guest signs in,
|
|
Steward adds its address to a set (`guest-signed-in`) for the session's
|
|
length, with a token that may only change that set. Addresses, not MAC
|
|
addresses: matching by MAC only works on the guests' own network
|
|
segment.
|
|
* **Names instead of addresses**: Steward knows every reservation's and
|
|
lease's address, so rules could name a device ("the printer") or a
|
|
subnet; Jarl knows the names in DNS.
|
|
* **Steward's deny lists** could also be enforced here, so a denied device
|
|
with an address set by hand is still blocked.
|
|
|
|
## Open questions
|
|
|
|
* IPv6 from the start, or IPv4 first as Steward did?
|
|
* Import an existing nftables (or iptables) ruleset, as Jarl imports a BIND
|
|
configuration?
|
|
* Connection tracking defaults: established and related traffic allowed
|
|
everywhere unless a rule says otherwise?
|
|
* Does Housecarl also run the router's interfaces and routes, or only the
|
|
firewall? (Only the firewall, for now.)
|
|
* Rate limits and flow offloading: later.
|