# GuruRMM Tunnel Protocol - Quick Reference ## Message Types ### Server → Agent | Message | Payload | Purpose | |---------|---------|---------| | `TunnelOpen` | `{ session_id: String, tech_id: Uuid }` | Open tunnel session | | `TunnelClose` | `{ session_id: String }` | Close tunnel session | | `TunnelData` | `{ channel_id: String, data: TunnelDataPayload }` | Send command/data | ### Agent → Server | Message | Payload | Purpose | |---------|---------|---------| | `TunnelReady` | `{ session_id: String }` | Confirm tunnel ready | | `TunnelData` | `{ channel_id: String, data: TunnelDataPayload }` | Return output/data | | `TunnelError` | `{ channel_id: String, error: String }` | Report error | ### TunnelDataPayload (Both Directions) | Variant | Fields | Direction | Purpose | |---------|--------|-----------|---------| | `Terminal` | `{ command: String }` | Server → Agent | Execute terminal command | | `TerminalOutput` | `{ stdout: String, stderr: String, exit_code: Option }` | Agent → Server | Return command output | --- ## Message Flow Examples ### 1. Open Tunnel ``` Client → Server API: POST /api/v1/tunnel/open {"agent_id":"..."} Server → Agent WS: {"type":"tunnel_open","payload":{"session_id":"...","tech_id":"..."}} Agent → Server WS: {"type":"tunnel_ready","payload":{"session_id":"..."}} Server: Updates last_activity, logs success ``` ### 2. Execute Command (Phase 2) ``` Client → Server API: POST /api/v1/tunnel/:session_id/command {"command":"ls -la"} Server → Agent WS: {"type":"tunnel_data","payload":{"channel_id":"...","data":{"type":"terminal","payload":{"command":"ls -la"}}}} Agent: Executes command Agent → Server WS: {"type":"tunnel_data","payload":{"channel_id":"...","data":{"type":"terminal_output","payload":{"stdout":"...\n","stderr":"","exit_code":0}}}} Server → Client WS: Forwards output to connected clients ``` ### 3. Error Handling ``` Agent encounters error Agent → Server WS: {"type":"tunnel_error","payload":{"channel_id":"...","error":"Failed to execute: permission denied"}} Server: Logs error, forwards to clients (Phase 2) ``` ### 4. Close Tunnel ``` Client → Server API: POST /api/v1/tunnel/close {"session_id":"..."} Server → Agent WS: {"type":"tunnel_close","payload":{"session_id":"..."}} Server: Updates database (status='closed', closed_at=NOW()) ``` ### 5. Agent Disconnect ``` Agent WebSocket closes Server: Detects disconnect Server: Calls close_agent_tunnel_sessions(agent_id) Server: Sets all active sessions to 'closed' Server: Logs count of sessions closed ``` --- ## JSON Examples ### TunnelOpen (Server → Agent) ```json { "type": "tunnel_open", "payload": { "session_id": "550e8400-e29b-41d4-a716-446655440000", "tech_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } ``` ### TunnelReady (Agent → Server) ```json { "type": "tunnel_ready", "payload": { "session_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` ### TunnelData - Terminal Command (Server → Agent) ```json { "type": "tunnel_data", "payload": { "channel_id": "terminal-1", "data": { "type": "terminal", "payload": { "command": "ls -la /home" } } } } ``` ### TunnelData - Terminal Output (Agent → Server) ```json { "type": "tunnel_data", "payload": { "channel_id": "terminal-1", "data": { "type": "terminal_output", "payload": { "stdout": "total 8\ndrwxr-xr-x 2 user user 4096 Jan 01 12:00 .\ndrwxr-xr-x 20 root root 4096 Jan 01 12:00 ..\n", "stderr": "", "exit_code": 0 } } } } ``` ### TunnelError (Agent → Server) ```json { "type": "tunnel_error", "payload": { "channel_id": "terminal-1", "error": "Failed to execute command: No such file or directory" } } ``` ### TunnelClose (Server → Agent) ```json { "type": "tunnel_close", "payload": { "session_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` --- ## HTTP API Endpoints ### Open Tunnel ```http POST /api/v1/tunnel/open Authorization: Bearer Content-Type: application/json { "agent_id": "550e8400-e29b-41d4-a716-446655440000" } ``` **Response:** ```json { "session_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "status": "active" } ``` **Status Codes:** - 200 OK - Tunnel opened successfully - 400 Bad Request - Invalid agent_id format - 404 Not Found - Agent not connected - 409 Conflict - Active session already exists --- ### Close Tunnel ```http POST /api/v1/tunnel/close Authorization: Bearer Content-Type: application/json { "session_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } ``` **Response:** ```json { "status": "closed" } ``` **Status Codes:** - 200 OK - Tunnel closed successfully - 400 Bad Request - Invalid session_id format - 403 Forbidden - Session not owned by user - 404 Not Found - Session not found --- ### Get Tunnel Status ```http GET /api/v1/tunnel/status/{session_id} Authorization: Bearer ``` **Response:** ```json { "session_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "agent_id": "550e8400-e29b-41d4-a716-446655440000", "status": "active", "opened_at": "2026-04-14T10:30:00Z", "last_activity": "2026-04-14T10:31:45Z" } ``` **Status Codes:** - 200 OK - Status retrieved successfully - 400 Bad Request - Invalid session_id format - 403 Forbidden - Session not owned by user - 404 Not Found - Session not found --- ## Database Schema ```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', CONSTRAINT unique_active_session UNIQUE (tech_id, agent_id, status) WHERE status = 'active' ); ``` **Indexes:** - `idx_tech_sessions_tech` on `tech_id` - `idx_tech_sessions_agent` on `agent_id` - `idx_tech_sessions_status` on `status` --- ## Error Codes ### PostgreSQL Errors - `23505` - Unique constraint violation (handled as 409 Conflict) ### HTTP Status Codes - `400` - Bad Request (invalid UUID format, malformed JSON) - `401` - Unauthorized (missing/invalid JWT token) - `403` - Forbidden (session not owned by user) - `404` - Not Found (agent offline, session doesn't exist) - `409` - Conflict (active session already exists) - `500` - Internal Server Error (database failure, unexpected error) --- ## Implementation Checklist ### Phase 1 (Complete) - [x] Database schema (`tech_sessions` table) - [x] Server message types (`TunnelOpen`, `TunnelClose`, `TunnelData`) - [x] Agent message types (`TunnelReady`, `TunnelData`, `TunnelError`) - [x] HTTP API endpoints (open, close, status) - [x] WebSocket message handlers (all 3 agent messages) - [x] Session ownership validation - [x] Unique constraint handling (409 Conflict) - [x] Agent disconnect cleanup - [x] Foreign key constraints - [x] Error logging and monitoring ### Phase 2 (Pending) - [ ] Client WebSocket endpoint for output streaming - [ ] Command execution endpoint (send Terminal commands) - [ ] Output buffering/forwarding to clients - [ ] Client connection tracking - [ ] Real-time output streaming - [ ] Command audit logging --- **Last Updated:** 2026-04-14