The boot unit fails closed: if ruleset.nft doesn't load, it loads ruleset-fallback.nft (render.Fallback), saved beside it from the same configuration: nothing new in from external zones, established, the ICMPv6 IPv6 needs and DHCPv6 replies let in, the inside left alone and masqueraded, and the unit marked failed with a critical message. housecarld replaces it when it applies; status, the dashboard and /healthz say while it is in force. no_external_zone now also says the fallback closes everything without an external zone. The boot ruleset has no flowtable: nftables on Debian 13's kernel refuses one naming a device that doesn't exist yet (ppp0 before PPPoE), and with it the whole file. internal/linkwatch listens to rtnetlink link messages; housecarld applies again when a flowtable device appears or comes back as a new interface (by ifindex), under the engine's lock. docs/flowtable.md: what it does, when to use it and when not, how. GET /healthz reports firewall (applied, unconfigured, fallback, missing), changes on probation and the machine's warnings, with 503 unless in force, read at most every five seconds. Renaming a zone, set or service changes what names it in the same change (one history entry, one undo); dynamic sets keep their names. The importer keeps a chain's head in order: once a would-be built-in stays a rule, the ones after it do too, so the maintainer's ICMPv6 accepts stay behind the invalid drop as written. hc settings set --flowtable none empties the list. make test (nft 1.1.3) and make e2e (242 checks, with VLAN sub-interfaces, the fallback and the flowtable) pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
13 KiB
The hc CLI
hc talks to housecarld's REST API. hc help COMMAND describes each
command and its flags.
Connecting
-
On the firewall, with nothing configured, the CLI uses housecarld's local socket,
/run/housecarl/housecarl.sock. Root and thehousecarluser are administrators there without a token, and their changes need no confirmation:sudo hc status. -
From elsewhere, give the server's URL and an API token:
hc config set server https://fw.example.com:8070 hc config set token # asks for itThe settings are in
~/.config/housecarl/config.yaml(on macOS~/Library/Application Support/housecarl/config.yaml, on Windows%AppData%\housecarl\config.yaml).
| Flag | Environment | |
|---|---|---|
-s, --server URL |
HOUSECARL_SERVER |
the server; local for the local socket |
--token TOKEN or @FILE |
HOUSECARL_TOKEN |
the API token |
--config FILE |
HOUSECARL_CLI_CONFIG |
the CLI's configuration file |
HOUSECARL_SOCKET |
the local socket's path | |
--insecure, --ca-cert FILE |
TLS: skip the check, or trust another CA | |
-n, --dry-run |
check and show a change without making it, with the ruleset's diff | |
--no-confirm |
leave a change on probation instead of confirming it once hc reaches Housecarl again |
|
-C, --changeset ID |
HOUSECARL_CHANGESET (checked, see Changesets) |
stage configuration changes in this changeset instead of making them |
-o, --output FORMAT |
table (default), json, yaml or id |
|
-q, --quiet |
print only errors (and the IDs of new objects) |
Exit codes: 0 ok, 1 error, 3 not authenticated or not allowed, 4 not found, 5 conflict, 6 invalid or refused by validation or nftables, 7 server error.
Changes
Every change is checked as a whole, then by nftables, before it is put
in force. A refused change says why, with the problem's code
(validation.md). An applied one says its history entry
and any new warnings. Over the network, it is on probation, and hc
confirms it by reaching Housecarl again over a new connection
(operations.md):
$ hc rule add input --from trusted --service ssh --comment "SSH from home"
created rule input #31 (from trusted · ssh)
applied and confirmed (history 12): this client reached Housecarl again over a new connection
Through the socket the change is kept at once:
applied and saved (history 12; undo with 'hc history undo 12')
-n shows what would change without changing it, with the diff of the
ruleset; nftables checks it too.
Changesets
To review several changes and apply them together, as one entry in the history, stage them in a changeset:
export HOUSECARL_CHANGESET=$(hc changeset create "guest network" -q)
hc zone add guest -i eth1.30
hc rule add forward --in guest --out wan
hc changeset diff # the operations, problems, nftables' check, the ruleset's diff
hc changeset remove 2 # take operation 2 out
hc changeset apply # one change; over the network on probation
unset HOUSECARL_CHANGESET
$ hc rule add forward --in guest --out wan
changeset 7 (guest network, 1 operation) from HOUSECARL_CHANGESET
staged: create rule forward #52
staged in changeset 7 as operation 2 (2 operations now); review with 'hc changeset diff 7', apply with 'hc changeset apply 7'
Each change is checked as it is staged: the changeset with it must stay
valid, and nftables must accept the ruleset, so make a set before the
rule that names it. Applying checks the whole changeset again, on the
configuration as it is then; an operation whose object changed since is
named (operation 3 (update rule input #12) can't be applied: …), and
hc changeset remove takes it out.
| Command | |
|---|---|
changeset create [DESCRIPTION] |
start one; -q prints only its ID |
changeset list [--status open|applied|discarded|""] [--mine] |
open ones by default |
changeset show [ID] |
its operations |
changeset diff [ID] |
what applying it would do |
changeset apply [ID] [--description D] |
apply it |
changeset discard [ID] |
throw it away |
changeset remove [ID] OP |
take one operation out |
show, diff, apply, discard and remove work on the changeset you
name, else the one in -C, else the one in HOUSECARL_CHANGESET.
A changeset from HOUSECARL_CHANGESET is checked before anything uses
it, because an exported ID easily outlives its changeset:
- Staging into it, and
apply,discardandremoveon it, need it to be open and yours. Otherwise the command stops and says why (there is no changeset 99,changeset 42 is applied,changeset 57 is sam's). Commands that only read, andhc changeset create, don't care. - Each command says on stderr which changeset it uses:
changeset 42 (guest network, 2 operations) from HOUSECARL_CHANGESET(not with-q). - After
applyordiscard, a reminder tounset HOUSECARL_CHANGESET.
-C ID and an ID argument are used as given (housecarld still refuses a
changeset that isn't open, or another user's unless you are an
administrator). Only configuration changes are staged: dynamic set
entries, confirm and rollback are made at once; history undo is
staged like any change; import and takeover can't be staged, so they
are refused while a changeset is named. Changesets staged in the web UI show up here too,
and the other way round.
Commands
Objects are named by ID or, for zones, sets, services and port forwards, by name.
status and the ruleset
| Command | |
|---|---|
status |
the ruleset (in force or not), changes on probation, problems, interfaces, other tables |
interfaces |
the machine's interfaces, their addresses and zones |
ruleset |
the nftables ruleset Housecarl builds |
counters |
the hits of every rule, NAT rule, built-in and policy, by mark (r12, f3, b-established, p-input) |
quotas |
what each rule's quota used (each address's, for per-address quotas), and when it resets |
log [-f] [--search TEXT] [--limit N] |
the packets logged by rules and policies (operations.md) |
connections [--search TEXT] [--limit N], connections close --search TEXT [-y] |
tracked connections; closing makes the rules check them again (operations.md) |
stats [--since 24h] |
traffic per zone, drops, the busiest rules, blocked sources, devices; stats status, stats traffic, stats top devices|blocked|tried|quota ID, stats rules [--unused], stats connections (stats.md) |
matrix [FROM TO] |
what the rules do to new connections between zones; with FROM and TO, that pair's rules in order (rules.md) |
trace [--address A] [--port N] [--protocol P] [--seconds S] |
follow packets through every table's rules (operations.md) |
zone, set, service
| Command | |
|---|---|
zone list, zone show ZONE |
|
zone add NAME -i IFACE... [--external] [--comment C] |
|
zone update ZONE [-i IFACE]... [--add IFACE] [--remove IFACE] [--external[=false]] [--name N] [--comment C] |
-i replaces the interfaces; --name renames it, and the rules naming it follow |
zone delete ZONE |
refused while rules use it |
set list, set show SET |
|
set add NAME [ENTRY...] [--dynamic] [--timeout 24h] [--comment C] |
|
set update SET [--entry E]... [--add E] [--remove E] [--timeout] [--name] [--comment] |
--entry replaces the entries |
set entries SET, set entries add SET ADDRESS [--timeout 1h], set entries remove SET ADDRESS |
a dynamic set's entries |
set delete SET |
|
service list, service show SERVICE |
with the built-in ones |
service add NAME ENTRY... [--comment C] |
entries tcp/22, udp/1000-2000, icmp/echo-request, icmpv6/packet-too-big |
service update SERVICE, service delete SERVICE |
See rules.md.
rule
| Command | |
|---|---|
rule list [CHAIN] |
in order, with their hits |
rule show ID |
|
rule add CHAIN [MATCH] [-a ACTION] [--first|--before ID|--after ID] |
at the end of its chain unless placed |
rule update ID [flags] |
only the flags given |
rule move ID --before ID|--after ID|--first|--last|--chain CHAIN |
|
rule enable|disable ID |
|
rule reset-quota ID |
start a rule's quota again now |
rule delete ID |
The match flags (rule add, snat add, redirect add):
--in ZONE, --out ZONE (--not-in, --not-out) |
zones |
--from, --to (--not-from, --not-to) |
sets, addresses, prefixes, ranges |
--service |
services or entries |
--family ipv4|ipv6 |
And for rules: --state, --status, --days, --hours, --limit 10/minute|1mbytes/second[,burst=N][,over][,per-source|,per-destination],
--quota 50gbytes[,over][,per-source|,per-destination][,daily|,weekly|,monthly=DAY],
--group-v4 N, --group-v6 N, --conn-limit N, --every-packet, -a accept|drop|reject|log, --reject-with tcp-reset, --log,
--log-prefix, --raw RULE, --comment, --disabled
(rules.md).
forward, snat, redirect
| Command | |
|---|---|
forward list, forward show FORWARD |
port forwards, with their hits |
forward add PORTS --to HOST[:PORT] [--proto tcp,udp] [--public ADDR]... [--from SRC]... [--hairpin] [--hairpin-from SRC]... [--to6 HOST] [--name N] [--comment C] |
|
forward update FORWARD [flags], forward delete FORWARD |
|
snat list, snat add [MATCH] [--snat-to ADDR], snat update ID, snat delete ID |
source NAT, in order; masquerade without --snat-to |
redirect list, redirect add [MATCH] [--to-port PORT], redirect update ID, redirect delete ID |
See nat.md.
settings
| Command | |
|---|---|
settings show |
|
settings set [--input-policy|--forward-policy|--output-policy accept|drop] [--log-drops on|off] [--builtin NAME=on|off]... [--own-prefix P]... [--antispoof-v4 P]... [--antispoof-v6 P]... [--flowtable DEV]... [--probation 2m] [--auto-confirm on|off] [--stats-per-device on|off] |
lists given replace the old ones; none empties one (--flowtable none: off) |
probation, history, import
| Command | |
|---|---|
confirm |
keep the changes on probation; the ruleset is saved for the boot |
rollback |
undo the changes on probation now |
history list [--limit N] |
changes, newest first |
history show ID |
each object before and after |
history undo ID |
apply the change backwards (refused if something it touched has changed since) |
import [FILE|-] [--text FILE] [--defines FILE] [--table T] [--replace] |
import.md |
takeover TABLE... [-y] |
delete tables Housecarl's ruleset replaces (administrators) |
cert
Administrators only (certificates.md).
| Command | |
|---|---|
cert |
the certificate: name, CA, validity, renewal, the last check |
cert check |
check that the challenge can be published through Jarl and seen, without the CA (exit code 1 if it can't) |
cert renew [-w] |
get a new certificate now; -w waits and shows the result |
users, tokens, audit
| Command | |
|---|---|
whoami |
who you are to the server, and what you may do |
user list|add|update|delete |
users (administrators); user add NAME --role operator |
token list [--all], token create NAME [--role R] [--grant G]... [--expires 90d] [--user U] [--save], token revoke ID |
API tokens |
token rotate ID|--current [--grace 1h] [--expires 90d|never] [--save] |
a new secret for a token, the old one working for the grace period |
password |
change your password |
audit [--user U] [--action PREFIX] [--search TEXT] |
the audit log |
config show, config set server|token|insecure|ca_cert [VALUE] |
this CLI's settings |
permissions.md explains roles and grants.
housecarld
The daemon's own commands work on the database directly, as root or the
housecarl user (sudo -u housecarl housecarld ...):
| Command | |
|---|---|
housecarld serve |
run (the systemd unit does) |
housecarld init [-u NAME] [--token] |
create the database and the first administrator (asks for the name on a terminal) |
housecarld user add NAME [--admin], user password NAME, user token NAME TOKEN-NAME |
when the API can't be used |
housecarld check |
check the configuration against this machine's interfaces |
housecarld backup [FILE], housecarld restore FILE |
operations.md |
housecarld config [--example] |
the settings in use, or a commented example |
housecarld version |
All take -c FILE for another configuration file.