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)
92 lines
3.2 KiB
Markdown
92 lines
3.2 KiB
Markdown
# 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:
|
|
|
|
1. **Single ACME config with domain list**: `acme_domains = ["git.alk.dev",
|
|
"alk.dev"]` — one certificate covering all domains (SAN certificate)
|
|
2. **Per-site TLS configuration**: Each site entry specifies its own TLS
|
|
mode (ACME or manual) and domain — more flexible but complex
|
|
3. **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:
|
|
|
|
```toml
|
|
# Previous (single-domain) format — no longer used
|
|
[tls]
|
|
mode = "acme"
|
|
acme_domain = "git.alk.dev" # single string
|
|
```
|
|
|
|
To the current multi-domain format:
|
|
|
|
```toml
|
|
[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-acme` accepts `Vec<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)
|
|
|
|
## References
|
|
|
|
- [tls.md](../tls.md)
|
|
- [config.md](../config.md)
|
|
- ADR-010 (multi-site in Phase 1)
|
|
- ADR-004 (ACME-primary certificate management) |