Documentation
¶
Overview ¶
Package dns implements the chassis's authoritative-DNS head: a personality that answers DNS queries for zones explicitly delegated to this chassis, straight from an in-memory snapshot of the dns_zones/dns_records tables.
Phase 1 scope (internal docs/todo-dns-authority.md): materialized records only — no record synthesis, no DNS-01, no DNSSEC. The server is authoritative-only and NEVER recursive: a query whose name falls under no served zone is REFUSED.
Data-plane discipline: a query runs NO opstack and never touches the bus. It is answered from a prebuilt ZoneSnapshot that is rebuilt on config-apply (dbcache OnReload), so the hot path does zero DB reads — the same "no syscalls on the hot path" rule the static-asset index and redaction registry follow.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RegisterChallengeStore ¶
func RegisterChallengeStore(scheme string, f func(dsn string) (ChallengeStore, error))
RegisterChallengeStore registers a factory for a DSN scheme (e.g. "postgres"). Called from an overlay init(); core registers nothing.
Types ¶
type ChallengeStore ¶
type ChallengeStore interface {
// Present publishes a challenge value at the given owner FQDN. Idempotent
// per (fqdn, value); multiple distinct values may coexist (ACME can
// place two during a single order). The value self-expires as a safety
// net for an abandoned solve; the normal lifecycle is an explicit CleanUp.
Present(fqdn, value string)
// CleanUp removes a specific (fqdn, value). Idempotent — removing an
// absent value, or one already expired, is a no-op.
CleanUp(fqdn, value string)
// ActiveTXT returns the live (unexpired) challenge values for an exact
// owner FQDN, or nil. fqdn is matched case-insensitively as a
// trailing-dot FQDN.
ActiveTXT(fqdn string) []string
}
ChallengeStore holds the short-lived `_acme-challenge` TXT records the DNS head serves while a cert is being issued via ACME DNS-01. It is the shared substrate for Phase 3: the in-process ACME solver (bundled in chassis/tls) AND the RFC2136 UPDATE receiver (the Caddy deploy) both write through it, and the query path reads it.
These records are deliberately OUTSIDE the ZoneSnapshot / dbcache reload cycle: they live for seconds-to-minutes and churn during issuance, so routing them through config-apply would be both too slow and semantically wrong (they are not durable config). The store is a small, lock-guarded, self-expiring map consulted on the query hot path — but only for the `_acme-challenge.*` name, so normal lookups never touch it.
Seam shape mirrors chassis/auth/registry/dialect.go: core ships the interface + an in-memory default; a shared (Postgres) implementation is registered out of tree by the overlay so a challenge written on one chassis is served by another when Let's Encrypt's validator lands there (see ChallengeStoreForDSN).
func ChallengeStoreForDSN ¶
func ChallengeStoreForDSN(dsn string) (ChallengeStore, error)
ChallengeStoreForDSN selects the challenge backend. An empty DSN (the default) or any scheme without a registered factory yields the in-memory store — the safe, single-node default. A recognised scheme builds the registered backend.
type DNSController ¶
type DNSController struct {
// contains filtered or unexported fields
}
DNSController owns the authoritative-DNS listeners and the prebuilt zone snapshot they answer from.
One controller hosts a UDP and a TCP listener per configured address. The snapshot is rebuilt on every dbcache reload (config-apply, fs-watch) and swapped atomically, so the query hot path does zero DB work and never blocks a reload.
DNS is OFF by default. Both gates must be flipped:
- `dns` must appear in `--personalities`
- `--dns-listen-addrs` must be non-empty
func NewController ¶
func NewController(ctx context.Context, pu *processor.Unit) *DNSController
NewController constructs (but does not start) a DNS controller. Mirrors the other personalities' constructor shape so server.go can treat them uniformly.
func (*DNSController) ChallengeStore ¶
func (c *DNSController) ChallengeStore() ChallengeStore
ChallengeStore exposes the controller's transient ACME-challenge store so the in-process DNS-01 solver (chassis/tls) writes to the same instance this head serves from. Nil only before NewController runs.
func (*DNSController) Origins ¶
func (c *DNSController) Origins() []string
Origins returns the canonical origins currently served, from the live snapshot (lock-free atomic read; safe to call from an OnReload hook). The bundled cert manager uses this to recompute the wildcard cert set when delegated zones change.
func (*DNSController) Start ¶
func (c *DNSController) Start()
Start binds UDP+TCP listeners on each configured address and serves authoritative DNS from the zone snapshot. The double-gate (personality string AND non-empty listen addrs) means an upgrade can't silently acquire a privileged listener.
func (*DNSController) Stop ¶
func (c *DNSController) Stop()
Stop drains in-flight queries and closes the listeners with a 5s ceiling so a wedged TCP session can't stall chassis shutdown.
type SynthConfig ¶
type SynthConfig struct {
Nameservers []string // FQDNs advertised as the apex NS set
EdgeIPs []string // A/AAAA targets for apex + per-stack hosts
MXHost string // MX target hostname (the LMTP head's public name)
MXPriority uint16
TTL uint32 // TTL for synthesized records (falls back to the zone default if 0)
// Mail-auth TXT at the apex, emitted alongside the MX (i.e. only when
// MXHost is set). SPFOverride replaces the auto-derived SPF; DMARC is the
// full policy string. Both overridable per-zone by a materialized
// dns_records TXT (first-match-clears).
SPFOverride string // "" → auto-derive from EdgeIPs + mx
DMARC string // e.g. "v=DMARC1; p=none"
// StructuredSuffix is the platform's default structured-host suffix
// (TXCO_STRUCTURED_HOST_SUFFIX), bare (no leading dot), e.g.
// "stacks.thanks.computer". When a served zone's origin equals it, the
// zone is the default-suffix zone and gets a WILDCARD RRset (so every
// <stack>-<rand>.<suffix> resolves) instead of per-stack records.
StructuredSuffix string
}
SynthConfig parameterizes the synthesized record pattern for delegated (pattern-mode) zones. Empty fields disable the corresponding records — synthesis is purely additive and degrades to "SOA only" when nothing is configured.
func EffectiveSynthConfig ¶
func EffectiveSynthConfig(db *sql.DB, flagDefaults SynthConfig) SynthConfig
EffectiveSynthConfig is the synthesis config the chassis actually uses: the operator-set dns_settings row when present, otherwise the boot `--dns-*` flag defaults (flagDefaults). Read at snapshot build and by the admin config/zone-create endpoints — never on the query hot path. A read failure or a missing row falls back to the flags, so existing flag-only deployments are unchanged.
func SynthConfigFrom ¶
func SynthConfigFrom(conf config.Config) SynthConfig
SynthConfigFrom builds a SynthConfig from chassis config. Single source so the dns controller and the admin render endpoint synthesize identically.
type ZoneSnapshot ¶
type ZoneSnapshot struct {
// contains filtered or unexported fields
}
ZoneSnapshot is an immutable, prebuilt view of every served zone. Build it with BuildSnapshot; serve from it with Lookup; preview it with Render. Swap a whole *ZoneSnapshot atomically on reload — never mutate one in place.
func BuildSnapshot ¶
func BuildSnapshot(db *sql.DB, cfg SynthConfig, logger *zap.Logger) (*ZoneSnapshot, error)
BuildSnapshot reads all active zones + records from the runtime mirror and assembles a ZoneSnapshot. A malformed individual record is logged and skipped (best-effort, like the LMTP MIME parse) rather than darkening the whole zone; only a DB-level failure returns an error. Pass dbc.Snapshot() — never a captured dbc.Db handle.
func (*ZoneSnapshot) Lookup ¶
Lookup resolves a single question against the snapshot.
rcode REFUSED → no served zone covers the name (we are
authoritative-only, never recursive)
rcode NOERROR + answer → matching RRset
rcode NOERROR + ns(SOA) → NODATA: name exists, type absent
rcode NXDOMAIN + ns(SOA) → name does not exist in the zone
ANY is refused (no ANY expansion — anti-amplification). CNAME chasing and wildcards are out of scope for Phase 1.
func (*ZoneSnapshot) Origins ¶
func (s *ZoneSnapshot) Origins() []string
Origins returns every canonical origin currently served, sorted. Used by the bundled cert manager to decide which `*.<origin>` + apex wildcard certificates to obtain/renew.
func (*ZoneSnapshot) OriginsForTenant ¶
func (s *ZoneSnapshot) OriginsForTenant(tenantID string) []string
OriginsForTenant returns the canonical origins served for a tenant, sorted. Used by the admin render endpoint.
func (*ZoneSnapshot) Render ¶
func (s *ZoneSnapshot) Render(origin string) (string, bool)
Render emits the zone TxCo would serve for origin in standard zone-file (presentation) form, or ok=false if the origin isn't served. The header comment carries the UTC generation stamp (the serial formatted as an RFC3339 instant) so an operator reads one unambiguous value; the SOA serial is the same number on the wire.