Files
claudetools/projects/msp-tools/guru-rmm/server/TUNNEL_PROTOCOL_REFERENCE.md
azcomputerguru 2e6d1a67dd Implement GuruRMM Phase 1: Real-time tunnel infrastructure
Complete bidirectional tunnel communication between server and agents,
enabling persistent secure channels for future command execution and
file operations. Agents transition from heartbeat mode to tunnel mode
on-demand while maintaining WebSocket connection.

Server Implementation:
- Database layer (db/tunnel.rs): Session CRUD, ownership validation,
  cleanup on disconnect (prevents orphaned sessions)
- API endpoints (api/tunnel.rs): POST /open, POST /close, GET /status
  with JWT auth, UUID validation, proper HTTP status codes
- Protocol extension (ws/mod.rs): TunnelOpen/Close/Data messages,
  agent response handlers (TunnelReady/Data/Error)
- Migration (006_tunnel_sessions.sql): tech_sessions table with
  partial unique constraint, foreign keys with CASCADE, audit table

Agent Implementation:
- State machine (tunnel/mod.rs): AgentMode (Heartbeat ↔ Tunnel),
  channel multiplexing, concurrent session prevention
- WebSocket handlers (transport/websocket.rs): Open/close tunnel,
  mode switching without dropping connection, cleanup on disconnect
- Protocol extension (transport/mod.rs): TunnelReady/Data/Error
  messages matching server definitions
- Unit tests: Lifecycle and channel management coverage

Key Features:
- Security: JWT auth, session ownership verification, SQL injection
  prevention, constraint-based duplicate session blocking
- Cleanup: Automatic session closure on agent disconnect (both sides),
  channel cleanup, graceful state transitions
- Error handling: Proper HTTP status codes (400/403/404/409/500),
  comprehensive Result types, detailed logging
- Extensibility: Channel types ready (Terminal/File/Registry/Service),
  TunnelDataPayload enum for Phase 2+ expansion

Phase 1 Scope (Implemented):
- Tunnel session lifecycle management
- Mode switching (heartbeat ↔ tunnel)
- Protocol message routing
- Database session tracking

Phase 2 Next Steps:
- Terminal command execution (tokio::process::Command)
- Client WebSocket connections for output streaming
- Command audit logging
- File transfer operations

Verification:
- Server compiles successfully (0 errors)
- Agent unit tests pass (tunnel lifecycle, channel management)
- Code review approved (protocol alignment verified)
- Database constraints enforce referential integrity
- Cleanup tested (session closure on disconnect)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-14 07:10:09 -07:00

7.3 KiB

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<i32> } 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)

{
  "type": "tunnel_open",
  "payload": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "tech_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
  }
}

TunnelReady (Agent → Server)

{
  "type": "tunnel_ready",
  "payload": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

TunnelData - Terminal Command (Server → Agent)

{
  "type": "tunnel_data",
  "payload": {
    "channel_id": "terminal-1",
    "data": {
      "type": "terminal",
      "payload": {
        "command": "ls -la /home"
      }
    }
  }
}

TunnelData - Terminal Output (Agent → Server)

{
  "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)

{
  "type": "tunnel_error",
  "payload": {
    "channel_id": "terminal-1",
    "error": "Failed to execute command: No such file or directory"
  }
}

TunnelClose (Server → Agent)

{
  "type": "tunnel_close",
  "payload": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

HTTP API Endpoints

Open Tunnel

POST /api/v1/tunnel/open
Authorization: Bearer <jwt_token>
Content-Type: application/json

{
  "agent_id": "550e8400-e29b-41d4-a716-446655440000"
}

Response:

{
  "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

POST /api/v1/tunnel/close
Authorization: Bearer <jwt_token>
Content-Type: application/json

{
  "session_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}

Response:

{
  "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

GET /api/v1/tunnel/status/{session_id}
Authorization: Bearer <jwt_token>

Response:

{
  "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

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)

  • Database schema (tech_sessions table)
  • Server message types (TunnelOpen, TunnelClose, TunnelData)
  • Agent message types (TunnelReady, TunnelData, TunnelError)
  • HTTP API endpoints (open, close, status)
  • WebSocket message handlers (all 3 agent messages)
  • Session ownership validation
  • Unique constraint handling (409 Conflict)
  • Agent disconnect cleanup
  • Foreign key constraints
  • 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