Fix RDP WebSocket auth and tunnel UUID protocol

- RdpTab: remove token from base URL; pass via client.connect(data)
  because WebSocketTunnel always appends "?"+data to the URL,
  corrupting a pre-built ?token=JWT into ?token=JWT?<data>

- rdp.ts: send UUID as Guacamole internal instruction "0.,36.<uuid>;"
  (opcode="" = INTERNAL_DATA_OPCODE) instead of a plain string,
  matching the WebSocketTunnel 1.5.0+ protocol expectation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
deadRabbit
2026-02-22 13:48:56 +01:00
parent 07da63a68e
commit 6494ecf698
2 changed files with 12 additions and 8 deletions

View File

@@ -166,10 +166,11 @@ export async function rdpWebsocket(fastify: FastifyInstance) {
throw new Error(`guacd handshake failed: expected 'ready', got '${readyInstruction[0]}'`); throw new Error(`guacd handshake failed: expected 'ready', got '${readyInstruction[0]}'`);
} }
// 5. Send the guacd connection UUID as the first WebSocket message. // 5. Send the tunnel UUID as a Guacamole internal instruction.
// Guacamole.WebSocketTunnel expects this as its tunnel-UUID handshake. // WebSocketTunnel (1.5.0+) expects opcode "" (empty string) with the
// UUID as the single argument: "0.,36.<uuid>;"
const guacdUUID = readyInstruction[1] ?? randomUUID(); const guacdUUID = readyInstruction[1] ?? randomUUID();
socket.send(guacdUUID); socket.send(buildInstruction('', guacdUUID));
// 6. Flush any buffered bytes that arrived after 'ready' // 6. Flush any buffered bytes that arrived after 'ready'
if (tcpBuf.value.length > 0 && socket.readyState === WebSocket.OPEN) { if (tcpBuf.value.length > 0 && socket.readyState === WebSocket.OPEN) {

View File

@@ -10,10 +10,12 @@ interface Props {
type Status = 'connecting' | 'connected' | 'disconnected' | 'error'; type Status = 'connecting' | 'connected' | 'disconnected' | 'error';
function getWsUrl(connectionId: string, token: string): string { // NOTE: Do NOT include query params here. Guacamole.WebSocketTunnel always
// appends "?" + connectData to the URL, so auth params must go via connect().
function getWsUrl(connectionId: string): string {
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host; const host = window.location.host;
return `${proto}//${host}/ws/rdp/${connectionId}?token=${encodeURIComponent(token)}`; return `${proto}//${host}/ws/rdp/${connectionId}`;
} }
export const RdpTab: React.FC<Props> = ({ session }) => { export const RdpTab: React.FC<Props> = ({ session }) => {
@@ -29,7 +31,7 @@ export const RdpTab: React.FC<Props> = ({ session }) => {
setStatus('connecting'); setStatus('connecting');
setErrorMsg(''); setErrorMsg('');
const url = getWsUrl(session.connection.id, token); const url = getWsUrl(session.connection.id);
const tunnel = new Guacamole.WebSocketTunnel(url); const tunnel = new Guacamole.WebSocketTunnel(url);
const client = new Guacamole.Client(tunnel); const client = new Guacamole.Client(tunnel);
@@ -80,8 +82,9 @@ export const RdpTab: React.FC<Props> = ({ session }) => {
setErrorMsg(`Client error: ${error.message ?? 'unknown'}`); setErrorMsg(`Client error: ${error.message ?? 'unknown'}`);
}; };
// Connect // Connect — pass token as query param via connect(data).
client.connect(); // WebSocketTunnel appends "?" + data to the base URL, so auth goes here.
client.connect(`token=${encodeURIComponent(token)}`);
return () => { return () => {
keyboard.onkeydown = null; keyboard.onkeydown = null;