Fix zone list filtering and BIND warning display; add a views guide
- jarl zone list showed only primary zones. zone create binds its --type flag (default "primary") to the same variable as list's --type, so the default leaked into the list filter and hid hint, secondary and forward zones. list now has its own variable. TestListQueries runs list commands against a fake server and checks the filters they send. - named-checkconf/named-checkzone messages are classified by the tool's exit code: on success, everything printed is a warning. Warnings such as 'both "recursion no;" and "allow-recursion" active' were shown as errors. The CLI, jarld check and the web UI now label them as warnings and skip the routine "loaded serial" lines. - The zone_outside_view error points at --adopt-zones. - docs/views.md: step-by-step split DNS for a server that has no views yet, tested on a copy of a stock Ubuntu 26.04 setup. Linked from the README, concepts and validation docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3c73afe0e1
commit
0795a1001e
13 files changed
+261
-26
No files matched your search
+134
@@ -0,0 +1,134 @@
|
||||
# Split DNS with views
|
||||
|
||||
Views let named answer differently depending on who asks. The usual setup
|
||||
for a homelab that is also authoritative on the internet has two views:
|
||||
|
||||
* **internal**: your networks get recursion (they use the server as their
|
||||
resolver) and see private addresses (`nas.adbraun.lol → 10.10.0.20`) and
|
||||
internal-only zones such as reverse zones for private networks.
|
||||
* **external**: everyone else gets authoritative answers only. That means
|
||||
public addresses, no recursion, and no internal zones.
|
||||
|
||||
## How named treats views (and why Jarl asks to move your zones)
|
||||
|
||||
Without any `view` statements, named puts every zone into an implicit
|
||||
default view. That's why an imported server shows no views. **As soon as
|
||||
one view exists, named requires every zone to be inside a view.** So the
|
||||
first view you create has to take over the existing zones, including the
|
||||
root hints zone `.` that Debian/Ubuntu configure.
|
||||
|
||||
Jarl does this in the same changeset that creates the view:
|
||||
|
||||
* web UI: Views → New view. The box "Move the N zone(s) that are not in a
|
||||
view into this view" is ticked by default.
|
||||
* CLI: `jarl view create NAME --adopt-zones`
|
||||
* API: `"adopt_zones": true`
|
||||
|
||||
Creating the first view without it is refused before anything reaches
|
||||
named (`zone_outside_view`).
|
||||
|
||||
Views are matched in order: the first view whose `match-clients` (and
|
||||
`match-destinations`) fit the client wins. Put the specific view first and
|
||||
the catch-all last.
|
||||
|
||||
## Step by step
|
||||
|
||||
### 1. Check the ACL for your networks
|
||||
|
||||
The internal view matches your networks. Usually that's the ACL you already
|
||||
use for `allow-recursion`:
|
||||
|
||||
```sh
|
||||
jarl acl list
|
||||
jarl acl update homelab --add localhost # if missing; see the note below
|
||||
```
|
||||
|
||||
Include **localhost** if the DNS server itself uses `127.0.0.1` as its
|
||||
resolver. Otherwise the server's own lookups land in the external view and
|
||||
get no recursion.
|
||||
|
||||
### 2. Create the internal view and move the existing zones into it
|
||||
|
||||
```sh
|
||||
jarl view create internal --match-clients homelab --adopt-zones --dry-run # look first
|
||||
jarl view create internal --match-clients homelab --adopt-zones
|
||||
```
|
||||
|
||||
```
|
||||
create view internal
|
||||
move zone . into view internal
|
||||
move zone 0.10.10.in-addr.arpa into view internal
|
||||
move zone adbraun.lol into view internal
|
||||
applied (changeset 4)
|
||||
```
|
||||
|
||||
Nothing changes for your clients: they match `internal` and get the same
|
||||
answers as before. The server-wide options (`recursion`, `allow-recursion`,
|
||||
`forwarders`, ...) still apply, because views inherit them.
|
||||
|
||||
### 3. Create the external view
|
||||
|
||||
```sh
|
||||
jarl view create external --match-clients any
|
||||
jarl option set recursion no --view external
|
||||
jarl option set allow-recursion '{ none; }' --view external
|
||||
```
|
||||
|
||||
The inherited `allow-recursion { homelab; }` already keeps outsiders from
|
||||
recursing. The explicit `recursion no` also stops them reading the cache,
|
||||
and makes the intent obvious. Setting `allow-recursion { none; }` as well
|
||||
avoids named-checkconf's warning that `allow-recursion` is still active in a
|
||||
view with recursion off. Jarl warns (`security_open_resolver`) if a view
|
||||
ever offers recursion to everyone.
|
||||
|
||||
### 4. Give the external view its zones
|
||||
|
||||
**Different data per view (split DNS).** Copy the zone, then change what
|
||||
should differ:
|
||||
|
||||
```sh
|
||||
jarl zone create adbraun.lol --view external --clone-from adbraun.lol --clone-view internal
|
||||
jarl record list adbraun.lol --view external
|
||||
jarl record update <id> --data 203.0.113.20 # public address instead of 10.10.0.20
|
||||
jarl record delete adbraun.lol nas-internal A --view external
|
||||
```
|
||||
|
||||
From then on the two copies are separate zones. A record added to one isn't
|
||||
in the other, and the serials are separate.
|
||||
|
||||
**The same data in both views.** Use an `in-view` zone. The external view
|
||||
then serves the internal view's zone, so there is one copy to maintain:
|
||||
|
||||
```sh
|
||||
jarl zone create example.org --view external --type in-view --in-view internal
|
||||
```
|
||||
|
||||
Don't do that for zones with private data. Internal-only zones (reverse
|
||||
zones for 10.x / 192.168.x) simply don't get an external copy: named
|
||||
answers outsiders with NXDOMAIN from its built-in empty zones.
|
||||
|
||||
### 5. Check from both sides
|
||||
|
||||
```sh
|
||||
dig @ns1.adbraun.lol nas.adbraun.lol +short # from the LAN: 10.10.0.20
|
||||
dig @<public-ip> nas.adbraun.lol +short # from outside: 203.0.113.20
|
||||
dig @<public-ip> example.com # from outside: status REFUSED
|
||||
jarl check
|
||||
```
|
||||
|
||||
On the server, `dig -b <source address>` picks which view you test.
|
||||
|
||||
## Living with views
|
||||
|
||||
* Commands that name a zone that exists in several views need `--view`
|
||||
(`jarl record add adbraun.lol www A ... --view external`). Jarl refuses to
|
||||
guess.
|
||||
* `jarl acme` writes challenge records into **every** view that has the
|
||||
zone, so Let's Encrypt (external) and internal clients see the same
|
||||
thing. Use `--view` to limit it.
|
||||
* `jarl view order internal external` changes the match order, and
|
||||
`jarl view list` shows it.
|
||||
* Deleting a view is refused while it still has zones, unless you pass
|
||||
`--cascade`.
|
||||
* Zone files are stored per view: `/var/lib/bind/jarl/internal/adbraun.lol.zone`,
|
||||
`/var/lib/bind/jarl/external/adbraun.lol.zone`.
|
||||
Reference in new issue
Block a user