Repository navigation
Conversation
The QUIC transport pins a self-signed certificate per transport, and the listener does not authenticate the dialer. This change adds a mode in which both peers present certificates from a private CA (ACM Private CA, SPIRE, cert-manager) and verify each other in the TLS handshake. The listener requires a client certificate. An application authorizer gets the verified peer identity (the leaf, the chain, and its URI SANs, such as spiffe://deployment/class/cell) and the connection origin. It decides on every connection, on every lane, in both directions, after the handshake and before the transport reads or writes a frame. A refused connection is closed with the code REFUSED and dispatches nothing. The messenger's _hello cannot do this check: system handlers skip it, and a connection that a peer dials back can begin with a response. QuicTls::rotate and QuicTls::set_roots change the certificate and the roots without a restart. New handshakes use them, also to peers that were registered before. Session resumption and 0-RTT are off in this mode: a resumed session skips certificate verification and keeps the first identity, which a test caught. The pinned self-signed certificate stays the default. Tests: a dialer without a certificate, with a certificate from another CA, with a chain but not its key, and a default transport are refused before any frame; a server chain without its key is refused by the dialer; each authorizer refuses a valid certificate; a connection that begins with a response and one that begins with mux traffic on lane 3 are authorized and delivered; rotation of the certificate and the roots; a Velo handler counts no calls from a refused peer. Signed-off-by: Ryan Olson <rolson@nvidia.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
set_roots wrote the roots (dialer) and the client verifier (listener) under two locks. Two calls at once could interleave so that, after both returned, the dialer trusted one set of roots and the listener the other. Withdrawing a CA could then stay without effect in one direction. Both now live in one snapshot behind one lock. Also require rustls-webpki 0.103.8, the first release with valid_uri_names. 0.103.7 met the old requirement and does not build. Signed-off-by: Ryan Olson <rolson@nvidia.com>
…y::from_der A consumer that kept rustls 0.23.22 met the old requirement and failed to build velo with the quic feature. Signed-off-by: Ryan Olson <rolson@nvidia.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The QUIC transport can authenticate both peers with certificates from a private CA (ACM Private CA, SPIRE, cert-manager), and the application decides on each connection before any frame on it is dispatched. The pinned self-signed certificate stays the default. Nothing changes for a caller that does not call
QuicTransportBuilder::tls.QuicTls::new(roots, chain, key)andQuicTransportBuilder::tls. Both ends present a chain and verify the other against the roots in the handshake. The listener requires a client certificate. The dialer verifies the server chain and theserverAuthusage, and not a name, because every dialer presents the namevelo.QuicTls::authorizertakes aQuicAuthorizeror a closureFn(&PeerIdentity, &ConnectionOrigin) -> bool.PeerIdentitygives the leaf, the chain, and the URI SANs (spiffe://<deployment>/<class>/<cell>).ConnectionOriginisAccepted { remote }orDialed { peer, lane, remote }.serve_connection, after the handshake and before the firstaccept_bi. Streams that the peer opened wait unaccepted in quinn, so nothing reachesroute_frame.open_bi, the dialed reader, and the first write. Queued frames fail through their error handlers.ConnectionFailed.REFUSED. A panic in the authorizer is a refusal.QuicTls::rotate(chain, key)andQuicTls::set_roots(roots). The certificate resolver and both verifiers read the current values on each handshake, so new connections use them, also to peers that were registered before.QuicTransport::tlsreturns the handle.set_roots.rustls-webpki>= 0.103.8 (already in the tree through rustls), to read URI SANs.rustlsnow needs >= 0.23.23.velo::transports::quicre-exportsCertificateDerandPrivateKeyDerfromrustls-pki-types.Limits
REFUSED. A refusal by the dialer, and a server chain that the dialer does not trust, fail the queued frames through their error handlers.Tests
lib/velo/src/transports/quic/auth_tests.rs. Each refusal test has a positive control with the same client. "Nothing dispatched" is read off the listener's message, response, event and shutdown streams, which every routed frame reaches.streaming/messenger_mux/lane_tests.rs). The lane 3 connections begin with mux traffic, and both nodes authorized their lane 3 dial.rotate, a new lane presents the new certificate. Afterset_rootsdrops the old CA, a new lane with an old-CA certificate is refused while the open lane keeps working.Velolevel: B accepts A and refuses C, both from B's CA. A's unary call gets its answer, C's times out, and B's handler counted one call.Conflicts
git merge-treeagainst #153 (advertised addresses) shows conflicts inREADME.md(both add a paragraph above the lanes paragraph) and inlib/velo/src/transports/quic/transport.rs(registerand the struct fields).builder.rs,Cargo.tomland the book merge cleanly. Whichever PR merges second needs a small rebase. The hub TLS PR (#155) merges cleanly with both.Review ledger
An independent reviewer did three rounds (
thermo-nuclear-code-quality-reviewandrust-code-review). Each valid finding got a failing test or reproduction first.set_rootscalls at once can leave the dialer trusting one CA and the listener another, because the roots and the client verifier sat behind separate locks.concurrent_root_updates_leave_both_directions_agreeingfailed at round 1378 of 2000.Trustsnapshot behind one lock.rustls-webpki = "0.103"admits 0.103.7, which has novalid_uri_names.0.103.8, and the resolver refuses 0.103.7. No minimal-versions CI job: the repo has none, so that is a separate decision.rustls = "0.23"admits 0.23.22, which has noCertifiedKey::from_der.quicand rustls 0.23.22 failed with E0599.0.23.23. The fixture refuses 0.23.22 and builds on 0.23.23.During development, a test found that TLS 1.3 session resumption presented the identity of the first session after
rotate. Resumption is off in CA mode.