# 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: 1. **Generation deploy (recommended for Certbot):** a local deploy hook calls `tls deploy`. The CLI reads Certbot's current `live/` targets and sends the bounded PEM pair to authenticated loopback `POST /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. 2. **In-place file replacement:** an operator replaces files already referenced by the desired configuration through another safe local mechanism, then calls `POST /api/certificates/reload` or `tls 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.