Separate authorised resolvers from what they may forward towards #5

Merged
gsadmin merged 1 commits from development into main 2026-08-25 16:40:19 +00:00
Owner

Reworks name resolution to match the model in the operator's production configuration, and stops carrying anything about DNS across from the machine that generates the document.

Before

One permit and one blanket deny. That said who may be queried, but nothing about what a resolver is then allowed to do — and it pinned the appliance to whatever resolver the generating host happened to be using.

After

Two aliases, separating who may be queried from what those resolvers may forward to:

clients ──► AUTHORIZED_DNS_SERVERS_INTERNAL ──► AUTHORIZED_DNS_SERVERS_UPSTREAM
            (the appliance, by default)         (1.1.1.1, 1.0.0.1 by default)
Was Now
DNS_SERVERS_001 AUTHORIZED_DNS_SERVERS_INTERNAL
(none) AUTHORIZED_DNS_SERVERS_UPSTREAM
DNS_PORTS_TCP_001 AUTHORIZED_DNS_PORTS_TCP
DNS_PORTS_UDP_001 AUTHORIZED_DNS_PORTS_UDP

Eight rules, four per transport:

seq 100/101  pass   AUTHORIZED_DNS_SERVERS_INTERNAL -> AUTHORIZED_DNS_SERVERS_UPSTREAM
seq 110/111  pass   RFC1918                         -> AUTHORIZED_DNS_SERVERS_INTERNAL
seq 120/121  pass   RFC1918                         -> (self)
seq 200/201  block  RFC1918                         -> NOT AUTHORIZED_DNS_SERVERS_INTERNAL

The deny inverts its destination (destination_not=1) rather than being a blanket deny sitting after the permits. It states the policy as meant — DNS to anything unauthorised is refused — and stays correct as the alias is edited, instead of depending on sequence ordering.

Rules 100/101 are inert today, because the internal alias holds only the appliance and traffic the appliance originates is not evaluated by an inbound rule. They are not redundant: they are what makes the alias extensible. Adding a real resolver to the internal alias gives it both directions at once — reached by clients via 110/111, forwarding upstream via 100/101 — with nothing further to write.

Nothing is read from the generating host

A resolver that machine uses is a fact about that machine, not about the network the appliance joins. Seeding it wrote a site-specific address into every document produced. The upstream alias now defaults to public resolvers and is overridable with -UpstreamDNSServerList.

Get-HostUpstreamDNSConfiguration still runs, but only for HostNetworkList, which feeds the parent-network exclusions. Its RecommendedDNSServerList is no longer consumed.

How the appliance resolves itself

Setting Value Why
system/dnsserver absent Nothing static is baked in
system/dnsallowoverride 1 The WAN lease populates the system resolver list
system/dnslocalhost 1 The appliance asks its own resolver first
unboundplus/general/local_zone_type transparent A local record is answered before anything is forwarded
unboundplus/forwarding/enabled 1 Forward to the system list rather than recursing

Forwarding is the piece that makes the lease meaningful. Left recursing, Unbound would query the root servers directly and the upstream the joined network expects would never be consulted — the appliance would resolve correctly while ignoring its own network's resolver.

The intended consequence: local records can be defined on the appliance and are answered authoritatively for every network behind it, while everything else follows the WAN.

Verification

  • Parse 0 failures, doc/parameter drift 0, 144 markdown links checked, 0 broken.
  • Generated document carries the eight rules in the intended order with the deny inverted; aliases hold the appliance address on every planned network plus the public upstreams.
  • -UpstreamDNSServerList @('9.9.9.9','149.112.112.112') honoured; system/dnsserver still empty.
  • No address belonging to the generating host (172.16.16.55, 172.16.16.1, 10.130.224.1) appears anywhere in the output.
  • Every alias named by any rule resolves. 53 rules, 30 aliases, exit 0.

Not verified live — this is XML/source-level only; the resolver behaviour needs a booted appliance.

🤖 Generated with Claude Code

Reworks name resolution to match the model in the operator's production configuration, and stops carrying anything about DNS across from the machine that generates the document. ## Before One permit and one blanket deny. That said *who may be queried*, but nothing about what a resolver is then allowed to do — and it pinned the appliance to whatever resolver the generating host happened to be using. ## After Two aliases, separating *who may be queried* from *what those resolvers may forward to*: ``` clients ──► AUTHORIZED_DNS_SERVERS_INTERNAL ──► AUTHORIZED_DNS_SERVERS_UPSTREAM (the appliance, by default) (1.1.1.1, 1.0.0.1 by default) ``` | Was | Now | | --- | --- | | `DNS_SERVERS_001` | `AUTHORIZED_DNS_SERVERS_INTERNAL` | | _(none)_ | `AUTHORIZED_DNS_SERVERS_UPSTREAM` | | `DNS_PORTS_TCP_001` | `AUTHORIZED_DNS_PORTS_TCP` | | `DNS_PORTS_UDP_001` | `AUTHORIZED_DNS_PORTS_UDP` | Eight rules, four per transport: ``` seq 100/101 pass AUTHORIZED_DNS_SERVERS_INTERNAL -> AUTHORIZED_DNS_SERVERS_UPSTREAM seq 110/111 pass RFC1918 -> AUTHORIZED_DNS_SERVERS_INTERNAL seq 120/121 pass RFC1918 -> (self) seq 200/201 block RFC1918 -> NOT AUTHORIZED_DNS_SERVERS_INTERNAL ``` **The deny inverts its destination** (`destination_not=1`) rather than being a blanket deny sitting after the permits. It states the policy as meant — DNS to anything unauthorised is refused — and stays correct as the alias is edited, instead of depending on sequence ordering. **Rules 100/101 are inert today**, because the internal alias holds only the appliance and traffic the appliance originates is not evaluated by an inbound rule. They are not redundant: they are what makes the alias extensible. Adding a real resolver to the internal alias gives it both directions at once — reached by clients via 110/111, forwarding upstream via 100/101 — with nothing further to write. ## Nothing is read from the generating host A resolver that machine uses is a fact about *that machine*, not about the network the appliance joins. Seeding it wrote a site-specific address into every document produced. The upstream alias now defaults to public resolvers and is overridable with `-UpstreamDNSServerList`. `Get-HostUpstreamDNSConfiguration` still runs, but only for `HostNetworkList`, which feeds the parent-network exclusions. Its `RecommendedDNSServerList` is no longer consumed. ## How the appliance resolves itself | Setting | Value | Why | | --- | --- | --- | | `system/dnsserver` | absent | Nothing static is baked in | | `system/dnsallowoverride` | `1` | The WAN lease populates the system resolver list | | `system/dnslocalhost` | `1` | The appliance asks its own resolver first | | `unboundplus/general/local_zone_type` | `transparent` | A local record is answered before anything is forwarded | | `unboundplus/forwarding/enabled` | `1` | Forward to the system list rather than recursing | Forwarding is the piece that makes the lease meaningful. Left recursing, Unbound would query the root servers directly and the upstream the joined network expects would never be consulted — the appliance would resolve correctly while ignoring its own network's resolver. The intended consequence: **local records can be defined on the appliance** and are answered authoritatively for every network behind it, while everything else follows the WAN. ## Verification - Parse 0 failures, doc/parameter drift 0, 144 markdown links checked, 0 broken. - Generated document carries the eight rules in the intended order with the deny inverted; aliases hold the appliance address on every planned network plus the public upstreams. - `-UpstreamDNSServerList @('9.9.9.9','149.112.112.112')` honoured; `system/dnsserver` still empty. - No address belonging to the generating host (`172.16.16.55`, `172.16.16.1`, `10.130.224.1`) appears anywhere in the output. - Every alias named by any rule resolves. 53 rules, 30 aliases, exit 0. Not verified live — this is XML/source-level only; the resolver behaviour needs a booted appliance. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
gsadmin added 1 commit 2026-08-25 16:40:06 +00:00
Modelled on the operator's production configuration, which was supplied as a
sample. Name resolution was one permit and one blanket deny, which said who may
be queried but had nothing to say about what a resolver is then allowed to do,
and pinned the appliance to whatever name server the machine generating the
document happened to be using.

Two aliases now carry the policy. The internal alias holds the resolvers a client
may query, which is the appliance itself by default, and the upstream alias holds
what an authorised resolver may forward towards. Eight rules follow, four per
transport, because each transport carries a different set of ports.

The deny inverts its destination rather than being a blanket deny placed after
the permits. That states the policy as it is actually meant, which is that name
resolution towards anything unauthorised is refused, and it stays true as the
alias is edited instead of depending upon the deny sitting at a later sequence
than every permit.

The rule permitting an authorised resolver to forward upstream changes nothing
while the internal alias holds only the appliance, because traffic the appliance
originates is not evaluated by an inbound rule. It is not redundant for that
reason. It is what makes the alias worth having: adding a real resolver to the
internal alias gives it both directions at once, reached by clients through one
rule and forwarding upstream through the other, with nothing further to write.

Nothing about name resolution is read from the machine that generates the
document any more. A resolver that machine uses is a fact about that machine
rather than about the network the appliance joins, and seeding it wrote a site
specific address into every document produced. The upstream alias defaults to
public resolvers and is overridable, and the host inspection that remains is only
for the networks it is attached to, which the parent network selection excludes.

The appliance resolves through its own resolver, which is what allows a local
record to be defined and answered authoritatively for the networks behind it, and
takes its upstream from the wide area network lease. Three settings carry that:
no static name server address is written, the lease is permitted to populate the
system list, and the local host is preferred so the appliance asks itself first.

The resolver is also switched to forwarding. Left recursing it would query the
root servers directly and the upstream the joined network expects would never be
consulted, so the appliance would resolve correctly while ignoring its own
network's resolver, which is not what taking the upstream from the lease is meant
to mean. The local zone stays transparent, so a local record is still answered
before anything is forwarded.

Verified that the document carries the eight rules in the intended order with the
deny inverted, that the aliases hold the appliance address on every planned
network and the public upstreams, that an explicit upstream list is honoured, that
no address belonging to this host appears anywhere in the output, and that every
alias named by any rule still resolves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gsadmin merged commit b291b68105 into main 2026-08-25 16:40:19 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Grace-Solutions/Invoke-OPNSenseVMDeployment#5