Update architecture specs to reflect live deployment findings and fix two bugs
Architecture updates based on gaps discovered during live deployment testing: - ADR-023: HTTP/2 client-facing support via ALPN-based protocol detection. The spec previously said HTTP/2 was out of scope, but the deployment revealed that modern browsers negotiate HTTP/2 via ALPN. The proxy now correctly detects the negotiated ALPN protocol and uses the appropriate HTTP server builder (http2::Builder for h2, auto::Builder for http/1.1). Upstream connections remain HTTP/1.1. Host resolution now falls back to URI host for HTTP/2 :authority pseudo-headers. - ADR-024: ANSI-disabled logging. All tracing-subscriber layers now use with_ansi(false) to prevent ANSI escape codes in log output, which broke fail2ban regex matching in Docker deployments. Also documents the fail2ban regex anchor fix (^RATE_LIMIT → RATE_LIMIT). Bug fixes found by architecture review: - Fix missing ALPN protocols in manual TLS mode. build_manual_server_config and build_multi_domain_server_config did not set alpn_protocols, meaning manual TLS mode could not support HTTP/2. Added h2 and http/1.1 ALPN entries to both functions (acme-tls/1 only in ACME mode). - Fix missing with_ansi(false) in JSON log format. The init_json function with file output did not disable ANSI on stdout or file layers, which would break fail2ban in production JSON logging mode. Other spec updates: - All document statuses updated from draft to reviewed - proxy.md: documented Server header removal, upstream HTTPS client, two-phase timeout enforcement, HTTP/2 host resolution, connect timeout - tls.md: documented ALPN configuration differing by mode (ACME vs manual) - overview.md: added HTTP/2 client-facing support to scope, updated crate deps (hyper-rustls, rustls-native-certs, hyper-util), clarified out-of-scope - config.md: fixed http_port type (u16→u32) to match implementation, added ANSI-disabled note for LoggingConfig - operations.md: documented ANSI-disabled logging, fail2ban regex anchor - open-questions.md: updated OQ-09 resolution (connect timeout fully implemented), OQ-10 (C2 bug is fixed)
This commit is contained in:
75
docs/architecture/decisions/023-http2-client-facing.md
Normal file
75
docs/architecture/decisions/023-http2-client-facing.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# ADR-023: HTTP/2 Client-Facing Support
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
The original architecture spec excluded HTTP/2 proxying from scope, stating "HTTP/2
|
||||
or HTTP/3 proxying (services that need these run their own native Rust servers)."
|
||||
This was interpreted as excluding HTTP/2 entirely — both for client connections
|
||||
and upstream connections.
|
||||
|
||||
During deployment testing, we discovered that modern browsers and HTTP clients
|
||||
negotiate HTTP/2 via ALPN during the TLS handshake. The initial implementation
|
||||
used `hyper_util::server::conn::auto::Builder` which failed to properly detect
|
||||
HTTP/2 over TLS connections because its `ReadVersion` mechanism doesn't work
|
||||
reliably with `tokio-rustls` `TlsStream` wrappers.
|
||||
|
||||
This caused two problems:
|
||||
1. HTTP/2 clients received degraded performance (no multiplexing) or connection
|
||||
failures
|
||||
2. In HTTP/2, the host is conveyed via the `:authority` pseudo-header, which
|
||||
hyper represents as the URI host rather than a `Host` header — causing 400
|
||||
errors for HTTP/2 clients
|
||||
|
||||
## Decision
|
||||
|
||||
The proxy now supports HTTP/2 on the **client-facing** side (between the client
|
||||
and the proxy). This is distinct from HTTP/2 proxying to upstream services,
|
||||
which remains out of scope.
|
||||
|
||||
**Implementation:**
|
||||
|
||||
1. **ALPN-based protocol detection**: After the TLS handshake, the proxy reads
|
||||
the negotiated ALPN protocol from `tls_stream.get_ref().1.alpn_protocol()`.
|
||||
If the ALPN is `h2`, the connection uses
|
||||
`hyper::server::conn::http2::Builder`; otherwise, it uses
|
||||
`hyper_util::server::conn::auto::Builder` with HTTP/1.1 + upgrade support.
|
||||
|
||||
2. **Host header fallback**: The proxy handler now falls back to
|
||||
`req.uri().host()` when the `Host` header is absent. In HTTP/2, the
|
||||
`:authority` pseudo-header is represented as the URI host in hyper, so this
|
||||
correctly handles both HTTP/1.1 (where `Host` is always present) and HTTP/2
|
||||
(where `:authority` maps to URI host).
|
||||
|
||||
3. **ALPN advertisement**: The TLS `ServerConfig` advertises `h2` and
|
||||
`http/1.1` as ALPN protocols, plus `acme-tls/1` for ACME challenges.
|
||||
|
||||
**Upstream connections remain HTTP/1.1.** The proxy communicates with upstream
|
||||
services over HTTP/1.1 (or HTTPS/1.1 when `upstream_scheme = "https"`). HTTP/2
|
||||
to upstreams is out of scope for Phase 1.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Modern browsers and HTTP/2 clients work correctly with the proxy
|
||||
- HTTP/2 multiplexing improves client-facing performance (multiple requests over
|
||||
a single connection)
|
||||
- ALPN-based detection is the standard mechanism for HTTP/2 negotiation over TLS
|
||||
- Host header fallback correctly handles both HTTP/1.1 and HTTP/2
|
||||
|
||||
**Negative:**
|
||||
- Slightly more complex TLS listener code (ALPN protocol detection, dual
|
||||
builder paths)
|
||||
- The distinction between "HTTP/2 to the proxy" and "HTTP/2 to upstream" must
|
||||
be clearly documented to avoid confusion
|
||||
- `ConnectInfoService` is typed to `Request<Incoming>` rather than the generic
|
||||
`Request<B>`, which is a correct but slightly less flexible implementation
|
||||
|
||||
## References
|
||||
|
||||
- [proxy.md](../proxy.md) — request flow and host-based routing
|
||||
- [tls.md](../tls.md) — TLS termination and ALPN configuration
|
||||
- [overview.md](../overview.md) — scope and out-of-scope items
|
||||
53
docs/architecture/decisions/024-ansi-disabled-logging.md
Normal file
53
docs/architecture/decisions/024-ansi-disabled-logging.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# ADR-024: ANSI-Disabled Logging for Container Deployments
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
During deployment, the proxy's log output contained ANSI escape codes (color
|
||||
codes) because `tracing-subscriber`'s default `fmt::layer()` enables ANSI
|
||||
output when connected to a terminal. In a Docker container, `docker logs`
|
||||
captures stdout/stderr, and the log file written to
|
||||
`/var/log/reverse-proxy/access.log` is also a plain text file.
|
||||
|
||||
ANSI escape codes in logs cause two problems:
|
||||
1. **fail2ban regex failure**: The fail2ban filter regex expects plain text with
|
||||
a `RATE_LIMIT` prefix. ANSI codes embedded in the log line before the prefix
|
||||
break pattern matching, causing fail2ban to miss rate limit events entirely.
|
||||
2. **Docker log readability**: `docker logs` output is cluttered with escape
|
||||
sequences when not running in a terminal that supports them.
|
||||
|
||||
## Decision
|
||||
|
||||
All `tracing-subscriber` fmt layers now use `with_ansi(false)`:
|
||||
|
||||
- **File layer**: Always plain text, no ANSI codes
|
||||
- **Stdout layer**: Always plain text, no ANSI codes
|
||||
- **JSON layer**: Always plain text (JSON format doesn't benefit from colors)
|
||||
|
||||
This applies to both text and JSON log formats, in both file and stdout
|
||||
destinations.
|
||||
|
||||
Additionally, the fail2ban regex was corrected: the `^` anchor was removed from
|
||||
the `failregex` pattern because log lines have a timestamp/level prefix before
|
||||
the `RATE_LIMIT` keyword. The corrected pattern matches `RATE_LIMIT` anywhere
|
||||
in the line rather than only at the start.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- fail2ban regex matching works reliably in all environments
|
||||
- Log output is clean and parseable regardless of environment
|
||||
- No behavioral difference between Docker, systemd, and terminal environments
|
||||
|
||||
**Negative:**
|
||||
- Loss of color-coding in terminal output during development (acceptable
|
||||
trade-off for reliability; developers can use `RUST_LOG` filtering instead)
|
||||
|
||||
## References
|
||||
|
||||
- [operations.md](../operations.md) — logging and fail2ban integration
|
||||
- [ADR-007](007-custom-log-format.md) — custom structured log format
|
||||
- [ADR-020](020-container-deployment.md) — container deployment model
|
||||
Reference in New Issue
Block a user