Repository · MIT license · Contributing · Security
Standalone provider-neutral Rust DNS service with its own Cargo workspace and
versioned dnsd-protocol crate. No sibling Apollo implementation dependencies.
certd speaks the external v1 wire protocol; it does not import this crate.
The gateway has no DNS client or provider credentials.
Configured modular adapters: Cloudflare, Route53, OVH and Hetzner. Reqwest uses
bounded streaming JSON responses; AWS credential resolution/signing and XML are
handled by the mature AWS SDK. Hickory handles DNS resolution and wire formats.
The same resolver verifies A, AAAA, CNAME, MX, NS, SRV, CAA and TXT. TXT verification
concatenates segments with strict UTF-8 decoding. A shared verification_server
can select a local resolver; otherwise system resolvers are used.
A Unix connection carries one big-endian u32 byte length then one JSON object.
Frames are capped at 256 KiB before allocation. Request fields: version,
operation_id, generation, action, provider, zone, record (name, kind,
value, ttl). The record is required for all actions. Names are canonical lowercase,
label-bounded and within the requested zone; ACME underscores are accepted.
Operation IDs are 1–96 ASCII bytes and positive generations fit signed 64 bits.
Actions: ensure_record, delete_record, inspect_record, verify_record,
wait_for_propagation. Responses echo operation ID and generation, with applied,
already_applied, accepted, failed, invalid or stale_generation. Unknown
request fields and unsupported versions are rejected; additive response fields
are allowed. Retain the exact operation payload and ID when retrying.
SQLite FULL synchronous WAL commits intent before provider effects. Operation IDs are durable: identical completed requests replay their exact stored result; conflicting payloads are rejected. Fences are scoped to provider, zone, name, kind and exact value, so concurrent DNS-01 proofs are independent. A single durable reconciler wakes through Notify and stored due-time timers; propagation is never an inner polling loop. Eight retries end in a stored terminal result.
Ensure persists effect_attempted before mutation. After ambiguous acknowledgement,
retries inspect the exact value instead of sending another creation request. This
prevents duplicates even with stale provider reads. If an effect never happened
or remains unobservable at the retry bound, the operation fails closed and needs
operator reconciliation/new operation; it cannot guarantee remote exactly-once
execution after an arbitrary network failure. TTL is a creation hint; existing RRset TTL is preserved. Delete removes the exact value and
preserves other TXT values. Provider-specific record encodings stay in adapters. Inspect/Verify/Wait do not
advance mutation generations.
Each managed zone/RRset must have one dnsd owner and no independent writers. The process lock prevents two local owners of the same state. Route53 has no conditional RRset update API: multiple owners of different state directories or outside writers can race its read/modify/write operation. Assign exclusive IAM and deployment ownership; this is a required deployment contract, not distributed locking supplied by this daemon.
Bounds: default 64 connections, maximum 1,024; provider registry maximum four;
256 KiB provider response bodies; at most ten pages/1,000 records; retained intents
10,000; reconciliation batches 1,024; finite HTTP/RPC/DNS deadlines. Capacity
exhaustion fails closed. Retention/pruning needs operator policy. State/socket
parents are private 0700, database/socket 0600. The authorized local client
must share the service OS identity. Future database schema versions are rejected.
socket = "/run/apollo-dnsd/control.sock"
state = "/var/lib/apollo-dnsd/state.db"
max_inflight = 64
[cloudflare]
api_token = "scoped-token"
# [hetzner]
# api_token = "scoped-token"
# [ovh]
# application_key = "application"
# application_secret = "secret"
# consumer_key = "consumer"
# [route53]
# The empty [route53] section enables AWS credential-chain configuration.Provider API endpoints require HTTPS, bounded host URLs without userinfo or
fragments, and bounded non-empty credentials. Redirects are disabled. An optional
root-level provider_trust_root selects a bounded PEM file for Cloudflare,
Hetzner and OVH HTTPS endpoints; normal public trust remains enabled. This option
does not configure the independent AWS SDK. Route53 uses the SDK's standard
public AWS trust and endpoint configuration; private-CA Route53 endpoints are
unsupported by this option. The configuration itself is capped at 64 KiB.
Multiple provider sections can be configured; the request's provider identity
selects the adapter. Route53 uses the AWS SDK credential chain and region setup.
Run apollo-dnsd /etc/apollo-dnsd/config.toml. Keep credentials in a private config
or the AWS credential source. Safe upgrades stop one owner, preserve state and
socket path, and restart compatible v1 binaries; gateway serving continues.
Run cargo test --locked --workspace --all-targets and
cargo clippy --locked --workspace --all-targets --all-features -- -D warnings.
Adapter tests use local HTTP fixtures. The
certd DNS-01 fixture
exercises ACME/DNS/Unix-socket interoperability and daemon crash recovery using
separately built binaries. The production build imports no sibling source.