The view shown in the zone list and on the zone page now links to that
view. The zone page and changeset page get breadcrumbs, and the zone list
keeps its filters in the address so going back restores them. Views can
be given a colour (Views > Edit, `jarl view color`, PUT /views/{id}/color)
so zones of the same view are easy to spot; the colour is UI-only, kept
outside changesets, and needs no deploy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
140 lines
5.3 KiB
Markdown
140 lines
5.3 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`.
|
|
* Give each view a colour (Views → Edit → Colour, or
|
|
`jarl view color internal green`) to tell the zones of each view apart
|
|
in the zone list. The colour only affects the web UI, so it applies at
|
|
once, with no changeset or deploy. `jarl view color internal default`
|
|
goes back to the link colour.
|
|
* Zone files are stored per view: `/var/lib/bind/jarl/internal/adbraun.lol.zone`,
|
|
`/var/lib/bind/jarl/external/adbraun.lol.zone`.
|