moq-relay: scope mTLS grants to the connection URL path#1535
Conversation
mTLS-authenticated peers were granted AuthToken::unrestricted() with an empty root, so a publisher dialing /demo announced at the cluster root (tos.hang) instead of demo/tos.hang. Path-scoped subscribers (demo/*) never saw it. Make unrestricted() take a root derived from the URL path, the same scoping a JWT's root claim gets. Cluster peers dial "/", which normalizes to an empty root, so they keep unscoped cluster-wide access. All four mTLS call sites (QUIC, WebSocket, serve_announced, serve_fetch) now pass the request path. Also drop the stale doc/comment claims that node identity comes from the cert's DNS SAN and that ?register= is consulted: the mTLS path validates only the cert chain (PeerIdentity is empty) and node identity comes from --cluster-mesh. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… bool PeerIdentity was a data-less struct used only as a presence flag, and the Option<PeerIdentity> return existed solely to guard a downcast that can't fail with rustls. Since the only signal we need is "did the peer present a CA-validated client cert", collapse it to a plain bool and drop the struct, the downcast, and the now-unused Result plumbing at the call site. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
WalkthroughThis PR refactors mTLS authentication to scope access by URL path instead of granting unrestricted cluster access. The moq-native request interface is simplified from extracting a 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@doc/bin/relay/auth.md`:
- Around line 187-191: The phrase "cluster privileges" is ambiguous—edit the
paragraph around the URL-root explanation so it clearly states that the
CA-signed certificate grants scoped publish/subscribe access based on the
URL-derived root and sets an internal token marker, not a separate permission
model; update wording that references "cluster privileges" to explain it's an
unscoped (empty root) internal marker for cluster nodes (examples: peer dialing
"/demo" → access under "demo/"; peer dialing "/" → empty root/unscoped but still
marked internal), and remove any implication of an additional "cluster
privilege" permission layer.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 01ad818f-45f9-4ba4-882c-3816699fb941
📒 Files selected for processing (7)
doc/bin/relay/auth.mdrs/moq-native/src/quinn.rsrs/moq-native/src/server.rsrs/moq-relay/src/auth.rsrs/moq-relay/src/connection.rsrs/moq-relay/src/web.rsrs/moq-relay/src/websocket.rs
"plus cluster privileges" implied a separate permission model. The token just gets full pub/sub within the URL-derived root and an internal flag that only selects the billing stats tier. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Summary
AuthToken::unrestricted()with an empty root, so a publisher dialing/demoannounced at the cluster root (e.g.tos.hang) instead ofdemo/tos.hang. Path-scoped subscribers (demo/*) never saw the broadcast.AuthToken::unrestricted(root)now takes a root derived from the connection URL path, the same scoping a JWT'srootclaim gets. All four mTLS call sites pass the request path: QUIC (connection.rs), WebSocket (websocket.rs), and the HTTPserve_announced/serve_fetchhandlers (web.rs).?register=is consulted. The mTLS path validates only the cert chain; node identity comes from--cluster-mesh.moq-native: the emptyPeerIdentitystruct was a data-less presence flag andpeer_identity() -> Result<Option<PeerIdentity>>only existed to guard a downcast that can't fail with rustls. Replaced it withRequest::has_peer_certificate() -> bool, removing the struct, the downcast, and theResultplumbing at the call site.Why it's safe for the cluster mesh
Cluster peers dial
https://{remote}/, and/normalizes to an empty root, so relay-to-relay keeps unscoped, cluster-wide access exactly as before. Only path-bearing dials get scoped, which is the fix.Branch targeting
Targeting
main: this is a bug fix that preserves the wire protocol and cluster behavior. The publicAuthToken::unrestricted()andRequest::peer_identity()signatures change, but both are relay/native-internal with no external consumers. Happy to retargetdevif a reviewer prefers.Test plan
cargo test -p moq-relay --lib(93 passed; addedunrestricted_scopes_to_rootandunrestricted_empty_root_is_unscoped)cargo clippy -p moq-native -p moq-relay --all-targetscleancargo build -p moq-native -p moq-relayclean/demoannounces underdemo/and ademo/*subscriber sees it; confirm cluster mesh (dialing/) still gets cluster-wide access🤖 Generated with Claude Code
(Written by Claude)