- 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>
135 lines
5.0 KiB
Markdown
135 lines
5.0 KiB
Markdown
# 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`.
|