6.5 KiB
RustProxy 0.22.3 specification audit
Scope
This audit covers the 0.22.3 delta over 0.22.2: downstream HTTP/1.1 Upgrade: h2c, dedicated certificate hot reload, and deployment of certificate/key pairs owned by an external local issuer such as system Certbot. Upstream mTLS/client-certificate support is intentionally deferred.
Requirement matrix
| Requirement | Source implementation | Regression / verification |
|---|---|---|
| Preserve h2c prior knowledge | cleartext listeners still sniff the HTTP/2 connection preface before entering HTTP/1 | existing prior-knowledge regression plus source/release checks |
Accept HTTP/1.1 Upgrade: h2c only when enabled |
H1 service checks the listener h2c capability before returning 101 |
malformed_h2c_upgrade_is_rejected_before_switching_protocols and source checks |
| Validate the Upgrade request | requires HTTP/1.1, Connection: Upgrade, HTTP2-Settings, exactly one Upgrade: h2c, one bounded base64url settings payload, Host/authority, no request body |
h2c_upgrade_validation_rejects_missing_settings_and_bodies |
| Reconstruct H1 request as H2 stream 1 | bounded HPACK bootstrap encodes pseudo/header fields and injects a stream-1 HEADERS frame after client preface + initial SETTINGS | http1_h2c_upgrade_bootstraps_request_as_http2_stream_one source regression |
| Keep later streams on normal H2 engine | upgraded I/O is handed to the same Hyper HTTP/2 server builder used by native H2 | source contract review; runtime gate open here |
| Bound malformed bootstrap traffic | initial preface/SETTINGS/header block have explicit size/shape checks; invalid input closes/rejects rather than entering routing | source checks + malformed request test source |
| Dedicated certificate reload API | authenticated POST /api/certificates/reload invokes App::reload_certificates |
admin regression + OpenAPI/source checks |
| Reload does not mutate desired config/revision | recompile uses the existing revision and only swaps the immutable effective snapshot | certificate_reload_keeps_configuration_revision_stable |
| Failed reload preserves active snapshot | compilation completes before ArcSwap publication; an error returns without store |
source review; release-host negative runtime acceptance required |
| Public H1/H2/H3 new handshakes use refreshed cert | public TCP TLS reads the current snapshot per ClientHello; QUIC config is rebuilt in the published snapshot | source review; live release-host acceptance required |
| Existing sessions are not interrupted | reload swaps configuration state only; it does not cancel public connection/tunnel tokens | source review |
| External Certbot/manual pair can be deployed | tls deploy --id ... --cert ... --key ... reads local PEMs and calls authenticated POST /api/certificates/deploy; the service stages a generation and publishes the existing manual certificate id |
CLI/API/source checks + external pair staging regression |
Certbot live/*.pem symlinks work |
local CLI canonicalizes the supplied path once, then opens the resolved regular file with bounded reads | source checks; live Certbot-path acceptance recommended |
| Private key safety | external key is size-bounded, must be a regular file and on Unix may not grant group/world permissions; staged files use private creation semantics | source review + existing TLS file safety contracts |
| Pair/SAN validation before publication | stage_external_pair calls the existing certificate/key parser and extracts DNS SANs |
external_pair_stages_generation_and_rolls_back_cleanly |
| Atomic multi-vhost switch | existing certificate id keeps all vhost assignments; only its generation filenames/coverage change in one revision-CAS config update | source/API review |
| No dual owner with built-in ACME | tls deploy rejects entries carrying RustProxy issuer metadata |
source contract checks |
| Roll back staged files if publication fails | failed config/API publication invokes remove_external_pair for the staged generation |
source review + helper regression |
| Administration TLS caveat is explicit | reload/deploy report that startup-fixed administration TLS needs restart when it selects the affected inventory certificate | API/CLI/UI/OpenAPI checks |
| Upstream mTLS | intentionally not part of 0.22.3 | deferred by product decision |
h2c bootstrap semantics
The Upgrade path is enabled only on a cleartext listener that already opts into h2c. It does not make ordinary HTTP listeners opportunistically negotiate HTTP/2. A valid H1 request receives 101 Switching Protocols; the client then sends the normal HTTP/2 connection preface and its initial non-ACK SETTINGS frame. RustProxy inserts a synthetic body-free HEADERS frame for stream 1 and lets Hyper own subsequent H2 framing, flow control, stream lifecycle and graceful shutdown.
The bootstrap deliberately rejects H1 request bodies. This avoids replaying or buffering a body across the protocol switch and keeps the transition bounded. Prior-knowledge h2c remains unchanged.
External certificate ownership model
There are two supported external workflows:
- Generation deploy (recommended for Certbot): a local deploy hook calls
tls deploy. The CLI reads Certbot's currentlive/targets and sends the bounded PEM pair to authenticated loopbackPOST /api/certificates/deploy; the running service validates and creates a new immutable pair in its private certificate directory, then switches an existing manual certificate id through the normal revisioned configuration API. Every vhost already assigned to that id moves together. - In-place file replacement: an operator replaces files already referenced by the desired configuration through another safe local mechanism, then calls
POST /api/certificates/reloadortls reload. The desired configuration and revision do not change.
RustProxy-managed ACME ids are excluded from external deploy so the same certificate lineage cannot have two renewal owners. The API does not accept arbitrary host filesystem paths from a remote request.
Release gate
This packaging workspace has no cargo, rustc or rustfmt. Rust parsing/type checking, compilation, linking and Rust test execution cannot be claimed here. Rust-independent JavaScript/JSON/language/source/package/Go gates are run locally. A Rust-enabled release host must still run bash dev.sh doctor, bash dev.sh check, bash dev.sh test, a release build, the raw H1 -> h2c Upgrade -> H2 stream-1 test, native h2c prior-knowledge, TLS/H2/H3 certificate hot reload, invalid-certificate rollback and normal H1/H2/H3 acceptance before deployment.