feat: Unbound DNS-Resolver — vollständig (Renderer + Handler + UI)

Stub raus, vollständig implementiert:

* Migration 0014: dns_settings (single-row) + dns_zones.forward_to.
  Default-Settings sind sinnvoll für die typische LAN-Resolver-Rolle
  (1.1.1.1 + 9.9.9.9 upstream, localnet allow, DNSSEC + qname-min on).
* internal/services/dns: CRUD-Repo für zones, records, settings.
* internal/handlers/dns.go: REST /api/v1/dns/zones, /records, /settings
  mit Auto-Reload nach jeder Mutation.
* internal/unbound/unbound.cfg.tpl + unbound.go: Renderer schreibt
  /etc/unbound/unbound.conf.d/edgeguard.conf direkt (kein Symlink-
  Dance, weil AppArmor unbound nur /etc/unbound erlaubt). Local-zones
  authoritativ aus dns_records; forward-zones per stub-zone; default-
  forwarders catchen alles sonst.
* main.go: dnsRepo + unbound-Reloader injiziert.
* render.go: unbound.New() bekommt Pool.
* postinst:
  - Conf-Datei /etc/unbound/unbound.conf.d/edgeguard.conf wird als
    edgeguard:edgeguard 0644 angelegt damit Renderer schreiben kann.
  - /etc/edgeguard + Service-Subdirs auf 0755 (Squid + Unbound laufen
    NICHT als edgeguard, brauchen Read-Traversal).
  - Sudoers: systemctl reload unbound.service whitelisted.
* Template: chroot:"" (Conf liegt außerhalb /var/lib/unbound default-
  chroot), DNSSEC-Trust-Anchor NICHT setzen (Distro hat schon
  root-auto-trust-anchor-file.conf — sonst doppelter Anchor → start
  failure).
* Frontend /dns: PageHeader + zwei Tabs (Zones + Resolver-Settings).
  Zones-Tab mit Drawer für Records (CRUD pro Zone, A/AAAA/CNAME/TXT/
  MX/SRV/NS/PTR/CAA). Sidebar-Eintrag unter Network.
* i18n DE/EN für dns.* Block.

Verified end-to-end: render → unbound restart → dig @127.0.0.1
example.com → 104.20.23.154 / 172.66.147.243.

Version 1.0.34 (mehrere Iterationen wegen AppArmor + chroot + perms).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-11 06:24:51 +02:00
parent 72269f5b7c
commit e537d70e04
19 changed files with 1416 additions and 23 deletions

View File

@@ -14,6 +14,7 @@
"vpn": "VPN",
"wireguard": "WireGuard",
"forwardProxy": "Forward-Proxy",
"dns": "DNS",
"firewall": "Firewall",
"cluster": "Cluster",
"settings": "Einstellungen",
@@ -399,6 +400,51 @@
"wg": "WireGuard"
}
},
"dns": {
"title": "DNS (Unbound)",
"intro": "Unbound-Resolver auf :53. Lokale Zonen (authoritativ aus DNS-Records) und Forward-Zonen (per stub-zone weiter zu fremden Resolvern). Default-Forwarder für alles andere.",
"tabs": { "zones": "Zonen", "settings": "Resolver-Settings" },
"zone": {
"name": "Zone-Name",
"nameExtra": "FQDN ohne führenden/abschließenden Punkt — z.B. internal.netcell-it.de",
"type": "Typ",
"typeLocal": "local — authoritativ (records hier)",
"typeForward": "forward — stub-zone zu fremdem Resolver",
"forwardTo": "Upstream-Resolver",
"forwardToExtra": "Komma-separierte IP-Liste — z.B. '10.0.0.53, 8.8.8.8'",
"description": "Beschreibung",
"records": "Records …",
"add": "Zone hinzufügen",
"edit": "Zone bearbeiten",
"deleteConfirm": "Zone {{name}} mit allen Records wirklich löschen?"
},
"record": {
"name": "Name",
"nameExtra": "Relativ zur Zone (z.B. 'mailcow') oder FQDN mit abschließendem Punkt.",
"type": "Typ",
"value": "Wert",
"valueExtra": "RDATA in Textform: A → IP, CNAME → FQDN, MX → 'priority host', TXT → 'string'.",
"ttl": "TTL (sec)",
"drawerTitle": "DNS-Records",
"add": "Record hinzufügen",
"edit": "Record bearbeiten",
"deleteConfirm": "Record {{name}} wirklich löschen?"
},
"settings": {
"intro": "Globale Resolver-Settings. Änderungen hier reloaden Unbound automatisch.",
"listenAddresses": "Listen-Adressen",
"listenAddressesExtra": "Komma-separiert. Standard 127.0.0.1+::1 — wenn LAN-Clients fragen sollen, z.B. die LAN-Iface-IP zusätzlich (10.10.20.3).",
"listenPort": "Port",
"upstreamForwards": "Default-Forwarders",
"upstreamForwardsExtra": "Wo geht alles hin was nicht lokal ist. Default 1.1.1.1 + 9.9.9.9.",
"accessACL": "Access-ACL (CIDRs)",
"accessACLExtra": "Wer darf diesen Resolver benutzen.",
"dnssec": "DNSSEC validieren",
"qnameMin": "QName-Minimisation (privacy)",
"cacheMin": "Cache min-TTL",
"cacheMax": "Cache max-TTL"
}
},
"fwd": {
"title": "Forward-Proxy (Squid)",
"intro": "Squid-basierter Forward-Proxy auf :3128. ACLs werden top-down nach Priority ausgewertet — first-match wins. Wenn keine Regel passt, gewinnt der Default: nur localnet (10/8, 172.16/12, 192.168/16) darf raus.",

View File

@@ -14,6 +14,7 @@
"vpn": "VPN",
"wireguard": "WireGuard",
"forwardProxy": "Forward proxy",
"dns": "DNS",
"firewall": "Firewall",
"cluster": "Cluster",
"settings": "Settings",
@@ -399,6 +400,51 @@
"wg": "WireGuard"
}
},
"dns": {
"title": "DNS (Unbound)",
"intro": "Unbound resolver on :53. Local zones (authoritative from DNS records) and forward zones (stub-zone to remote resolvers). Default forwarders catch everything else.",
"tabs": { "zones": "Zones", "settings": "Resolver settings" },
"zone": {
"name": "Zone name",
"nameExtra": "FQDN without leading/trailing dot — e.g. internal.netcell-it.de",
"type": "Type",
"typeLocal": "local — authoritative (records here)",
"typeForward": "forward — stub-zone to remote resolver",
"forwardTo": "Upstream resolvers",
"forwardToExtra": "Comma-separated IP list — e.g. '10.0.0.53, 8.8.8.8'",
"description": "Description",
"records": "Records …",
"add": "Add zone",
"edit": "Edit zone",
"deleteConfirm": "Really delete zone {{name}} and all its records?"
},
"record": {
"name": "Name",
"nameExtra": "Relative to zone (e.g. 'mailcow') or FQDN with trailing dot.",
"type": "Type",
"value": "Value",
"valueExtra": "RDATA in text form: A → IP, CNAME → FQDN, MX → 'priority host', TXT → 'string'.",
"ttl": "TTL (sec)",
"drawerTitle": "DNS records",
"add": "Add record",
"edit": "Edit record",
"deleteConfirm": "Really delete record {{name}}?"
},
"settings": {
"intro": "Global resolver settings. Saves reload Unbound automatically.",
"listenAddresses": "Listen addresses",
"listenAddressesExtra": "Comma-separated. Default 127.0.0.1+::1 — to let LAN clients query, add the LAN iface IP (e.g. 10.10.20.3).",
"listenPort": "Port",
"upstreamForwards": "Default forwarders",
"upstreamForwardsExtra": "Where everything not local goes. Default 1.1.1.1 + 9.9.9.9.",
"accessACL": "Access ACL (CIDRs)",
"accessACLExtra": "Who is allowed to use this resolver.",
"dnssec": "DNSSEC validation",
"qnameMin": "QName minimisation (privacy)",
"cacheMin": "Cache min-TTL",
"cacheMax": "Cache max-TTL"
}
},
"fwd": {
"title": "Forward proxy (Squid)",
"intro": "Squid-based forward proxy on :3128. ACLs are evaluated top-down by priority — first match wins. If no rule matches, the default permits only localnet (10/8, 172.16/12, 192.168/16).",