Skip to content

feat(ingress): opt-in DNS-01 ACME with pluggable providers (Cloudflare + exec) - #25

Open
zainokta wants to merge 1 commit into
masterfrom
feat/dns01-acme-cloudflare
Open

feat(ingress): opt-in DNS-01 ACME with pluggable providers (Cloudflare + exec)#25
zainokta wants to merge 1 commit into
masterfrom
feat/dns01-acme-cloudflare

Conversation

@zainokta

@zainokta zainokta commented Jun 5, 2026

Copy link
Copy Markdown
Owner

What

Adds an opt-in DNS-01 ACME challenge so Denia can issue TLS certs for hostnames fronted by a proxy/WAF (Cloudflare orange-cloud, SafeLine, …) where the in-process HTTP-01 challenge can't reach :80. Modeled on Traefik/lego: a provider-agnostic core + pluggable providers. HTTP-01 stays the default and behavior-identical.

Why

denia auth against a Cloudflare-proxied console returned 403 and TLS couldn't be issued: HTTP-01 needs Let's Encrypt to reach the origin directly, which an orange-cloud proxy blocks. DNS-01 proves ownership via a _acme-challenge TXT record — no inbound reachability needed.

How

  • Dns01Provider trait (present/cleanup/timeout) + build_provider factory selected by DENIA_ACME_DNS_PROVIDER.
  • cloudflare provider — API token (Zone:Read + DNS:Edit), zone resolved most-specific-first via GET /zones?name= (no public-suffix-list dep), retryable vs permanent error classification, token in a redacted Secret.
  • exec provider — runs <script> present|cleanup <fqdn> <value> for any DNS host (Akamai, Route53, PowerDNS, BIND…); Denia holds no credential. Hook stderr redacted by default.
  • Shared propagation — confirms the TXT over DoH (default Cloudflare+Google, per-request timeout) before set_ready; rejects SERVFAIL/REFUSED/5xx, treats NXDOMAIN as no-CNAME. CNAME delegation of _acme-challenge resolved to an effective FQDN (delegate into a zone you control); loops / hop-limit hard-error.
  • TXT cleanup on every issue exit path. require_dns01_provider fails fast at boot.

Config

acme_challenge, acme_dns_provider, cf_dns_api_token(_file), acme_dns_exec, acme_dns_propagation_secs, acme_dns_resolvers. See ADR-038 and config.toml.in.

Verification

  • cargo build — clean
  • cargo test --lib543 passed, 2 ignored (network-gated DNS-01/HTTP-01 net tests under DENIA_RUN_ACME_NET_TESTS=1)
  • cargo fmt --all, cargo clippy --all-targets --all-features — clean for new code
  • Reviewed across 4 Codex passes (all findings resolved: exec stderr redaction, CNAME loop/hop hard-error, DoH per-request timeout + RCODE handling, TXT type filtering, exec-path fail-fast).

Note: scope is single-host certs; wildcard issuance is a documented follow-up.

…e + exec)

Denia issued TLS certs only via HTTP-01, which fails for hostnames fronted by a
proxy (Cloudflare orange-cloud, a WAF) because the :80 challenge never reaches
the origin. Add an opt-in DNS-01 challenge, modeled on Traefik/lego: a
provider-agnostic core + pluggable providers.

- `Dns01Provider` trait (present/cleanup/timeout) + `build_provider` factory
  selected by `DENIA_ACME_DNS_PROVIDER`.
- `cloudflare` provider: API token (Zone:Read + DNS:Edit), zone resolved by
  most-specific `GET /zones?name=` (no PSL dep), retryable/permanent error
  classification, token held in a redacted `Secret`.
- `exec` provider: runs `<script> present|cleanup <fqdn> <value>` for any DNS
  host; Denia holds no credential. Hook stderr redacted by default
  (`DENIA_ACME_DNS_EXEC_DEBUG=1` to include).
- Shared propagation: confirms the TXT over DoH (default Cloudflare+Google,
  per-request timeout) before `set_ready`; rejects SERVFAIL/REFUSED/5xx, treats
  NXDOMAIN as no-CNAME. CNAME delegation of `_acme-challenge` resolved to an
  effective FQDN; loops / hop-limit hard-error.
- HTTP-01 remains the default and behavior-identical. `AcmeDriver::issue` keeps
  order setup + finalize identical; only per-authorization answering branches;
  TXT cleanup on every exit path.
- Config: `acme_challenge`, `acme_dns_provider`, `cf_dns_api_token(_file)`,
  `acme_dns_exec`, `acme_dns_propagation_secs`, `acme_dns_resolvers`.
  `require_dns01_provider` fails fast at boot (cloudflare->token,
  exec->executable script path).
- ADR-038, config template, and project guidance updated.
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