feat: auth, enrollment API, and webapp for agent tunnel#1742
Draft
irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 11 commits intofeat/quic-tunnel-2-routingfrom
Draft
feat: auth, enrollment API, and webapp for agent tunnel#1742irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 11 commits intofeat/quic-tunnel-2-routingfrom
irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 11 commits intofeat/quic-tunnel-2-routingfrom
Conversation
Contributor
Author
Add QUIC-based agent tunnel core infrastructure. Agents in private
networks connect outbound to Gateway via QUIC/mTLS, advertise reachable
subnets and domains, and proxy TCP connections on behalf of Gateway.
Protocol (agent-tunnel-proto crate):
- RouteAdvertise with subnets + domain advertisements
- ConnectMessage/ConnectResponse for session stream setup
- Heartbeat/HeartbeatAck for liveness detection
- Protocol version negotiation (v2)
Gateway (agent_tunnel module):
- QUIC listener with mTLS authentication
- Agent registry with subnet/domain tracking
- Certificate authority for agent enrollment
- Enrollment token store (one-time tokens)
- Bidirectional proxy stream multiplexing
Agent (devolutions-agent):
- QUIC client with auto-reconnect and exponential backoff
- Agent enrollment with config merge (preserves existing settings)
- Domain auto-detection (Windows: USERDNSDOMAIN, Linux: resolv.conf)
- Subnet validation on incoming connections
- Certificate file permissions (0o600 on Unix)
API endpoints:
- POST /jet/agent-tunnel/enroll — agent enrollment
- GET /jet/agent-tunnel/agents — list agents
- GET /jet/agent-tunnel/agents/{id} — get agent
- DELETE /jet/agent-tunnel/agents/{id} — delete agent
- POST /jet/agent-tunnel/agents/resolve-target — routing diagnostics
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ConnectMessage → ConnectRequest (precise naming) - Move encode/decode into ControlStream/SessionStream wrappers (actor-on-object: ctrl.send(&msg) instead of msg.encode(&mut stream)) - ControlStream.into_split() → ControlSendStream + ControlRecvStream (compile-time separation, no phantom halves) - From<(S, R)> for stream wrappers (connection.open_bi().await?.into()) - Rename spawned tasks: run_control_reader, run_session_proxy, run_agent_connection, run_control_loop - Spawned tasks own args and handle errors internally - Collect JoinHandles, abort all on shutdown - Extract helpers to tunnel_helpers.rs - Document backoff strategy with examples Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CaManager::load_or_generate returns Arc<Self> directly - Rename enrollment token consume → redeem - Remove unused resolve-target API endpoint + helpers + tests - Remove routing methods from registry (PR2 scope) - Remove Option from RouteAdvertisementState (empty = no routes) - Target enum for typed IP vs domain parsing - Prefix variables clearly (server_cert_*, ca_*) - Add TODO for traffic audit and Windows DACL - Backoff strategy documented with examples Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review feedback from Benoit and Marc-André: - Rename HTTP mountpoint /jet/agent-tunnel → /jet/tunnel - Replace SkipHostnameVerification with SpkiPinnedVerifier that performs full chain + hostname + SPKI pin validation - Enrollment response now includes server_spki_sha256 for pinning - Agent sends machine hostname; gateway adds it as DNS SAN alongside the UUID SAN (dual names for future direct connectivity) - Agent connects using real gateway hostname instead of dummy value - Move sha2/hex to cross-platform deps, add x509-parser + hostname Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove agent_name from EnrollResponse (agent knows it already) - Agent generates its own UUID and sends it in EnrollRequest - Rename api/agent_enrollment.rs → api/tunnel.rs (match endpoint) - Use backoff crate for reconnect loop (same pattern as subscriber.rs) - ALPN: "devolutions-agent-tunnel" → "gw-agent-tunnel/1" (versioned) - Protocol version: 2 → 1 (previous was experimental, start fresh) - Move session tests to integration test file (public API only) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SanType::Rfc822Name → SanType::URI for urn:uuid: (correct X.509 type) - GeneralName::RFC822Name → GeneralName::URI in extraction - Reject duplicate agent UUID on enrollment (409 Conflict) - tokio::join! instead of select! for session proxy (prevents data loss) - JoinSet instead of Vec<JoinHandle> (prevents unbounded growth) - Timeout (30s) on session handshake recv_request/recv_response - Fix typos: "redeemd" → "redeemed" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move current_time_millis() to agent-tunnel-proto (R1: eliminate duplication) - Delete DomainInfo, use DomainAdvertisement directly in AgentInfo (R2) - Merge enroll_agent/bootstrap_and_persist into single function (I1) - Agent task_handles: Vec<JoinHandle> → JoinSet with reaping (I4) - Same-epoch route refresh: mutate updated_at in place, no clone (I5) - Add #[must_use] on enrollment_store::redeem() (I6) - connect_via_agent: cleaner error extraction with if-let (I3) - Add TODO for active_stream_count tracking (I2) - SECS_PER_DAY constant replaces magic 86400 (P4) - Consistent .context() for ProtoError instead of map_err (P7) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Hoist protocol version validation before match in both gateway and agent control loops (single check, no per-variant boilerplate) - Validate ConnectResponse protocol version in connect_via_agent - ServerCertStatus enum for ensure_server_cert (expiry + hostname SAN) - send.finish() after proxy copy (graceful QUIC EOF) - Fix constant_time_eq doc (inaccurate timing claim) - Extract ALPN to agent_tunnel_proto::ALPN_PROTOCOL constant - Destruct EnrollResponse at parameter level for readability - ValidatedTunnelConf: make wrong state unrepresentable at type level (dto::TunnelConf for JSON, TunnelConf for runtime with non-optional fields) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
76acc25 to
b70e278
Compare
When a connection target matches an agent's advertised subnets or domains, the gateway automatically routes through the QUIC tunnel instead of connecting directly. This enables access to private network resources without VPN or inbound firewall rules. - Add routing pipeline (subnet match → domain suffix → direct) - Integrate tunnel routing into RDP, SSH, VNC, ARD, and KDC proxy paths - Support ServerTransport enum (Tcp/Quic) in rd_clean_path - Add 7 routing unit tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
b70e278 to
80aed20
Compare
Scope token exchange, enrollment string generation, and the Angular webapp for agent management and tunnel-aware session creation. - Enrollment string endpoint (POST /enrollment-string) - Scope token exchange for agent management API - QUIC endpoint override from enrollment string - Agent enrollment UI (Agents page, enrollment form) - Agent selector control in connection forms - Auth interceptor fix (skip requests with existing Authorization) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
03a6427 to
f49cfd5
Compare
f323f30 to
3c49f7f
Compare
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.
Summary
Auth layer, enrollment string generation, and Angular webapp for agent tunnel management (PR 3 of 4, stacked on #1741).
Depends on: #1741 (must merge first)
Changes
Backend:
POST /enrollment-stringendpoint — generates enrollment string with QUIC endpointFrontend:
PR stack
🤖 Generated with Claude Code