diff --git a/CHANGELOG.md b/CHANGELOG.md index c3b1ecd1..af5e6754 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* **console:** fix remote node Host Console failing with "Connection error" / 502. The gateway's console-token request to the remote node was missing license tier headers, causing the remote's Admiral license gate to reject the request. Both the HTTP fetch and the WS upgrade handler now correctly propagate proxy tier headers for console_session tokens. * **auto-update:** resolve failure when executing auto-update policies on remote Distributed API nodes. Previously, the scheduler tried to access the remote Docker daemon directly, which is not supported. Now the update execution is proxied to the remote Sencho instance via HTTP, matching the existing Distributed API architecture. * **schedules:** auto-update policies no longer appear in the Scheduled Operations list. Each view now fetches only its relevant task type via server-side action filtering. diff --git a/backend/src/index.ts b/backend/src/index.ts index e47c6d73..823db1a8 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -2690,9 +2690,14 @@ server.on('upgrade', async (req, socket, head) => { let bearerTokenForProxy = node.api_token; if (isInteractiveConsolePath) { try { + const ls = LicenseService.getInstance(); const tokenRes = await fetch(`${node.api_url.replace(/\/$/, '')}/api/system/console-token`, { method: 'POST', - headers: { 'Authorization': `Bearer ${node.api_token}` }, + headers: { + 'Authorization': `Bearer ${node.api_token}`, + [PROXY_TIER_HEADER]: ls.getTier(), + [PROXY_VARIANT_HEADER]: ls.getVariant() || '', + }, }); if (tokenRes.ok) { const data = await tokenRes.json() as { token?: string }; @@ -2759,9 +2764,20 @@ server.on('upgrade', async (req, socket, head) => { socket.destroy(); return; } - // Admiral license gate: host console requires Admiral (paid + team variant) + // Admiral license gate: host console requires Admiral (paid + team variant). + // For proxied connections (console_session tokens), trust the tier headers sent by the gateway; + // for direct connections, check the local LicenseService. + const isConsoleSession = decoded.scope === 'console_session'; + const consoleTierHeader = req.headers[PROXY_TIER_HEADER] as string | undefined; + const consoleVariantHeader = req.headers[PROXY_VARIANT_HEADER] as string | undefined; const ls = LicenseService.getInstance(); - if (ls.getTier() !== 'paid' || ls.getVariant() !== 'admiral') { + const consoleTier = (isConsoleSession && isLicenseTier(consoleTierHeader)) + ? normalizeTier(consoleTierHeader) + : ls.getTier(); + const consoleVariant = (isConsoleSession && consoleVariantHeader !== undefined && isLicenseVariant(consoleVariantHeader)) + ? normalizeVariant(consoleVariantHeader) + : ls.getVariant(); + if (consoleTier !== 'paid' || consoleVariant !== 'admiral') { socket.write('HTTP/1.1 403 Forbidden\r\n\r\n'); socket.destroy(); return;