Sets from Jarl: sets holding the addresses of names in Jarl
A set can take its addresses from Jarl: every A and AAAA record of its
names (NAME@VIEW), CNAMEs followed within Jarl's zones in the same view.
housecarld reads them every jarl.refresh and after each change, keeps
only the addresses within the zones and networks the set allows, and
keeps what it last read (saved in the database and the boot ruleset)
while Jarl can't be reached. A change of the addresses applies the
ruleset again without a history entry and is audited (set.jarl).
hc set add --jarl --within, hc set jarl; the kind "from Jarl" and
Addresses… on the Sets page; GET /sets/{name}/jarl and /jarl/refresh;
validation codes set_jarl_*, status warnings jarl_*; docs/jarl.md.
e2e phase with scripts/fakejarl.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
50dce02746
commit
68ba2d06c5
37 files changed
+2315
-59
No files matched your search
+36
@@ -129,6 +129,7 @@ The objects:
|
||||
```json
|
||||
{"name": "lan", "interfaces": ["eth1", "eth1.*"], "external": false, "comment": ""}
|
||||
{"name": "trusted", "entries": ["192.168.1.0/24", "2001:db8:1::/64"], "dynamic": false, "timeout": 0, "comment": ""}
|
||||
{"name": "nas", "entries": [], "jarl": {"names": [{"name": "nas.example.com", "view": "internal"}], "within": ["lan", "10.9.0.0/16"]}, "comment": ""}
|
||||
{"name": "web", "entries": ["tcp/8080", "tcp/8443"], "comment": ""}
|
||||
{"chain": "input",
|
||||
"in_zones": ["wan"], "not_in": false, "out_zones": [], "not_out": false,
|
||||
@@ -145,6 +146,12 @@ The objects:
|
||||
"comment": "", "disabled": false}
|
||||
```
|
||||
|
||||
A set with `jarl` takes its addresses from Jarl ([jarl.md](jarl.md#sets-from-jarl)):
|
||||
`names` are names in Jarl, each with a `view` (left out: the only view that
|
||||
has it); `within` are zones and addresses its addresses must lie in. It
|
||||
has no `entries` and isn't `dynamic`; `"jarl": null` makes it an ordinary
|
||||
set again.
|
||||
|
||||
A source NAT rule is a match (as a rule's, without the rule's other
|
||||
fields) with `"to"` (empty: masquerade), a redirect a match with
|
||||
`"to_port"`. A ban list ([bans.md](bans.md)):
|
||||
@@ -209,6 +216,35 @@ These need `sets:read` or `sets:write`, for that set
|
||||
([permissions.md](permissions.md)); they aren't on probation or in the
|
||||
history, but are audited.
|
||||
|
||||
### Sets from Jarl
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `GET /sets/{name}/jarl` | what the set holds and why (below) |
|
||||
| `POST /sets/{name}/jarl/refresh` | reads Jarl now (for every set from Jarl), then answers as `GET` |
|
||||
|
||||
They need `sets:read` for that set. 400 for a set that doesn't come from
|
||||
Jarl. The answer:
|
||||
|
||||
```json
|
||||
{"set": "nas", "configured": true, "read": "2026-10-23T14:02:11Z", "error": "",
|
||||
"entries": ["10.10.0.20", "10.10.0.21"], "within": ["lan"],
|
||||
"names": [{"name": "files.example.com", "view": "", "found_view": "internal", "chain": ["nas.example.com"],
|
||||
"addresses": [{"address": "10.10.0.20", "used": true},
|
||||
{"address": "2001:db8:10::20", "used": false, "why": "not within lan"}],
|
||||
"problem": "", "checked": "2026-10-23T14:02:11Z", "error": ""}]}
|
||||
```
|
||||
|
||||
* `configured`: `housecarl.yaml` names a Jarl server; `read`: when Jarl's
|
||||
zones were last read; `error`: why the last read failed (the set keeps
|
||||
its addresses).
|
||||
* `entries`: the addresses the set holds now.
|
||||
* For each name: `found_view` is the view it was read from, `chain` the
|
||||
CNAMEs followed, `addresses` every address Jarl gives it with `used`
|
||||
(or `why` not), `problem` why it gives none, `checked` when Jarl last
|
||||
answered for it, and `error` why the last try failed (its addresses are
|
||||
then those of `checked`).
|
||||
|
||||
### Bans
|
||||
|
||||
| | |
|
||||
|
||||
+3
-1
@@ -154,8 +154,10 @@ by name.
|
||||
| `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 add NAME --jarl NAME[@VIEW]... --within ZONE\|NETWORK...` | a set from Jarl: the names' addresses, only those within the zones and networks ([jarl.md](jarl.md#sets-from-jarl)) |
|
||||
| `set update SET [--entry E]... [--add E] [--remove E] [--timeout] [--jarl N]... [--within W]... [--name] [--comment]` | `--entry` replaces the entries, `--jarl` a Jarl set's names, `--within` where its addresses may be |
|
||||
| `set entries SET`, `set entries add SET ADDRESS [--timeout 1h]`, `set entries remove SET ADDRESS` | a dynamic set's entries |
|
||||
| `set jarl SET [--refresh]` | a set from Jarl: each name's addresses in Jarl (CNAMEs followed), used or why not, and when Jarl answered; `--refresh` reads Jarl first |
|
||||
| `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` |
|
||||
|
||||
@@ -13,7 +13,8 @@ reads the file when it starts.
|
||||
| `listen` | `["127.0.0.1:8070"]` | addresses of the web UI and API (`host:port`). Keep them on localhost or a management network, and write an input rule that lets you reach them |
|
||||
| `tls.cert_file`, `tls.key_file` | none | serve HTTPS with this certificate; housecarld rereads it when the file changes, or on `systemctl reload housecarl` |
|
||||
| `tls.acme.*` | off | get the certificate from Let's Encrypt, with Jarl publishing the challenge ([certificates.md](certificates.md#settings)) |
|
||||
| `jarl.server`, `jarl.token_file` (or `jarl.token`), `jarl.ca_cert`, `jarl.insecure` | none | the Jarl server for the certificate's challenge, and its token ([jarl.md](jarl.md)) |
|
||||
| `jarl.server`, `jarl.token_file` (or `jarl.token`), `jarl.ca_cert`, `jarl.insecure` | none | the Jarl server for the certificate's challenge and the sets from Jarl, and its token ([jarl.md](jarl.md)) |
|
||||
| `jarl.refresh` | `1m` | how often the sets from Jarl read their names (10s or more; [jarl.md](jarl.md#when-the-addresses-change)) |
|
||||
| `ruleset` | `/var/lib/housecarl/ruleset.nft` | the confirmed ruleset, which `housecarl-ruleset.service` loads at boot; its fallback is saved beside it as `ruleset-fallback.nft` ([operations.md](operations.md#if-the-ruleset-fails-to-load-at-boot)). Change both paths in the unit too |
|
||||
| `trusted_proxies` | `[]` | addresses or prefixes of reverse proxies whose `X-Forwarded-For` and `X-Forwarded-Proto` are believed |
|
||||
| `socket` | `/run/housecarl/housecarl.sock` | the local API socket; `""` turns it off |
|
||||
|
||||
+11
-3
@@ -94,7 +94,8 @@ table inet housecarl { ... }
|
||||
Two things survive the replacement: the **entries of dynamic sets**
|
||||
(read before, written back with their remaining timeouts, in the same
|
||||
script), and the **counters** (housecarld adds the old totals to the
|
||||
new, so hits don't reset with every change).
|
||||
new, so hits don't reset with every change). Sets from Jarl are written
|
||||
with the addresses last read from Jarl.
|
||||
|
||||
Every rule carries a comment `hc:<mark>`, such as `hc:r12` for rule 12,
|
||||
`hc:f3` for port forward 3, `hc:b-established` for a built-in. Counters
|
||||
@@ -199,8 +200,15 @@ Housecarl's replaces it ([import.md](import.md)).
|
||||
may only change that set (`sets:write:guests`). Addresses, not MAC
|
||||
addresses: matching by MAC only works on the guests' own network
|
||||
segment.
|
||||
* **Names instead of addresses**: Steward knows every reservation's
|
||||
address and Jarl every name, so rules could name a device or a subnet.
|
||||
* **Names instead of addresses**: a set can take its addresses from Jarl
|
||||
([jarl.md](jarl.md#sets-from-jarl)). housecarld reads the names' A and
|
||||
AAAA records through Jarl's API (CNAMEs followed, one view per name)
|
||||
and keeps the addresses that lie within the zones and networks the set
|
||||
allows; Jarl never calls Housecarl. What was last read is kept (in the
|
||||
database too) while Jarl can't be reached. A change of the addresses
|
||||
applies the ruleset again without a history entry, as dynamic sets'
|
||||
entries aren't in it either. Names Steward makes from leases must not
|
||||
be used: a client chooses its own hostname.
|
||||
* **The web UI's certificate** comes through Jarl, as Steward's does
|
||||
([certificates.md](certificates.md)).
|
||||
|
||||
|
||||
+3
-1
@@ -20,12 +20,14 @@ internal/
|
||||
server/ the HTTP API and the web UI, authentication, the local socket, probation, the log ring
|
||||
api/, client/ JSON types and the Go client shared by server and CLI
|
||||
auth/ passwords (argon2id), tokens, sessions, grants and roles
|
||||
certs/, jarl/ the web UI's certificate through Jarl (docs/certificates.md)
|
||||
certs/, jarl/ the web UI's certificate through Jarl (docs/certificates.md); jarl/ is the Jarl API client
|
||||
jarlsets/ sets from Jarl: reads the names, follows CNAMEs, keeps what was read, within (docs/jarl.md)
|
||||
config/ housecarld's YAML configuration
|
||||
version/ what a build reports
|
||||
web/static/ the web UI: plain ES modules, no build step, embedded with go:embed
|
||||
deploy/ systemd units, sysusers.d and tmpfiles.d files, Debian package scripts
|
||||
scripts/e2e.sh end-to-end test with real traffic through a router container
|
||||
scripts/fakejarl/ the bit of Jarl's API sets from Jarl read, for the e2e test
|
||||
scripts/mkdeb.sh the Debian packages (make deb)
|
||||
scripts/logo.py the web UI's logo from artwork/logo.svg
|
||||
scripts/banner.py the README's banner from the web UI's logo and wordmark
|
||||
|
||||
+219
-7
@@ -1,24 +1,44 @@
|
||||
# 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.
|
||||
manager, for two things:
|
||||
|
||||
* the DNS-01 challenge for the web UI's certificate
|
||||
([certificates.md](certificates.md)): Housecarl writes a TXT record;
|
||||
* **sets from Jarl** ([below](#sets-from-jarl)): sets whose addresses are
|
||||
those of names in Jarl, such as `nas.adbraun.lol`, kept up to date.
|
||||
Housecarl only reads.
|
||||
|
||||
Housecarl talks to Jarl's REST API with an API token. Give it a **user of its
|
||||
own**, with **only the permission it needs**.
|
||||
own**, with **only the permission it needs**. Jarl never calls Housecarl.
|
||||
|
||||
## 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:
|
||||
Jarl's web UI, only use its tokens.
|
||||
|
||||
For **sets from Jarl**, grant it reading the records of the zones and views
|
||||
your names are in:
|
||||
|
||||
```sh
|
||||
jarl user create housecarl --no-password --comment "Housecarl on fw01" \
|
||||
--grant "records:read zones=adbraun.lol views=internal"
|
||||
```
|
||||
|
||||
Include every zone a name's CNAMEs lead to as well (`zones=adbraun.lol,wg.adbraun.lol`).
|
||||
Reading is all it needs: it can't change anything in Jarl.
|
||||
|
||||
For the **certificate**, 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"
|
||||
```
|
||||
|
||||
For both, give both grants (`--grant` twice). `jarl user update housecarl
|
||||
--grant ... --grant ...` replaces a user's grants with the ones given.
|
||||
|
||||
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`:
|
||||
@@ -63,7 +83,9 @@ 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.
|
||||
every couple of months, nothing breaks while you do. Sets from Jarl keep
|
||||
their addresses while the token doesn't work (they warn, see
|
||||
[below](#when-jarl-cant-be-reached)), so they don't break either.
|
||||
|
||||
## 3. Housecarl's settings
|
||||
|
||||
@@ -74,6 +96,7 @@ 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
|
||||
# refresh: 1m # how often sets from Jarl read their names
|
||||
```
|
||||
|
||||
Then restart housecarld (`systemctl restart housecarl`). Housecarl needs to
|
||||
@@ -83,3 +106,192 @@ 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.
|
||||
|
||||
## Sets from Jarl
|
||||
|
||||
A set can take its addresses from Jarl instead of listing them: you name
|
||||
DNS names, and the set holds their addresses. Rules use it like any other
|
||||
set. When a record changes in Jarl, the set follows within a minute,
|
||||
without a change to Housecarl's configuration.
|
||||
|
||||
```sh
|
||||
hc set add nas --jarl nas.adbraun.lol@internal --within lan
|
||||
hc rule add forward --in wg --to nas --service smb
|
||||
hc set jarl nas # what it holds, name by name
|
||||
```
|
||||
|
||||
In the web UI: Sets → Add set, kind "from Jarl". The **Addresses…**
|
||||
button shows what the set holds.
|
||||
|
||||
A set from Jarl has:
|
||||
|
||||
* **names**: one or more names, each `NAME@VIEW`, or `NAME` when only one
|
||||
of Jarl's views has it ([Views](#views));
|
||||
* **within**: where its addresses may be, one or more zones or networks
|
||||
([Within](#within)). It is required.
|
||||
|
||||
It has no entries of its own, and isn't dynamic. It can be renamed (it goes
|
||||
by its names in Jarl, not by its own).
|
||||
|
||||
### What a name gives
|
||||
|
||||
Housecarl reads the name's records as named would answer them in the
|
||||
view:
|
||||
|
||||
* **Every A and AAAA record** of the name. A name with three addresses
|
||||
puts all three in the set, IPv4 and IPv6 alike; a rule using the set
|
||||
matches each family with its own addresses.
|
||||
* **CNAMEs are followed**: `files.adbraun.lol CNAME nas.adbraun.lol.` gives
|
||||
nas's addresses. Chains are followed up to 8 CNAMEs, from zone to zone
|
||||
(a sub-zone such as `wg.adbraun.lol` too), but always in the **same
|
||||
view**, and only into zones Jarl holds. `hc set jarl` shows the chain:
|
||||
`files.adbraun.lol → nas.adbraun.lol`.
|
||||
* **Disabled records** are left out, as named leaves them out.
|
||||
|
||||
A name gives **no addresses**, and a warning (`jarl_name`) says why, when:
|
||||
|
||||
* it has no A, AAAA or CNAME record (only an MX, say, or nothing);
|
||||
* only a **wildcard** (`*.adbraun.lol`) would answer for it: wildcards
|
||||
aren't followed, so name a record of its own;
|
||||
* a **CNAME leads out of Jarl** (`host.duckdns.org`), into a zone Jarl
|
||||
doesn't hold, or to a name with no address;
|
||||
* the CNAMEs **loop**, or are more than 8;
|
||||
* it is **below a delegation**: NS records in the zone hand that part to
|
||||
other servers, so Jarl doesn't hold its records;
|
||||
* its zone is a **secondary, mirror, stub or forward zone** (Jarl doesn't
|
||||
hold their records), or **disabled** in Jarl;
|
||||
* no zone the token can see holds it.
|
||||
|
||||
A **reverse zone** isn't involved: Housecarl reads the A and AAAA records
|
||||
only, never PTRs.
|
||||
|
||||
An **in-view zone** (one view serving another's zone) is followed to the
|
||||
zone it serves: `srv.adbraun.lol@vps` reads the internal view's records if
|
||||
vps's `adbraun.lol` is an in-view of internal's.
|
||||
|
||||
### Views
|
||||
|
||||
The same name can have different addresses in different views: Jarl's
|
||||
`internal` view may give `nas.adbraun.lol` its private address, `external`
|
||||
a public one. A set reads **one view per name**, written `NAME@VIEW`:
|
||||
|
||||
* **Name the view your devices see.** The firewall matches the addresses
|
||||
packets carry, and for devices inside that is the internal view's.
|
||||
* **Without a view**, the name is read from the only view that has a zone
|
||||
for it. If zones in **more than one view** hold it, the name gives
|
||||
nothing until you name the view (`jarl_name`: "is in Jarl's views
|
||||
external, internal: name the view"). This counts every view whose zones
|
||||
cover the name, even one where the name has no record: a record added
|
||||
there later would otherwise change which view Housecarl reads.
|
||||
* **Linked zones** make no difference: a shared record has the same
|
||||
address in every view, and a view's own records are why the view is
|
||||
named.
|
||||
* **Without views** in Jarl, write the name alone.
|
||||
* A set can mix views: `--jarl nas.adbraun.lol@internal --jarl
|
||||
host.adbraun.lol@vps`.
|
||||
|
||||
A wrong view can't widen the set: an address from it that isn't
|
||||
[within](#within) is left out.
|
||||
|
||||
### Within
|
||||
|
||||
`--within` says where the set's addresses may be. Each entry is:
|
||||
|
||||
* **a zone**: the networks connected to its interfaces (the addresses on
|
||||
them, as `hc status` lists them), IPv4 and IPv6;
|
||||
* **a network, address or range**: `10.10.0.0/24`, `2001:db8:10::/64`,
|
||||
`192.0.2.7`.
|
||||
|
||||
An address from Jarl that isn't within any of them is **left out** of the
|
||||
set, and a warning names it (`jarl_outside`: "nas.adbraun.lol has address
|
||||
192.0.2.9, which is left out: not within lan"). The set's other addresses
|
||||
stay. So a record changed in Jarl, by mistake or by someone who shouldn't,
|
||||
can only point the set at addresses you allowed.
|
||||
|
||||
Zones grow by themselves: when you add a VLAN to the `lan` zone, or give
|
||||
an interface another network, its addresses count at once. A new zone
|
||||
counts only once you add it to `within`. If a zone's interfaces have no
|
||||
address for a while (at boot, a link down), the networks it had last count.
|
||||
|
||||
Some addresses never count, whatever `within` says: unspecified (`0.0.0.0`,
|
||||
`::`), loopback and multicast ones.
|
||||
|
||||
`within 0.0.0.0/0` or `::/0` lets any address in, and gives a warning
|
||||
(`set_jarl_anywhere`): then whoever can change the names in Jarl decides
|
||||
what the set holds. Name a zone or a network instead. For an address
|
||||
outside your networks, such as a VPS's public address, name that address
|
||||
or its network.
|
||||
|
||||
### When the addresses change
|
||||
|
||||
Housecarl reads the names every `jarl.refresh` (a minute), right after a
|
||||
change to Housecarl's configuration (a new set or name), when an interface
|
||||
changes, and when you ask: `hc set jarl NAME --refresh`, or **Read Jarl
|
||||
now** in the web UI.
|
||||
|
||||
When a set's addresses change, housecarld puts the ruleset in force again
|
||||
with the new ones. That isn't a change of the configuration: there is no
|
||||
history entry and no probation. It is recorded in the audit log
|
||||
(`set.jarl`, "added 10.10.0.22; removed 10.10.0.20") and in the journal.
|
||||
|
||||
A new set holds nothing for the moment until its names are read, usually
|
||||
less than a second.
|
||||
|
||||
**Open connections aren't closed** when an address leaves a set, as with
|
||||
any change to a set: the connection's packets are let through as part of an
|
||||
established connection (or by the flowtable). Close them with `hc
|
||||
connections close` if they must stop at once.
|
||||
|
||||
### When Jarl can't be reached
|
||||
|
||||
A set **keeps the addresses it last had** while Jarl can't be read: Jarl is
|
||||
down, the network to it is, or the token stopped working. Nothing is
|
||||
emptied, so rules keep working. A warning says so (`jarl_unreachable`), on
|
||||
the status page and in `hc status`, and `hc set jarl` shows when Jarl last
|
||||
answered.
|
||||
|
||||
What was last read is saved in the database. After a restart or a reboot,
|
||||
the sets get those addresses at once, before Jarl is read again, and the
|
||||
boot ruleset ([operations.md](operations.md)) has them too.
|
||||
|
||||
When Jarl **answers** that a record is gone, its addresses go. Only a
|
||||
failure to read keeps them.
|
||||
|
||||
Without `jarl.server` in `housecarl.yaml`, sets from Jarl keep what they
|
||||
had (nothing if Jarl was never read) and warn (`jarl_not_configured`).
|
||||
|
||||
### Who decides what the set holds
|
||||
|
||||
A set from Jarl lets **whoever can change those records in Jarl** decide
|
||||
which addresses a rule matches. Keep that in mind for rules that accept:
|
||||
|
||||
* `within` limits it to networks you chose;
|
||||
* Housecarl's token only reads ([step 1](#1-a-user-for-housecarl-in-jarl));
|
||||
* give the records' zones only to people you'd let change the firewall,
|
||||
and check Jarl's audit log for who changed what.
|
||||
|
||||
**Don't use names that clients choose for themselves.** A name a DHCP
|
||||
server makes from the client's own hostname belongs to whoever asks for
|
||||
it: any guest could call itself `nas`. Name records that you made by hand,
|
||||
or that come from a DHCP reservation.
|
||||
|
||||
### Seeing what a set holds
|
||||
|
||||
```
|
||||
$ hc set jarl nas
|
||||
Set: nas
|
||||
Within: lan
|
||||
Holds: 10.10.0.20, 10.10.0.21
|
||||
Jarl read: 2026-10-23 14:02:11
|
||||
|
||||
NAME VIEW ADDRESS USED
|
||||
files.adbraun.lol → nas.adbraun.lol internal 10.10.0.20 yes
|
||||
files.adbraun.lol → nas.adbraun.lol internal 10.10.0.21 yes
|
||||
files.adbraun.lol → nas.adbraun.lol internal 2001:db8:10::20 no: not within lan
|
||||
```
|
||||
|
||||
The web UI's **Addresses…** button on the Sets page shows the same, and the
|
||||
status page lists the warnings: names that give nothing (`jarl_name`),
|
||||
addresses left out (`jarl_outside`), Jarl not read (`jarl_unreachable`).
|
||||
The API is `GET /sets/{name}/jarl` ([api.md](api.md#sets-from-jarl)).
|
||||
|
||||
+4
-1
@@ -83,7 +83,10 @@ sudo nft -c -f /var/lib/housecarl/ruleset.nft # check it
|
||||
In shadow mode both files are shadow mode's, and the fallback filters
|
||||
nothing ([shadow.md](shadow.md#what-works-differently)).
|
||||
|
||||
Dynamic sets start empty after a reboot. The saved ruleset has no
|
||||
Dynamic sets start empty after a reboot. Sets from Jarl have the
|
||||
addresses they last had, in the saved ruleset and again when housecarld
|
||||
starts, whether Jarl is reachable yet or not ([jarl.md](jarl.md#when-jarl-cant-be-reached)).
|
||||
The saved ruleset has no
|
||||
flowtable, since its devices may not exist yet: housecarld adds it when
|
||||
it starts, and whenever a flowtable device appears
|
||||
([flowtable.md](flowtable.md#devices-that-come-and-go)).
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ else gets **grants**, `resource:access`:
|
||||
| Resource | read | write |
|
||||
|---|---|---|
|
||||
| `config` | settings, zones, sets, services, rules, NAT, the ruleset, history, interfaces, probation, changesets (the web UI shows the forms locked) | change them, stage and apply changesets (your own, or anyone's for administrators), undo, import, confirm and roll back; close connections; start and stop traces |
|
||||
| `sets` | dynamic sets' entries | add and remove entries |
|
||||
| `sets` | dynamic sets' entries, what sets from Jarl hold | add and remove entries |
|
||||
| `bans` | ban lists' bans, and the lists the grant names | ban and unban ([bans.md](bans.md)) |
|
||||
| `log` | hit counters, the log, connections, traces | – |
|
||||
| `audit` | the audit log | – |
|
||||
|
||||
@@ -83,6 +83,19 @@ change to the ruleset, and a restart of housecarld; a reboot empties
|
||||
them. A token can be limited to one set ([permissions.md](permissions.md)):
|
||||
that is how another program (Steward's captive portal) lets a client in.
|
||||
|
||||
A set **from Jarl** holds the addresses of names in Jarl, the BIND
|
||||
manager: every A and AAAA record of each name, CNAMEs followed, read again
|
||||
every minute. Only addresses within the zones and networks it names are
|
||||
used, so a changed record can't open the firewall to anything else:
|
||||
|
||||
```sh
|
||||
hc set add nas --jarl nas.example.com@internal --within lan
|
||||
hc set jarl nas # what it holds, and why
|
||||
```
|
||||
|
||||
Views, CNAMEs, what happens when Jarl can't be reached and the rest are in
|
||||
[jarl.md](jarl.md#sets-from-jarl).
|
||||
|
||||
## Services
|
||||
|
||||
A service names ports of a protocol (`tcp`, `udp`, `sctp`) and ICMP
|
||||
|
||||
+10
-1
@@ -53,9 +53,14 @@ are unique per kind, ignoring case.
|
||||
|---|---|---|
|
||||
| `set_name_reserved` | error | a set name starting with `meter_`, `conn_`, `quota_`, `stats_` or `ban_`: Housecarl's own sets (rate limits, quotas, statistics, ban lists) use those |
|
||||
| `set_entry` | error | an entry that isn't an address, prefix (without host bits) or range |
|
||||
| `set_empty` | warning | a set (not dynamic) with no entries: rules using it match nothing |
|
||||
| `set_empty` | warning | a set (not dynamic, not from Jarl) with no entries: rules using it match nothing |
|
||||
| `set_dynamic_entries` | error | configured entries on a dynamic set: its entries come at run time |
|
||||
| `set_timeout` | error | a timeout on a set that isn't dynamic, or under a second |
|
||||
| `set_jarl_entries` | error | configured entries on a set from Jarl: its addresses are its names' ([jarl.md](jarl.md#sets-from-jarl)) |
|
||||
| `set_jarl_dynamic` | error | a set both dynamic and from Jarl |
|
||||
| `set_jarl_names` | error | a set from Jarl without names, with one that isn't a DNS name (a wildcard, say) or a bad view, or with a name twice |
|
||||
| `set_jarl_within` | error | a set from Jarl without `within`, or with an entry there that is neither a zone nor an address, network or range |
|
||||
| `set_jarl_anywhere` | warning | a set from Jarl `within 0.0.0.0/0` or `::/0`: any address may come in, so whoever changes the names in Jarl decides what it holds |
|
||||
| `service_empty` | error | a service with no entries |
|
||||
| `service_entry` | error | an entry that isn't `PROTO/PORTS` (`tcp`, `udp`, `sctp`; a port or range) or `icmp/TYPE`, `icmpv6/TYPE` with a type nftables knows |
|
||||
|
||||
@@ -134,6 +139,10 @@ configuration: the dashboard and `hc status` show them.
|
||||
| `conntrack_full` | warning, *host* | the connection tracking table is over 80% full; once it is full, the kernel drops new connections. Raise `net.netfilter.nf_conntrack_max`, or find what opens so many ([stats.md](stats.md#warnings)) |
|
||||
| `takeover_pending` | warning, *host* | the table you imported from is still in force beside Housecarl's, so a packet must get past both; `hc takeover TABLE` deletes it once Housecarl's rules are right |
|
||||
| `shadow_unprotected` | warning, *host* | shadow mode, and no other table has a filter chain on the input hook: Housecarl's ruleset blocks nothing, and nothing else protects the machine. Load your firewall, or leave shadow mode ([shadow.md](shadow.md)) |
|
||||
| `jarl_name` | warning, *host* | a set from Jarl has a name that gives no addresses, and why: no A, AAAA or CNAME record, a wildcard only, a CNAME out of Jarl's zones or in a loop, below a delegation, a zone Jarl doesn't hold the records of, or in more than one view without one named ([jarl.md](jarl.md#what-a-name-gives)) |
|
||||
| `jarl_outside` | warning, *host* | an address a name has in Jarl is left out of its set: it isn't within the set's zones and networks ([jarl.md](jarl.md#within)) |
|
||||
| `jarl_unreachable` | warning, *host* | reading Jarl failed (unreachable, the token refused…), for every name or one; the sets keep the addresses they had ([jarl.md](jarl.md#when-jarl-cant-be-reached)) |
|
||||
| `jarl_not_configured` | warning, *host* | sets from Jarl, but no `jarl.server` in `housecarl.yaml`: they keep the addresses they last had |
|
||||
| `shadow_raw_left_out` | warning, *host* | shadow mode leaves this raw rule out: it does more to packets than accept, drop or reject (NAT, setting a mark or field, a jump, a verdict map…). Its hits aren't counted, and the rules after it see what it would have taken ([shadow.md](shadow.md#what-changes-in-the-ruleset)) |
|
||||
|
||||
In shadow mode `nftables_service` and `takeover_pending` aren't raised:
|
||||
|
||||
Reference in new issue
Block a user