internal/matrix walks each pair's rules in order, as nftables would, for new connections: zone → zone (forward), zone → the firewall (input) and back (output), with the built-ins that act on new connections. A rule for the whole pair decides it; one for part of its traffic (addresses, services, times, rates, a quota) is an exception; the rest falls to the policy. Rules whose addresses all lie in networks connected to other zones are left out of a pair. GET /matrix (with ?changeset, pending changes), hc matrix [FROM TO], and a Matrix page whose cells open their rules. e2e: 166 checks, among them the matrix of the maintainer's imported ruleset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
264 lines
12 KiB
Markdown
264 lines
12 KiB
Markdown
# 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
|
|
the `housecarl` user 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:
|
|
|
|
```sh
|
|
hc config set server https://fw.example.com:8070
|
|
hc config set token # asks for it
|
|
```
|
|
|
|
The 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](#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](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](operations.md#changes-on-probation)):
|
|
|
|
```
|
|
$ 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:
|
|
|
|
```sh
|
|
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`, `discard` and `remove` on 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, and `hc 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 `apply` or `discard`, a reminder to `unset 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](operations.md#the-log)) |
|
|
| `connections [--search TEXT] [--limit N]`, `connections close --search TEXT [-y]` | tracked connections; closing makes the rules check them again ([operations.md](operations.md#connections)) |
|
|
| `matrix [FROM TO]` | what the rules do to new connections between zones; with FROM and TO, that pair's rules in order ([rules.md](rules.md#the-zone-matrix)) |
|
|
| `trace [--address A] [--port N] [--protocol P] [--seconds S]` | follow packets through every table's rules ([operations.md](operations.md#tracing)) |
|
|
|
|
### 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 |
|
|
| `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](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](rules.md#rules)).
|
|
|
|
### 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](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]` | lists given replace the old ones |
|
|
|
|
### 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](import.md) |
|
|
| `takeover TABLE... [-y]` | delete tables Housecarl's ruleset replaces (administrators) |
|
|
|
|
### cert
|
|
|
|
Administrators only ([certificates.md](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](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](operations.md) |
|
|
| `housecarld config [--example]` | the settings in use, or a commented example |
|
|
| `housecarld version` | |
|
|
|
|
All take `-c FILE` for another configuration file.
|