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.
76 lines
2.8 KiB
Markdown
76 lines
2.8 KiB
Markdown
# ADR-008: Static/Dynamic Configuration Split with ArcSwap
|
|
|
|
## Status
|
|
|
|
Accepted
|
|
|
|
## Context
|
|
|
|
The proxy needs configuration that can be partially reloaded at runtime (site
|
|
definitions, rate limits) without restarting the process and dropping active
|
|
connections. However, some configuration (bind addresses, TLS mode) fundamentally
|
|
requires creating new listeners and cannot be changed at runtime.
|
|
|
|
Two approaches:
|
|
- **Full restart for all config changes**: Simple, but requires dropping
|
|
active connections for every change, including trivial rate limit adjustments.
|
|
- **Static/dynamic split**: Immutable parameters (bind address, TLS mode) in a
|
|
`StaticConfig` that requires restart; runtime-adjustable parameters (sites,
|
|
rate limits) in a `DynamicConfig` that can be atomically swapped via
|
|
`Arc<ArcSwap<DynamicConfig>>` without dropping connections.
|
|
|
|
This pattern is proven in the alknet project, which uses the same
|
|
`ArcSwap<DynamicConfig>` approach for auth policy, forwarding rules, and rate
|
|
limits.
|
|
|
|
## Decision
|
|
|
|
Split configuration into `StaticConfig` (immutable after startup) and
|
|
`DynamicConfig` (hot-reloadable via `ArcSwap`). The split is:
|
|
|
|
**StaticConfig** (restart required):
|
|
- Bind address, HTTP port, HTTPS port
|
|
- TLS mode (ACME vs. manual), cert paths, ACME settings
|
|
- Log level and format
|
|
|
|
**DynamicConfig** (hot-reloadable via SIGHUP):
|
|
- Site definitions (hostname → upstream mappings)
|
|
- Rate limits (requests per second, burst)
|
|
- Body size limits
|
|
|
|
`ConfigReloadHandle` provides a `reload(DynamicConfig)` method that atomically
|
|
swaps the entire config. All request handlers read `DynamicConfig` via
|
|
`ArcSwap::load()` — a lock-free operation.
|
|
|
|
## Rationale
|
|
|
|
- Rate limits and site definitions change more frequently than bind addresses
|
|
and TLS settings. Hot-reload avoids unnecessary downtime.
|
|
- `ArcSwap` provides lock-free reads and atomic writes — no partial updates,
|
|
no lock contention on the hot path.
|
|
- Proven pattern from alknet, where it's used for auth policy, forwarding
|
|
rules, and rate limits.
|
|
- SIGHUP trigger is simple, well-understood, and compatible with systemd and
|
|
process supervisors.
|
|
- The entire config is swapped at once, preventing inconsistent states where
|
|
some sites use the old config and others use the new one.
|
|
|
|
## Consequences
|
|
|
|
**Positive:**
|
|
- Zero-downtime config reload for sites and rate limits
|
|
- Lock-free reads on the request hot path
|
|
- Atomic config updates — no partial states
|
|
- Proven pattern from alknet
|
|
|
|
**Negative:**
|
|
- Two config types add conceptual complexity
|
|
- SIGHUP reload requires reading the config file from disk (need to handle
|
|
file read errors gracefully)
|
|
- Must validate DynamicConfig before swapping (invalid config must not replace
|
|
valid config)
|
|
|
|
## References
|
|
|
|
- [config.md](../config.md)
|
|
- alknet ADR-030 (static/dynamic config split) |