Changesets, as in Jarl: configuration changes are operations that can be staged in a changeset, checked as they are added (validation and nft -c), reviewed with the diff of the ruleset, and applied together as one history entry, then on probation like any change made over the network. The web UI collects edits for review by default (pending rows, a bar with Review and Apply now, a Changes page, a switch for live edits); hc stages with -C ID or HOUSECARL_CHANGESET (checked like JARL_CHANGESET) and has hc changeset create/list/show/diff/apply/discard/remove. Undo, import and takeover refuse a changeset. --dry-run shows the ruleset's diff. Bandwidth and quotas: rate limits in bytes, per source or destination; quotas per rule or per address that reset daily, weekly, monthly on a day, or by hand, with their usage carried across reloads, saved for reboots, shown by hc quotas and on the rules page, and reset with hc rule reset-quota. Rules that count traffic are checked before established connections are accepted (so they see every packet) and can't accept. Per-address limits group IPv6 by /64 by default (--group-v4/-v6). Per-source rate meters use update. The importer turns meters, byte rates and quotas into these fields. The examples in the web UI, hc's help and the docs use documentation addresses and generic names instead of the maintainer's network. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
137 lines
5.6 KiB
Markdown
137 lines
5.6 KiB
Markdown
# Importing an nftables ruleset
|
|
|
|
`hc import` turns the ruleset you have into Housecarl's configuration,
|
|
and `hc takeover` deletes the old table once Housecarl's replaces it.
|
|
Until then both apply.
|
|
|
|
## 1. Look first
|
|
|
|
On the firewall, as root:
|
|
|
|
```sh
|
|
sudo hc import --defines /etc/nftables.conf -n
|
|
```
|
|
|
|
`hc` runs `nft -j list ruleset` (and `nft -a list ruleset`, for the text
|
|
of rules it can't translate) and sends both to housecarld. `-n` (dry run)
|
|
shows what it would make, without saving anything: zones, sets,
|
|
services, rules, port forwards and NAT, then **notes** on every guess it
|
|
made, and the rules it **kept raw**.
|
|
|
|
`--defines` reads your ruleset file's `define` lines, so sets get your
|
|
names (`define admins = { ... }` becomes the set `admins`).
|
|
Without it, sets are named after what they hold. Interfaces named by a
|
|
define name their zone too (`define wan_interface = "ppp0"` makes the
|
|
zone `wan`); otherwise a zone is named after its interface.
|
|
|
|
From another machine, give the JSON as a file, and the text with
|
|
`--text`:
|
|
|
|
```sh
|
|
ssh fw 'sudo nft -j list ruleset' > ruleset.json
|
|
ssh fw 'sudo nft -a list ruleset' > ruleset.txt
|
|
hc import ruleset.json --text ruleset.txt --defines nftables.conf -n
|
|
```
|
|
|
|
With several tables, `--table "inet main"` picks one; by default every
|
|
table but Housecarl's is read. The web UI has the same under Server →
|
|
Import a ruleset.
|
|
|
|
## What becomes what
|
|
|
|
* **Interfaces** become zones. Zones are marked external when the rules
|
|
show it: MSS clamping on the way out, DHCPv6 client replies, ping
|
|
accepted from everywhere else.
|
|
* **Sets and defines** of addresses become address sets; port lists
|
|
become services.
|
|
* **Jumps and gotos** to your own chains are flattened: the jump's
|
|
condition is added to each rule of the chain, in place.
|
|
* **Rules** keep their order. A rule that is a built-in (loopback,
|
|
established, invalid, the ICMPv6 rules, MSS clamping, the DHCPv6 client
|
|
rule, antispoofing) becomes that built-in only when it sits where the
|
|
built-in goes, at the head of every chain it covers. Otherwise it stays
|
|
a rule where it was, so the ruleset does what it did. Built-ins your
|
|
ruleset doesn't have are turned off.
|
|
* **DNAT** rules become port forwards, TCP and UDP of the same port
|
|
merged into one. The forward-chain accepts that let them through
|
|
become part of the port forward, and a masquerade for inside sources
|
|
to the same host becomes its hairpin. An IPv6 accept to the same port
|
|
becomes the forward's IPv6 pinhole when there is only one candidate.
|
|
* **Maps are expanded**, one element at a time. A DNAT map
|
|
(`dnat ip to tcp dport map @forwards`, named or written in the rule)
|
|
becomes one port forward per element; forwards from a named map are
|
|
called after it (`forwards-443`). A verdict map
|
|
(`tcp dport vmap { 22 : accept, 23 : drop }`) becomes one rule per
|
|
element, in place, with `jump`s inlined as above. A lookup matches at
|
|
most one element, so the rules do the same. A map keyed by several
|
|
fields (`ip saddr . tcp dport`) can't be expanded: a rule using a named
|
|
one is left out with a note, to be added by hand.
|
|
* **Masquerade and SNAT** become source NAT; **redirect** becomes a
|
|
redirect.
|
|
* **Meters** (`update @m { ip saddr limit rate over 3/minute }`,
|
|
`add @m { ip saddr ct count over 20 }`, quotas in sets) become the
|
|
rule's per-source (or per-destination) limit, connection limit or quota;
|
|
a mask in the key (`ip saddr and 255.255.255.0`) becomes its grouping.
|
|
Byte rates and quotas are imported too; a quota imported resets only by
|
|
hand until you give it a period. A rule with a quota or a byte rate that
|
|
accepts stays a raw rule ([rules.md](rules.md#bandwidth-and-quotas)).
|
|
* **Policies** become the chains' policies.
|
|
* **Anything else** becomes a raw rule with its original text, in place.
|
|
A raw rule naming a set of the old table needs the new set name:
|
|
Housecarl's are `NAME_v4` and `NAME_v6`, and a note says so.
|
|
|
|
Chains on hooks Housecarl doesn't manage (ingress, a bridge table) are
|
|
left out, with a note.
|
|
|
|
## 2. Import
|
|
|
|
```sh
|
|
sudo hc import --defines /etc/nftables.conf
|
|
sudo hc ruleset # what Housecarl now applies
|
|
sudo hc status # problems, and the other tables
|
|
```
|
|
|
|
Housecarl's table is now in force **alongside** the old one: a packet
|
|
must be accepted by both. Nothing should change for your traffic. If
|
|
Housecarl already has a configuration, `--replace` replaces it.
|
|
|
|
Imported over the network (not through the local socket), the import is
|
|
on probation like any change: confirm it.
|
|
|
|
## 3. Take over
|
|
|
|
Once the rules look right (and their hits in the web UI agree), delete
|
|
the old table:
|
|
|
|
```sh
|
|
sudo hc takeover "inet main"
|
|
```
|
|
|
|
From now on only Housecarl's rules apply. Over the network, this is on
|
|
probation too: unless confirmed, the old table comes back.
|
|
|
|
## 4. The boot
|
|
|
|
Housecarl's ruleset is loaded at boot by `housecarl-ruleset.service`.
|
|
Debian's `nftables.service` loads `/etc/nftables.conf`, which usually
|
|
starts with `flush ruleset`: that would delete Housecarl's table, or
|
|
bring the old one back. Retire it:
|
|
|
|
```sh
|
|
sudo systemctl disable nftables.service
|
|
sudo mv /etc/nftables.conf /etc/nftables.conf.before-housecarl
|
|
```
|
|
|
|
`housecarl-ruleset.service` runs after `nftables.service` in any case,
|
|
so a leftover `flush ruleset` can't undo it. Docker, Incus and libvirt
|
|
keep adding their own tables as before.
|
|
|
|
## Checking the result
|
|
|
|
* `hc rule list input` (and `forward`) side by side with
|
|
`nft -a list table inet main`: the same rules, in the same order, with
|
|
built-ins above.
|
|
* The notes: each says what to check.
|
|
* `hc counters` after a while: a rule with no hits that had hits before
|
|
is worth a look.
|