Phase 1 architecture docs covering proxy handler, TLS termination (ACME + manual), TOML config with static/dynamic split (ArcSwap), and operations (rate limiting, logging, health check, systemd, graceful shutdown). Nine ADRs documenting key decisions: Rust/axum, custom proxy handler, TOML config, rustls-acme for cert management, tokio-rustls direct, token bucket rate limiting, custom log format for fail2ban, static/dynamic config split, and signal handling strategy. Includes threat landscape research documenting the nginx CVEs motivating this project.
2.5 KiB
ADR-005: tokio-rustls Directly, Not axum-server
Status
Accepted
Context
We need to serve HTTPS (TLS) traffic through axum. Two approaches exist for integrating TLS with axum:
-
axum-server: A wrapper that provides TLS support for axum viatls_rustlsfeature. Handles TCP binding, TLS accept, and passing TLS streams to axum. Simple API but limited control over the TLS configuration. -
tokio-rustlsdirectly: Bind TCP manually, perform TLS handshake withTlsAcceptor, then serve the TLS stream to axum/hyper. More code but full control overServerConfig, cipher suites, ALPN protocols, and cert resolvers.
The alknet project uses tokio-rustls directly and has proven this pattern for both manual and ACME certificate management.
Decision
Use tokio-rustls directly for TLS termination, with hyper serving the
resulting TLS streams to axum. Do not use axum-server.
Rationale
- ACME integration: The
rustls-acmeResolvesServerCertAcmeresolver needs to be set as the certificate resolver onServerConfigviawith_cert_resolver().axum-serverdoes not expose this level of control over theServerConfig. - Cipher suite control: We may need to configure cipher suites beyond the
defaults (see OQ-01).
axum-serverwraps theServerConfigconstruction and may not exposeCryptoProviderconfiguration. Directtokio-rustlsusage gives us full control. - ALPN configuration: ACME TLS-ALPN-01 challenge requires adding
acme-tls/1to the ALPN protocol list. This is only possible with directServerConfigaccess. - Proven pattern: alknet uses exactly this approach (
TlsAcceptorwrappingtokio-rustls, with manual or ACMEServerConfigconstruction). - No abstraction cost: The code to bind TCP, accept TLS, and serve to
axum/hyper is ~50 lines.
axum-serversaves little for our simple case.
Consequences
Positive:
- Full control over TLS configuration
- Direct
rustls-acmeintegration - Ability to add ALPN protocols for ACME challenges
- Proven pattern from alknet
Negative:
- Slightly more code than
axum-server(~50 lines for the TLS acceptor loop) - Need to manage the TCP listener and TLS accept explicitly
- Must handle the
TlsStream<TcpStream>→hyper::service_fn→ axum integration manually (well-documented pattern from Felix Knorr's blog and alknet)
References
- tls.md
- alknet transport layer (
alknet-core/src/transport/tls.rs,alknet-core/src/transport/acme.rs)