Files
housecarl/docs/design.md
T
abrown38andClaude Opus 5.5 54beb0bba3 Start Housecarl: the logo, the wordmark and the skeleton
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>
2026-10-18 21:20:05 +01:00

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.