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>
298 lines
7.3 KiB
Markdown
298 lines
7.3 KiB
Markdown
# 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)
|
|
```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 <jwt_token>
|
|
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 <jwt_token>
|
|
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 <jwt_token>
|
|
```
|
|
|
|
**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
|