Promote multi-site support from Phase 2 to Phase 1 (ADR-010): the proxy must support git.alk.dev and alk.dev from initial release. Add multi-domain TLS configuration (ADR-011): acme_domains array replaces acme_domain string, single SAN certificate via rustls-acme. Key changes: - ADR-010: Multi-site in Phase 1 — avoids config format migration later - ADR-011: Multi-domain TLS — single SAN cert, acme_domains Vec<String> - ADR-002: Updated rationale for multi-site (one upstream per domain) - overview.md: Phase 1 now includes multi-site, alk.dev pass-through, dual licensing (MIT OR Apache-2.0), real IP removed - config.md: acme_domain → acme_domains, TOML example shows both sites, validation adds unique host check, real IP replaced with 203.0.113.10 - tls.md: Multi-domain SNI section moved from Future to current, manual mode uses ResolvesServerCert for SNI mapping, TOML header fixed - proxy.md: Updated for multi-site, removed single-domain language - operations.md: RFC 5737 documentation IPs, clarified rate limit eviction semantics (distinct scan interval vs eviction age) - open-questions.md: OQ-05 resolved (single bind_addr sufficient), new OQ-07 (per-site TLS overrides) Review fixes: - acme_domains (plural) consistently used across all docs and diagram - ADR-011 clearly scopes acme_domain as previous design - Inline decision rationale extracted: tls.md hot-reload → ADR-004 ref, config.md static/dynamic → ADR-008 ref - TOML section headers consistent (server.tls)
3.2 KiB
ADR-011: Multi-Domain TLS Configuration
Status
Accepted
Context
With multi-site support in Phase 1 (ADR-010), the TLS configuration must
support multiple domains. The previous design used a single tls.acme_domain
string field, which only works for one domain.
There are several approaches to multi-domain TLS:
- Single ACME config with domain list:
acme_domains = ["git.alk.dev", "alk.dev"]— one certificate covering all domains (SAN certificate) - Per-site TLS configuration: Each site entry specifies its own TLS mode (ACME or manual) and domain — more flexible but complex
- Hybrid: A global TLS section with ACME domains, plus per-site overrides for manual certificates
For our use case, all proxied domains use the same ACME certificate authority (Let's Encrypt) and the same challenge type (TLS-ALPN-01). There's no need for per-site TLS configuration in Phase 1.
Decision
Use a single ACME configuration with a list of domains, producing one SAN certificate covering all proxied domains. Manual mode uses certificate file paths (single cert file with all domains, or one cert per domain resolved via SNI).
The config format changes from the previous single-domain format:
# Previous (single-domain) format — no longer used
[tls]
mode = "acme"
acme_domain = "git.alk.dev" # single string
To the current multi-domain format:
[tls]
mode = "acme"
acme_domains = ["git.alk.dev", "alk.dev"] # array of strings
In ACME mode, rustls-acme provisions a single certificate covering all
listed domains via Subject Alternative Names (SAN). This is the standard
Let's Encrypt approach for multi-domain certificates.
In manual mode, the cert and key files must cover all domains (either a SAN certificate or separate certificates resolved via SNI).
Rationale
- A single SAN certificate is simpler to manage (one renewal, one cert)
- Let's Encrypt supports SAN certificates with up to 100 domains
rustls-acmeacceptsVec<String>for domain lists — this is its natural API- All our domains use the same ACME configuration (Let's Encrypt production, TLS-ALPN-01 challenge)
- Per-site TLS overrides add complexity with no current benefit
- If per-site TLS configuration is needed later (e.g., a site with a manual cert), it can be added as an optional override without changing the global config structure
Consequences
Positive:
- Single certificate for all domains — simpler renewal, simpler cert management
- Matches
rustls-acme's natural API (AcmeConfig::new(domains: Vec<String>)) - All domains in one cert means SNI resolution is handled by ACME automatically
- Config format is a minimal change from single-domain
Negative:
- Adding or removing a domain requires re-provisioning the certificate (ACME handles this automatically, but it means cert changes affect all domains)
- If one domain fails ACME validation, the entire cert renewal fails (all domains must be validated) — mitigated by Let's Encrypt's domain-level validation
- Per-site TLS configuration (e.g., a domain with a manual cert) requires a future config extension (OQ-07)