Move connection token validation earlier

This commit is contained in:
Alex Dima
2022-01-24 17:37:34 +01:00
parent 38a7ba7c6d
commit 12ca9894a5
5 changed files with 33 additions and 23 deletions

View File

@@ -27,7 +27,7 @@
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="/manifest.json">
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
</head>
<body aria-label="">

View File

@@ -24,7 +24,7 @@
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="/manifest.json">
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link data-name="vs/workbench/workbench.web.main" rel="stylesheet" href="./static/out/vs/workbench/workbench.web.main.css">
</head>

View File

@@ -30,15 +30,13 @@ import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAg
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ExtensionHostConnection } from 'vs/server/node/extensionHostConnection';
import { ManagementConnection } from 'vs/server/node/remoteExtensionManagement';
import { parseServerConnectionToken, ServerConnectionToken, ServerConnectionTokenParseError, ServerConnectionTokenType } from 'vs/server/node/serverConnectionToken';
import { parseServerConnectionToken, requestHasValidConnectionToken as httpRequestHasValidConnectionToken, ServerConnectionToken, ServerConnectionTokenParseError, ServerConnectionTokenType } from 'vs/server/node/serverConnectionToken';
import { IServerEnvironmentService, ServerParsedArgs } from 'vs/server/node/serverEnvironmentService';
import { setupServerServices, SocketServer } from 'vs/server/node/serverServices';
import { serveError, serveFile, WebClientServer } from 'vs/server/node/webClientServer';
const SHUTDOWN_TIMEOUT = 5 * 60 * 1000;
export type ServerListenOptions = { host?: string; port?: number; socketPath?: string };
declare module vsda {
// the signer is a native module that for historical reasons uses a lower case class name
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -114,13 +112,14 @@ export class RemoteExtensionHostAgentServer extends Disposable {
return res.end('OK');
}
if (!httpRequestHasValidConnectionToken(this._connectionToken, req, parsedUrl)) {
// invalid connection token
return serveError(req, res, 403, `Forbidden.`);
}
if (pathname === '/vscode-remote-resource') {
// Handle HTTP requests for resources rendered in the rich client (images, fonts, etc.)
// These resources could be files shipped with extensions or even workspace files.
if (!this._connectionToken.validate(parsedUrl.query['tkn'])) {
return serveError(req, res, 403, `Forbidden.`);
}
const desiredPath = parsedUrl.query['path'];
if (typeof desiredPath !== 'string') {
return serveError(req, res, 400, `Bad request.`);

View File

@@ -3,7 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as cookie from 'cookie';
import * as fs from 'fs';
import * as http from 'http';
import * as url from 'url';
import { generateUuid } from 'vs/base/common/uuid';
import { ServerParsedArgs } from 'vs/server/node/serverEnvironmentService';
@@ -106,3 +109,14 @@ export function parseServerConnectionToken(args: ServerParsedArgs): ServerConnec
return new MandatoryServerConnectionToken(generateUuid());
}
export function requestHasValidConnectionToken(connectionToken: ServerConnectionToken, req: http.IncomingMessage, parsedUrl: url.UrlWithParsedQuery) {
// First check if there is a valid `tkn` query parameter
if (connectionToken.validate(parsedUrl.query['tkn'])) {
return true;
}
// Otherwise, check if there is a valid `vscode-tkn` cookie
const cookies = cookie.parse(req.headers.cookie || '');
return connectionToken.validate(cookies['vscode-tkn']);
}

View File

@@ -110,11 +110,6 @@ export class WebClientServer {
}
}
private _hasCorrectTokenCookie(req: http.IncomingMessage): boolean {
const cookies = cookie.parse(req.headers.cookie || '');
return this._connectionToken.validate(cookies['vscode-tkn']);
}
/**
* Handle HTTP requests for /static/*
*/
@@ -141,12 +136,18 @@ export class WebClientServer {
return serveError(req, res, 400, `Bad request.`);
}
const queryTkn = parsedUrl.query['tkn'];
if (typeof queryTkn === 'string') {
// tkn came in via a query string
// => set a cookie and redirect to url without tkn
if (typeof parsedUrl.query['tkn'] === 'string') {
// We got a connection token as a query parameter.
// We want to have a clean URL, so we strip it
const responseHeaders: Record<string, string> = Object.create(null);
responseHeaders['Set-Cookie'] = cookie.serialize('vscode-tkn', queryTkn, { sameSite: 'strict', maxAge: 60 * 60 * 24 * 7 /* 1 week */ });
responseHeaders['Set-Cookie'] = cookie.serialize(
'vscode-tkn',
parsedUrl.query['tkn'],
{
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7 /* 1 week */
}
);
const newQuery = Object.create(null);
for (let key in parsedUrl.query) {
@@ -161,10 +162,6 @@ export class WebClientServer {
return res.end();
}
if (this._environmentService.isBuilt && !this._hasCorrectTokenCookie(req)) {
return serveError(req, res, 403, `Forbidden.`);
}
const remoteAuthority = req.headers.host;
function escapeAttribute(value: string): string {