Skip to content

feat(dns): honor towonel.io/dns-record to opt hostnames out of the DNS handoff - #97

Merged
jacaudi merged 3 commits into
mainfrom
feat/dns-record-optout
Oct 31, 2026
Merged

jacaudi merged 3 commits into
mainfrom
feat/dns-record-optout

Conversation

@jacaudi

@jacaudi jacaudi commented Oct 31, 2026

Copy link
Copy Markdown
Owner

Summary

A per-object opt-out for the DNS handoff, plus the docs the operator survey called for. It lands before v1.10.0 is cut, so it ships in the same release as the handoff.

 Service / Gateway / HTTPRoute
+  metadata.annotations: towonel.io/dns-record: "false"     # present & not truthy → opt out (typos too, with a Warning)
 TowonelAgent.spec.services[]
+  dnsRecord: false                                        # new optional field; hand-written agents set it directly
 TowonelTunnel reconcile
   desiredDNSEndpoint(tt, view, edgeTargets
+                     , optOut)                            # any referencing agent opting a hostname out excludes it
  • Fail closed. Only an absent annotation or a truthy value publishes. Anything else opts out, and an unrecognized value also raises InvalidAnnotation.
  • Hub authorization unaffected. The hostname leaves only the DNSEndpoint and status.dnsRecords.
  • Observe-only agents. An ObserveOnly agent that serves an opted-out hostname without dnsRecord: false gets a Warning, not silence.
  • Docs (docs/dns.md):
    • Zone-apex hostnames. Apex CNAMEs depend on the provider; the remedy is the opt-out plus an ALIAS or A record.
    • Worked Recipe B. Reuse your existing public and LAN external-dns instances: audit first, label LAN-only objects and tunneled routes lan, then !=lan on the public instance and !=public on the LAN one. Gateways are filtered only by --gateway-label-filter (external-dns source/gateway.go:156-160).
    • k8s_gateway on the LAN.

Evidence

  • Before: the annotation was reserved and ignored. TestDNSRecordAnnotationKeepsHostnameOutOfDNSEndpoint failed: endpoints = [extra private public], want [extra public].
    After: it passes, as does TestDNSRecordAnnotationRemovalRepublishesHostname (removing the annotation clears dnsRecord and republishes the hostname).
  • Unit tests:
    • TestApplyDNSRecordAnnotation: absent, truthy, falsy, typo and empty values.
    • TestObserveUserAgentWarnsWhenDNSOptOutNotOnAgent.
    • TestDesiredDNSEndpointOptOut and TestDNSOptOutHostnames.
  • Mutation checks by the reviewer: dropping the annotation call, the optOut filter, the fail-closed branch or the observe-only warning each makes a test fail.
  • Full suite: task ci exits 0 (fmt, vet, -race tests with envtest, helm lint and template, verify-generated).

Merge Danger

Door: two-way. The field is additive and optional, and the annotation only removes records.

Blast Radius: DNS records

Raw-manifest installs must update the TowonelAgent CRD before relying on the annotation. A stale CRD that silently prunes dnsRecord leaves the hostname published.

🤖 Generated with Claude Code

jacaudi and others added 3 commits October 30, 2026 23:47
…S handoff

A Service, Gateway or HTTPRoute annotated `towonel.io/dns-record` with a
value that is present and not truthy (typos included; unrecognized values
also raise a Warning Event) stamps `dnsRecord: false` on the agent services
it contributes. The new optional `TowonelAgent.spec.services[].dnsRecord`
field lets hand-written agents do the same. The tunnel leaves any hostname
that any referencing agent opts out of the DNSEndpoint and status.dnsRecords
(explicit opt-out wins); hub authorization is unaffected. An ObserveOnly
agent that serves an opted-out hostname without the field gets a Warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The towonel.io/dns-record opt-out and dnsRecord field (source layer,
  agent CRD reference, example, README).
- Zone-apex hostnames: provider-dependent apex CNAMEs and the opt-out +
  ALIAS/A remedy.
- Recipe B as a worked alternative to Recipe A: audit, label LAN-only and
  tunneled routes `lan`, `!=lan` on the reused public instance, `!=public`
  on the LAN instance; Gateways are only filtered by --gateway-label-filter.
- k8s_gateway on the LAN (canonical codeberg repo, ignore label).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… test

The DNSEndpoint is applied before the tunnel's status write. Mutating the
spec in that window conflicts the status update, so inviteId is never
persisted; the delete then takes the invite-less path that needs no API
key and removes the finalizer, failing the follow-up update with NotFound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jacaudi
jacaudi merged commit 1761d02 into main Oct 31, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant