Skip to content

About

Provider-neutral Rust DNS service with durable operations and modular provider adapters

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

apollo-dnsd

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.

Protocol v1

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.

Durability and ownership

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.

Configuration and operation

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.

About

Provider-neutral Rust DNS service with durable operations and modular provider adapters

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages