Files
jarl/docs/views.md
T
abrown38andClaude Opus 5.5 0795a1001e 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>
2026-10-07 15:36:24 +00:00

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`.