Build Housecarl 0.1.0: the nftables firewall manager
Zones, address sets (IPv4 and IPv6 mixed, dynamic sets with timeouts) and services, ordered rules in the early, input, forward and output chains, port forwards with hairpin NAT and IPv6 pinholes, source NAT, redirects, and switchable built-ins (loopback, conntrack, ICMPv6, antispoofing, MSS clamping, DHCPv6 client, flowtable). Every change is validated, checked with nft -c and applied in one transaction to Housecarl's own table, inet housecarl; changes made over the network roll back unless confirmed, and the confirmed ruleset is loaded at boot by housecarl-ruleset.service. An empty configuration applies nothing. Also: hit counters and the drop log (nflog), 'hc import' of an existing nftables ruleset and 'hc takeover', the hc CLI, the REST API and the web UI, users, scoped tokens (sets:write:NAME), the audit log, the web UI's certificate through Jarl, Debian packages, docs, unit tests with real nft 1.1.3, and an e2e test with real traffic (71 checks). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
54beb0bba3
commit
34292eeebb
118 files changed
+22165
-114
No files matched your search
@@ -0,0 +1,85 @@
|
||||
# Connecting Housecarl to Jarl
|
||||
|
||||
Housecarl uses [Jarl](https://gitea.adbraun.lol/adam/jarl), the BIND
|
||||
manager, for its DNS records. In this version that means one thing: the
|
||||
DNS-01 challenge for the web UI's certificate ([certificates.md](certificates.md)).
|
||||
Naming hosts in rules by their DNS names may use the same connection later.
|
||||
|
||||
Housecarl talks to Jarl's REST API with an API token. Give it a **user of its
|
||||
own**, with **only the permission it needs**.
|
||||
|
||||
## 1. A user for Housecarl, in Jarl
|
||||
|
||||
On the Jarl server, make a user without a password. It can't log in to
|
||||
Jarl's web UI, only use its tokens. Grant it the challenge record of
|
||||
Housecarl's own name, and nothing else:
|
||||
|
||||
```sh
|
||||
jarl user create housecarl --no-password --comment "Housecarl on fw01" \
|
||||
--grant "records:write zones=adbraun.lol names=_acme-challenge.fw.adbraun.lol types=TXT"
|
||||
```
|
||||
|
||||
Use your zone for `adbraun.lol`, and the name Housecarl's web UI is reached
|
||||
by for `fw.adbraun.lol` (`tls.acme.domain`). The grant lets the token add
|
||||
and remove TXT records at exactly `_acme-challenge.fw.adbraun.lol`:
|
||||
|
||||
* nothing else in the zone (`names=`);
|
||||
* no other record type (`types=TXT`);
|
||||
* no other zone (`zones=`).
|
||||
|
||||
It can see that the zone exists, which it needs to find it, but it can't
|
||||
read the zone's other records.
|
||||
|
||||
Why not something wider:
|
||||
|
||||
* **Jarl's `acme` role** (`records:write names=_acme-challenge.* types=TXT`)
|
||||
would let the token publish challenges for *any* name in *any* zone, and
|
||||
so obtain certificates for all of them. Housecarl needs only its own name.
|
||||
* **Your own account's token** would carry all your permissions, and
|
||||
Housecarl's certificate would stop renewing the day your account changes.
|
||||
A user per program keeps the two apart, and the audit log shows
|
||||
`housecarl/...` for what Housecarl did.
|
||||
|
||||
If the challenge name is a CNAME into a zone kept for challenges, such as
|
||||
`_acme-challenge.fw.adbraun.lol CNAME fw.acme.adbraun.lol.`, grant the
|
||||
target instead: `zones=acme.adbraun.lol names=fw.acme.adbraun.lol`.
|
||||
|
||||
## 2. The token
|
||||
|
||||
Make the token, and copy it to the Housecarl server into a file only the
|
||||
`housecarl` user can read:
|
||||
|
||||
```sh
|
||||
jarl token create fw01 --user housecarl -q > housecarl.token # on the Jarl server, or with the jarl CLI anywhere
|
||||
scp housecarl.token fw01:
|
||||
ssh fw01 'sudo install -m 0600 -o housecarl -g housecarl housecarl.token /etc/housecarl/jarl.token && rm housecarl.token'
|
||||
rm housecarl.token
|
||||
```
|
||||
|
||||
A token file is better than putting the token in `housecarl.yaml`: the
|
||||
configuration file is readable by the `housecarl` group, and gets copied
|
||||
around more readily.
|
||||
|
||||
You can give the token an expiry (`--expires 365d`). Rotate it in Jarl with
|
||||
`jarl token rotate ID --grace 1d`, then put the new secret in the file and
|
||||
restart housecarld within the grace period. Since the certificate is renewed
|
||||
every couple of months, nothing breaks while you do.
|
||||
|
||||
## 3. Housecarl's settings
|
||||
|
||||
In `/etc/housecarl/housecarl.yaml`:
|
||||
|
||||
```yaml
|
||||
jarl:
|
||||
server: https://dns.adbraun.lol:8053
|
||||
token_file: /etc/housecarl/jarl.token
|
||||
# ca_cert: /etc/housecarl/jarl-ca.pem # if Jarl's certificate isn't from a public CA
|
||||
```
|
||||
|
||||
Then restart housecarld (`systemctl restart housecarl`). Housecarl needs to
|
||||
reach Jarl's API over the network: Jarl's `listen` must include an
|
||||
address Housecarl can reach, ideally over HTTPS.
|
||||
|
||||
`hc cert check` ([certificates.md](certificates.md#checking)) tests
|
||||
the connection, the token's permission, and that the record reaches the
|
||||
internet.
|
||||
Reference in new issue
Block a user