Files
reverse-proxy/tasks/tls/manual-tls.md
glm-5.1 309878c561 Decompose architecture into 23 atomic tasks across 7 parallel generations
Task graph covers all Phase 1 concerns: config system, TLS termination,
proxy handler, operations (rate limiting, logging, health check, admin
socket, signals, shutdown, body size limit), deployment artifacts, and
two review checkpoints.

No circular dependencies. Critical path length of 7. Risk distribution:
3 high-risk (ACME, TLS listener setup, startup orchestration), 7 medium,
11 low, 2 trivial.
2026-06-11 11:21:10 +00:00

78 lines
3.2 KiB
Markdown

---
id: tls/manual-tls
name: Implement manual TLS certificate loading and ServerConfig construction
status: pending
depends_on: [setup/project-init]
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement the manual TLS mode where certificates are loaded from PEM files on disk at startup. This covers building a `rustls::ServerConfig` with manually loaded certificate chains and private keys.
### Manual Mode
For each listener in manual mode:
1. Load `cert_path` PEM file using `rustls_pemfile``Vec<CertificateDer>`
2. Load `key_path` PEM file using `rustls_pemfile``PrivateKeyDer`
3. Build `ServerConfig` with `with_no_client_auth()` and the loaded cert/key
4. Configure cipher suites (restricted set per ADR-012)
5. Configure protocol versions (TLS 1.2 and 1.3 only)
### Cipher Suite Configuration
Per ADR-012, restrict to nginx-equivalent cipher suites:
**TLS 1.2 (explicitly selected):**
- `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`
- `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`
- `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`
- `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`
**TLS 1.3 (all default suites):**
- `TLS_AES_128_GCM_SHA256`
- `TLS_AES_256_GCM_SHA384`
- `TLS_CHACHA20_POLY1305_SHA256`
This is configured via a custom `CryptoProvider` with a `cipher_suite` list passed to `ServerConfig::builder_with_provider()`.
### Single-Domain Manual Mode
For a listener with one domain, build a simple `ServerConfig` with the single certificate chain and private key. No SNI resolver needed.
### Multi-Domain Manual Mode (on shared-IP listener)
For a listener with multiple sites on a shared IP, implement a custom `ResolvesServerCert` that maps SNI hostnames to `CertifiedKey` entries loaded from disk. If no certificate matches the SNI hostname, the handshake fails — we don't serve a default certificate for unknown domains.
Note: multi-domain manual mode with different certs per domain is a rare edge case. The initial implementation should handle the common case (single cert per manual listener). The SNI resolver can be a follow-up if needed.
## Acceptance Criteria
- [ ] `rustls::ServerConfig` construction for manual TLS mode
- [ ] PEM file loading via `rustls_pemfile` for certificates and private keys
- [ ] Cipher suite restriction per ADR-012 (4 TLS 1.2 suites + all TLS 1.3)
- [ ] Protocol version restriction to TLS 1.2 and 1.3
- [ `aws_lc_rs` crypto provider used
- [ ] `with_no_client_auth()` for no client certificate requirement
- [ ] Custom `ResolvesServerCert` for SNI-based cert selection in multi-domain manual mode
- [ ] Unknown SNI hostname → handshake fails (no default cert)
- [ ] Unit tests for ServerConfig construction with test certs (using `rcgen`)
- [ ] Unit tests for cipher suite and protocol version configuration
## References
- docs/architecture/tls.md — manual mode, cipher suites, SNI
- docs/architecture/decisions/004-rustls-acme.md — manual mode is fallback
- docs/architecture/decisions/005-tokio-rustls-direct.md — direct tokio-rustls usage
- docs/architecture/decisions/012-cipher-suite-restriction.md — cipher suite selection
## Notes
> This task focuses on ServerConfig construction. The actual TCP listener + TLS acceptor wiring is in tls/tls-listener-setup.
## Summary
> To be filled on completion