gns3-server/docs/features/vnc-websocket-console.md
YueGuobin 6d6b5351fa
fix: issue short-lived console tickets instead of JWTs in node_console MCP tool
LLM clients transcribing the console WebSocket URL into shell commands
reliably corrupted the ~200-char JWT embedded in it (dropped header
segment -> "MissingAlgorithmError: Missing 'alg' value in header" on
every connection attempt). The node_console tool now mints a short
random ticket ("gns3t_" + 16 urlsafe chars, 10 min TTL, multi-use)
stored server-side and bound to the node's console endpoints:

- new ConsoleTicketService (gns3server/services/console_tickets.py),
  in-memory store with lazy expiry sweeps
- get_current_active_user_from_websocket redeems tickets through the
  existing "token" query parameter, gated on websocket.path_params so a
  ticket only authenticates the console/ws and console/vnc routes of
  the node it was minted for; the JWT path is unchanged
- redemption reuses the existing user lookup, token_version revocation
  and is_active checks, so logging out invalidates outstanding tickets
- vnc_url no longer embeds the full session JWT
- the tool docstring now tells clients to run the returned command
  verbatim instead of reconstructing the URL
2026-08-29 01:00:12 +08:00

403 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
SPDX-License-Identifier: CC-BY-SA-4.0
See LICENSE file for licensing information.
-->
> This documentation is organized by AI with reference to actual code. AI can make mistakes — please verify against the source code when in doubt.
# VNC WebSocket Console
## Overview
GNS3 server supports WebSocket-based VNC console access, enabling browser-based graphical console connections to QEMU and Docker VMs without requiring standalone VNC client applications.
This implementation uses GNS3's API layer as a transparent WebSocket-to-TCP proxy, forwarding binary VNC protocol data between the browser and the VM's VNC server.
## Architecture
### Connection Flow
```mermaid
graph LR
A[Browser noVNC] -->|WebSocket binary| B[GNS3 Controller]
B -->|WebSocket + BasicAuth + SSL| C[GNS3 Compute]
C -->|TCP| D[QEMU/Docker VNC :5900]
```
**Controller** acts as a WebSocket-to-WebSocket relay (JWT auth, IPv6 handling).
**Compute** acts as a WebSocket-to-TCP bridge (validates node state, opens VNC TCP connection).
### Components
1. **Browser (noVNC)**
- HTML5 VNC client running in the browser
- Connects via WebSocket using `binary` subprotocol
- Handles RFB protocol (Remote Frame Buffer) for VNC
2. **GNS3 Controller API**
- WebSocket endpoint: `/v3/projects/{project_id}/nodes/{node_id}/console/vnc`
- Authentication: JWT token via query parameter
- Authorization: RBAC privilege check via `has_privilege_on_websocket("Node.Console")`
- Proxies WebSocket to compute node (WebSocket-to-WebSocket relay)
- Handles IPv6 addresses by wrapping in brackets
3. **GNS3 Compute API**
- WebSocket endpoint: `/v3/compute/projects/{project_id}/{node_type}/nodes/{node_id}/console/vnc`
- Authentication: HTTP Basic Auth via `ws_compute_authentication()`
- Establishes TCP connection to VNC server, bridges WebSocket ↔ TCP
4. **Node (QEMU/Docker)**
- VNC server listening on configured port (default: 5900+)
- RFB protocol for remote display
## API Endpoints
### Controller WebSocket Endpoint
**URL**: `ws://{controller_host}:{port}/v3/projects/{project_id}/nodes/{node_id}/console/vnc?token={jwt_token}`
**Authentication**:
- JWT token via query parameter, **or** a short-lived console ticket (`gns3t_…`, minted per node by the `node_console` MCP tool, valid 10 min, bound to this node's console endpoints)
- User must have `Node.Console` privilege
**WebSocket Subprotocols**:
- Accepts: `binary`
- Used by noVNC for binary data transfer
**Request Example**:
```javascript
const token = "eyJ0eXAiOiJKV1QiLCJhbGc...";
const wsUrl = `ws://localhost:3080/v3/projects/${projectId}/nodes/${nodeId}/console/vnc?token=${token}`;
const ws = new WebSocket(wsUrl, 'binary');
ws.binaryType = 'arraybuffer';
```
### Compute WebSocket Endpoint
**URL**: `ws://{compute_host}:{port}/v3/compute/projects/{project_id}/{node_type}/nodes/{node_id}/console/vnc`
**Authentication**:
- HTTP Basic Auth via `ws_compute_authentication()` dependency
- Configured via `settings.Server.compute_username` (default: `gns3`) and `settings.Server.compute_password` (default: empty)
**Response**:
- Bidirectional binary WebSocket connection
- Transparent VNC protocol forwarding
## Supported Node Types
### QEMU VMs
**Console Type Configuration**:
```json
{
"console_type": "vnc",
"console": 5900,
"console_resolution": "1024x768"
}
```
**QEMU Parameters**:
```bash
-vnc :0 # VNC on display 0 (port = 5900 + display)
```
**Implementation**:
- File: `gns3server/api/routes/compute/qemu_nodes.py`
- Endpoint: `/{node_id}/console/vnc`
- Method: `start_vnc_websocket_console(websocket)`
### Docker Containers
**Console Type Configuration**:
```json
{
"console_type": "vnc",
"console": 5900,
"console_resolution": "1024x768",
"console_http_port": 8080,
"console_http_path": "/"
}
```
**Implementation**:
- File: `gns3server/api/routes/compute/docker_nodes.py`
- Endpoint: `/{node_id}/console/vnc`
- Method: `start_vnc_websocket_console(websocket)`
## WebSocket Data Forwarding
### Compute Layer (WebSocket ↔ TCP)
**Location**: `gns3server/compute/base_node.py``start_vnc_websocket_console()`
1. Validates node is started and `console_type == "vnc"`; closes WebSocket with code 1000 otherwise
2. Opens TCP connection to VNC server at `console_host:console_port`
3. Runs two concurrent tasks via `asyncio.wait(FIRST_COMPLETED)`:
- `ws_forward()`: WebSocket → TCP (catches `WebSocketDisconnect`)
- `vnc_forward()`: TCP → WebSocket (reads 65536-byte buffer)
4. Cancels pending tasks, closes TCP writer on completion
### Controller Layer (WebSocket ↔ WebSocket)
**Location**: `gns3server/api/routes/controller/nodes.py``vnc_console()`
1. Authenticates user via `has_privilege_on_websocket("Node.Console")` dependency
2. Constructs compute URL with IPv6 bracket handling
3. Connects to compute WebSocket using `aiohttp.ws_connect()` with HTTP Basic Auth and SSL context
4. Uses `asyncio.ensure_future()` for client→compute forwarding, `async for msg` iteration for compute→client
### Data Flow
```mermaid
sequenceDiagram
participant B as Browser (noVNC)
participant C as Controller
participant W as Compute
participant V as VNC Server
B->>C: WebSocket connect (binary, JWT token)
C->>W: WebSocket connect (binary, BasicAuth, SSL)
W->>V: TCP connect (asyncio.open_connection)
Note over B,V: Bidirectional binary forwarding active
B->>C: WebSocket binary frame
C->>W: aiohttp send_bytes()
W->>V: TCP write(data)
V->>W: TCP data (read 65536)
W->>C: WebSocket binary frame
C->>B: send_bytes()
```
## Authentication & Authorization
### Controller Layer
1. **Authentication**:
- JWT token validation via `has_privilege_on_websocket("Node.Console")` dependency
- Token passed as query parameter: `?token={jwt}`, or a console ticket (`gns3t_…`) redeemable only on the node it was minted for
2. **Authorization**:
- RBAC privilege check: `Node.Console`
- Per-node access control
3. **WebSocket Subprotocol**:
- Client requests: `binary`
- Server accepts: `binary` (if requested)
### Compute Layer
1. **Authentication**:
- HTTP Basic Auth
- Credentials from controller config
- Username: `settings.Server.compute_username`
- Password: `settings.Server.compute_password`
2. **Node Validation**:
- Check node exists
- Check node is started
- Check console type is `vnc`
## Configuration
### Server Settings
**Controller Configuration** (`gns3-server.conf`):
```ini
[Server]
host = 0.0.0.0
port = 3080
```
**Compute Configuration** (same file):
```ini
[Server]
compute_username = gns3
compute_password = # empty by default, must be set for compute auth
```
### VNC Port Range
VNC console ports are allocated from a configurable range (`gns3server/schemas/config.py`):
| Setting | Default | Range |
|---------|---------|-------|
| `vnc_console_start_port_range` | 5900 | 590065535 |
| `vnc_console_end_port_range` | 10000 | 590065535 |
Validation: `vnc_console_end_port_range` must be greater than `vnc_console_start_port_range`.
### Node Settings
**QEMU VM Example**:
```json
{
"name": "vm-1",
"node_type": "qemu",
"console_type": "vnc",
"console": 5900,
"console_resolution": "1280x720",
"properties": {
"qemu_path": "/usr/bin/qemu-system-x86_64"
}
}
```
**Docker Container Example**:
```json
{
"name": "container-1",
"node_type": "docker",
"console_type": "vnc",
"console": 5900,
"console_resolution": "1024x768",
"console_http_port": 8080
}
```
## Troubleshooting
### Common Issues
**1. "Node is not started"**
- **Symptom**: WebSocket closes immediately
- **Solution**: Start the VM before opening console
- **API Check**: `GET /v3/projects/{project_id}/nodes/{node_id}` → verify `status == "started"`
**2. "Console type is not vnc"**
- **Symptom**: WebSocket closes with error
- **Solution**: Set node console_type to "vnc"
- **API Update**: `PUT /v3/projects/{project_id}/nodes/{node_id}` with `{"console_type": "vnc"}`
**3. "Cannot connect to VNC server"**
- **Symptom**: Connection timeout
- **Possible Causes**:
- VNC server not listening
- Port conflict
- Firewall blocking connection
- **Verification**:
```bash
# Check if VNC is listening
netstat -tlnp | grep 5900
# Check QEMU process
ps aux | grep qemu
```
**4. WebSocket Subprotocol Negotiation Failed**
- **Symptom**: Connection rejected during handshake
- **Solution**: Ensure noVNC sends `binary` subprotocol
- **Browser Console Check**:
```javascript
console.log(ws.protocol); // Should be "binary"
```
**5. Authentication Errors**
- **Symptom**: HTTP 401/403 responses
- **Controller**: Check JWT token is valid and not expired
- **Compute**: Verify compute username/password in config
### Debug Logging
**Enable Detailed Logging**:
```ini
[gns3server]
debug = true
```
**Check Controller Logs**:
```bash
# Look for WebSocket connection messages
grep "VNC console WebSocket" /var/log/gns3/gns3.log
```
**Check Compute Logs**:
```bash
# Look for VNC forwarding messages
grep "Connected to VNC server" /var/log/gns3/gns3.log
```
**Browser Console**:
```javascript
// Enable noVNC debugging
RFB.messages.log = function(msg) { console.log(msg); };
```
## Security
### Authentication
1. **Controller → Client**
- JWT token with expiration
- RBAC authorization
- Privilege: `Node.Console`
2. **Controller → Compute**
- HTTP Basic Auth via `aiohttp.BasicAuth`
- SSL context from `Controller.instance().ssl_context()`
- Raises `ControllerForbiddenError` if `compute_username` is not set
3. **VNC Server**
- Optional VNC password (QEMU only)
- Configured via node properties
### Network Security
**Recommendations**:
1. Use HTTPS/WSS for production deployments
2. Firewall compute API ports
3. Use short-lived JWT tokens
4. Enable VNC password for sensitive VMs
**Example**:
```bash
# Controller with TLS
gns3server --ssl --cert /path/to/cert.pem --key /path/to/key.pem
# VNC with password
qemu-system-x86_64 -vnc :0,password
```
### WebSocket Security
**Subprotocol Validation**:
- Only `binary` subprotocol is accepted
- Prevents protocol downgrade attacks
**Origin Validation**:
- Browser enforces same-origin policy
- Configure CORS if using separate domains
## Limitations
1. **Single Client per VNC Port**
- VNC protocol supports one client at a time
- New connections disconnect existing clients
2. **No Audio Redirection**
- VNC protocol does not support audio
3. **No USB Redirection**
- VNC protocol does not support device redirection
4. **Performance Overhead**
- WebSocket-to-TCP bridging adds processing overhead compared to native VNC clients
## References
- [RFB Protocol 3.8](https://tools.ietf.org/html/rfc6143) - VNC Protocol Specification
- [noVNC Documentation](https://github.com/novnc/noVNC) - HTML5 VNC Client
- [FastAPI WebSocket](https://fastapi.tiangolo.com/advanced/websockets/) - WebSocket Implementation
- [GNS3 Documentation](https://docs.gns3.com/) - General GNS3 Documentation
## Version History
| Version | Date | Changes |
|---------|------|---------|
| 1.1 | 2026-04-19 | Review against code: fix buffer size, auth details, add port range config, remove unverified content |
| 1.0 | 2026-03-17 | Initial VNC WebSocket documentation |