# GuruRMM Server - Agent Tunnel Protocol Update ## Summary Updated the server's WebSocket protocol to handle tunnel messages FROM the agent, completing the bidirectional tunnel communication. **Status:** ✅ Complete - Code compiles successfully with no errors. --- ## Problem Statement The agent was sending `TunnelReady`, `TunnelData`, and `TunnelError` messages, but the server's `AgentMessage` enum didn't have these variants. This would cause deserialization failures when agents attempted to send tunnel messages. **Error that would occur:** ``` Error: Failed to deserialize agent message: unknown variant `tunnel_ready` ``` --- ## Changes Made ### 1. Updated AgentMessage Enum **File:** `server/src/ws/mod.rs` (lines 80-91) **Added three new variants:** ```rust pub enum AgentMessage { Auth(AuthPayload), Metrics(MetricsPayload), NetworkState(NetworkStatePayload), CommandResult(CommandResultPayload), WatchdogEvent(WatchdogEventPayload), UpdateResult(UpdateResultPayload), Heartbeat, // NEW: Tunnel messages from agent TunnelReady { session_id: String }, TunnelData { channel_id: String, data: TunnelDataPayload }, TunnelError { channel_id: String, error: String }, } ``` **Serialization format:** - Uses `#[serde(tag = "type", content = "payload")]` for tagged enum - Uses `#[serde(rename_all = "snake_case")]` for JSON field names - Matches agent's message format exactly --- ### 2. Added Message Handlers **File:** `server/src/ws/mod.rs` (in `handle_agent_message` function, after UpdateResult handler) #### TunnelReady Handler ```rust AgentMessage::TunnelReady { session_id } => { info!( "Agent {} tunnel ready: session_id={}", agent_id, session_id ); // Update session activity timestamp if let Err(e) = db::update_session_activity(&state.db, &session_id).await { error!( "Failed to update session activity for {}: {}", session_id, e ); } } ``` **Purpose:** - Confirms agent received `TunnelOpen` and is ready - Updates `last_activity` timestamp in database - Logs successful tunnel establishment **Future Enhancement (Phase 2):** - Could mark session status as "ready" (vs "active" but not ready) - Could notify waiting clients that tunnel is available --- #### TunnelData Handler ```rust AgentMessage::TunnelData { channel_id, data } => { debug!( "Received tunnel data from agent {}: channel_id={}, type={:?}", agent_id, channel_id, data ); // Phase 2: Forward data to connected clients via WebSocket or REST API // For now, just log the data match data { TunnelDataPayload::TerminalOutput { stdout, stderr, exit_code } => { if !stdout.is_empty() { debug!("Terminal stdout: {}", stdout.trim()); } if !stderr.is_empty() { debug!("Terminal stderr: {}", stderr.trim()); } if let Some(code) = exit_code { debug!("Terminal exit code: {}", code); } } TunnelDataPayload::Terminal { command } => { debug!("Terminal command echo: {}", command); } } } ``` **Purpose:** - Receives terminal output from agent - Logs output for debugging - **Placeholder for Phase 2:** Will forward to connected clients **Phase 2 Implementation:** - Store output in database or in-memory buffer - Forward to WebSocket clients listening on this channel - Or provide REST endpoint to poll for output --- #### TunnelError Handler ```rust AgentMessage::TunnelError { channel_id, error } => { error!( "Tunnel error from agent {}: channel_id={}, error={}", agent_id, channel_id, error ); // Phase 2: Forward error to connected clients // For now, just log the error } ``` **Purpose:** - Receives error messages from agent tunnel operations - Logs errors for monitoring and debugging - **Placeholder for Phase 2:** Will notify clients of errors **Phase 2 Implementation:** - Forward error to connected clients - Mark channel as failed in database - Potentially close tunnel session on critical errors --- ## Message Flow ### Tunnel Lifecycle **1. Open Tunnel (Server → Agent):** ``` Client HTTP Request → Server API → Database Insert ↓ Server WebSocket → Agent (TunnelOpen) ``` **2. Tunnel Ready (Agent → Server):** ``` Agent (TunnelReady) → Server WebSocket → Database Update ↓ Log Success ``` **3. Terminal Command (Phase 2):** ``` Client Request → Server (TunnelData/Terminal) → Agent ↓ Agent Executes Command ↓ Agent (TunnelData/TerminalOutput) → Server → Client ``` **4. Error Handling:** ``` Agent Error → Agent (TunnelError) → Server → Log ↓ (Phase 2: Notify Client) ``` **5. Close Tunnel:** ``` Client HTTP Request → Server API → Server (TunnelClose) → Agent ↓ Database Update ``` **6. Agent Disconnect:** ``` Agent WebSocket Close → Server Cleanup → Database Close All Sessions ``` --- ## Protocol Verification ### Agent Messages (FROM Agent to Server) ✅ `Auth` - Authentication handshake ✅ `Metrics` - System metrics reporting ✅ `NetworkState` - Network interface updates ✅ `CommandResult` - Command execution results ✅ `WatchdogEvent` - Service monitoring events ✅ `UpdateResult` - Agent update status ✅ `Heartbeat` - Keep-alive ping ✅ **`TunnelReady`** - Tunnel established (NEW) ✅ **`TunnelData`** - Tunnel data payload (NEW) ✅ **`TunnelError`** - Tunnel error message (NEW) ### Server Messages (FROM Server to Agent) ✅ `AuthAck` - Authentication response ✅ `Command` - Execute command ✅ `ConfigUpdate` - Configuration change ✅ `Update` - Agent update instruction ✅ `Ack` - Generic acknowledgment ✅ `Error` - Error message ✅ **`TunnelOpen`** - Open tunnel session (Phase 1) ✅ **`TunnelClose`** - Close tunnel session (Phase 1) ✅ **`TunnelData`** - Tunnel data payload (Phase 1) --- ## Data Structures ### TunnelDataPayload (Shared by Agent and Server) ```rust #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", content = "payload")] #[serde(rename_all = "snake_case")] pub enum TunnelDataPayload { /// Terminal command execution (Phase 1) Terminal { command: String }, /// Terminal output response TerminalOutput { stdout: String, stderr: String, exit_code: Option, }, } ``` **Note:** This enum is already defined in `ws/mod.rs` and is used by both `ServerMessage::TunnelData` and `AgentMessage::TunnelData`. --- ## Testing Validation ### 1. TunnelReady Message **Agent sends:** ```json { "type": "tunnel_ready", "payload": { "session_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` **Expected server behavior:** - Deserializes successfully - Logs: `Agent tunnel ready: session_id=` - Updates `tech_sessions.last_activity` timestamp - No errors --- ### 2. TunnelData Message (Terminal Output) **Agent sends:** ```json { "type": "tunnel_data", "payload": { "channel_id": "terminal-1", "data": { "type": "terminal_output", "payload": { "stdout": "Hello, World!\n", "stderr": "", "exit_code": 0 } } } } ``` **Expected server behavior:** - Deserializes successfully - Logs: `Received tunnel data from agent : channel_id=terminal-1` - Logs: `Terminal stdout: Hello, World!` - Logs: `Terminal exit code: 0` --- ### 3. TunnelError Message **Agent sends:** ```json { "type": "tunnel_error", "payload": { "channel_id": "terminal-1", "error": "Failed to execute command: permission denied" } } ``` **Expected server behavior:** - Deserializes successfully - Logs error: `Tunnel error from agent : channel_id=terminal-1, error=Failed to execute command: permission denied` --- ## Compilation Status **Result:** ✅ SUCCESS ```bash $ cargo check Checking gururmm-server v0.2.0 Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.50s ``` **Notes:** - Zero compilation errors - All tunnel message variants properly integrated - Existing warnings unrelated to tunnel changes --- ## Phase 2 Requirements To complete the tunnel feature, Phase 2 needs: ### Server-Side: 1. **Client WebSocket endpoint** for tunnel output streaming - Route: `GET /api/v1/tunnel/:session_id/stream` - Streams terminal output in real-time 2. **Send command endpoint** (HTTP or WebSocket) - Route: `POST /api/v1/tunnel/:session_id/command` - Body: `{ "command": "ls -la" }` - Sends `TunnelData(Terminal)` to agent 3. **Output buffering** (optional) - Store recent output in memory or database - Allow clients to retrieve missed output 4. **Client connection tracking** - Track which clients are listening to which sessions - Forward output only to connected clients ### Agent-Side (Already Complete): ✅ `TunnelOpen` handler ✅ `TunnelClose` handler ✅ `TunnelData` handler for terminal commands ✅ Terminal command execution ✅ Output capture and streaming --- ## Security Considerations ### Already Implemented: ✅ Session ownership verification (only tunnel creator can interact) ✅ JWT authentication required for all endpoints ✅ Foreign key constraints (sessions tied to users) ✅ Automatic session cleanup on agent disconnect ### Phase 2 Considerations: - Rate limiting on command execution (prevent abuse) - Command whitelisting/blacklisting (security policy) - Audit logging of all commands executed - Session timeout for idle tunnels - Maximum concurrent sessions per user --- ## Database Schema Current schema already supports the protocol: ```sql CREATE TABLE tech_sessions ( id SERIAL PRIMARY KEY, session_id VARCHAR(36) UNIQUE NOT NULL, tech_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE, opened_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), last_activity TIMESTAMPTZ NOT NULL DEFAULT NOW(), closed_at TIMESTAMPTZ, status VARCHAR(20) NOT NULL DEFAULT 'active' ); ``` **Notes:** - `last_activity` updated on `TunnelReady` and future command activity - `status` can be extended: 'active', 'ready', 'closed', 'error' - `tunnel_audit` table ready for Phase 2 command logging --- ## Files Modified 1. **server/src/ws/mod.rs** - Added 3 new `AgentMessage` variants - Added handlers for `TunnelReady`, `TunnelData`, `TunnelError` - Uses existing `TunnelDataPayload` enum (already defined) **Total lines changed:** ~70 lines added --- ## Next Steps 1. **Test Protocol Integration** - Mock agent sending TunnelReady, TunnelData, TunnelError - Verify server logs show correct deserialization - Verify database updates (last_activity timestamp) 2. **Phase 2 Server Implementation** - Client WebSocket endpoint for output streaming - Command execution endpoint - Client connection management - Output buffering/forwarding 3. **End-to-End Testing** - Full tunnel lifecycle with real agent - Command execution and output streaming - Error handling and edge cases - Performance testing (concurrent sessions) --- **Last Updated:** 2026-04-14 **Status:** Protocol update complete, ready for Phase 2 implementation