From a1131fadf2b1920f5ec665e9c6e98a7c577ccc72 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 26 Sep 2023 08:35:41 -0800 Subject: [PATCH] Enable secret storage (#6450) * Remove unused dependency patch * Enable secret storage based on local storage * Remove unnecessary GitHub auth patch It works now without the patch. --- patches/base-path.diff | 24 ++++++++- patches/dependencies.diff | 62 ---------------------- patches/github-auth.diff | 106 -------------------------------------- patches/series | 1 - src/node/routes/vscode.ts | 31 +++++++++++ 5 files changed, 53 insertions(+), 171 deletions(-) delete mode 100644 patches/dependencies.diff delete mode 100644 patches/github-auth.diff diff --git a/patches/base-path.diff b/patches/base-path.diff index 4ed778243..8a0d08aea 100644 --- a/patches/base-path.diff +++ b/patches/base-path.diff @@ -265,15 +265,35 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts } private startListening(): void { -@@ -569,7 +570,7 @@ function readCookie(name: string): strin +@@ -550,17 +551,6 @@ class WorkspaceProvider implements IWork + } + } + +-function readCookie(name: string): string | undefined { +- const cookies = document.cookie.split('; '); +- for (const cookie of cookies) { +- if (cookie.startsWith(name + '=')) { +- return cookie.substring(name.length + 1); +- } +- } +- +- return undefined; +-} +- + (function () { + + // Find config by checking for DOM +@@ -569,8 +559,8 @@ function readCookie(name: string): strin if (!configElement || !configElementAttribute) { throw new Error('Missing web configuration element'); } - const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents; callbackRoute: string } = JSON.parse(configElementAttribute); +- const secretStorageKeyPath = readCookie('vscode-secret-key-path'); + const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents; callbackRoute: string } = { ...JSON.parse(configElementAttribute), remoteAuthority: location.host } - const secretStorageKeyPath = readCookie('vscode-secret-key-path'); ++ const secretStorageKeyPath = (window.location.pathname + "/mint-key").replace(/\/\/+/g, "/"); const secretStorageCrypto = secretStorageKeyPath && ServerKeyedAESCrypto.supported() ? new ServerKeyedAESCrypto(secretStorageKeyPath) : new TransparentCrypto(); + Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts diff --git a/patches/dependencies.diff b/patches/dependencies.diff deleted file mode 100644 index 815633543..000000000 --- a/patches/dependencies.diff +++ /dev/null @@ -1,62 +0,0 @@ -Modify VS Code dependencies - -1. Kerberos: this is not building in our cross-compile step. It does not look - like something code-server uses right now anyway. - -Index: code-server/lib/vscode/remote/package.json -=================================================================== ---- code-server.orig/lib/vscode/remote/package.json -+++ code-server/lib/vscode/remote/package.json -@@ -18,7 +18,6 @@ - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.3", - "jschardet": "3.0.0", -- "kerberos": "^2.0.1", - "keytar": "7.9.0", - "minimist": "^1.2.6", - "native-watchdog": "^1.4.1", -Index: code-server/lib/vscode/remote/yarn.lock -=================================================================== ---- code-server.orig/lib/vscode/remote/yarn.lock -+++ code-server/lib/vscode/remote/yarn.lock -@@ -454,15 +454,6 @@ jschardet@3.0.0: - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.0.0.tgz#898d2332e45ebabbdb6bf2feece9feea9a99e882" - integrity sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ== - --kerberos@^2.0.1: -- version "2.0.1" -- resolved "https://registry.yarnpkg.com/kerberos/-/kerberos-2.0.1.tgz#663b0b46883b4da84495f60f2e9e399a43a33ef5" -- integrity sha512-O/jIgbdGK566eUhFwIcgalbqirYU/r76MW7/UFw06Fd9x5bSwgyZWL/Vm26aAmezQww/G9KYkmmJBkEkPk5HLw== -- dependencies: -- bindings "^1.5.0" -- node-addon-api "^4.3.0" -- prebuild-install "7.1.1" -- - keytar@7.9.0: - version "7.9.0" - resolved "https://registry.yarnpkg.com/keytar/-/keytar-7.9.0.tgz#4c6225708f51b50cbf77c5aae81721964c2918cb" -@@ -604,24 +595,6 @@ picomatch@^2.3.1: - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - --prebuild-install@7.1.1: -- version "7.1.1" -- resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" -- integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== -- dependencies: -- detect-libc "^2.0.0" -- expand-template "^2.0.3" -- github-from-package "0.0.0" -- minimist "^1.2.3" -- mkdirp-classic "^0.5.3" -- napi-build-utils "^1.0.1" -- node-abi "^3.3.0" -- pump "^3.0.0" -- rc "^1.2.7" -- simple-get "^4.0.0" -- tar-fs "^2.0.0" -- tunnel-agent "^0.6.0" -- - prebuild-install@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.1.tgz#c10075727c318efe72412f333e0ef625beaf3870" diff --git a/patches/github-auth.diff b/patches/github-auth.diff deleted file mode 100644 index 20ab57e42..000000000 --- a/patches/github-auth.diff +++ /dev/null @@ -1,106 +0,0 @@ -Add the ability to provide a GitHub token - -To test install the GitHub PR extension and start code-server with GITHUB_TOKEN -or set github-auth in the config file. The extension should be authenticated. - -Index: code-server/lib/vscode/src/vs/platform/credentials/node/credentialsMainService.ts -=================================================================== ---- code-server.orig/lib/vscode/src/vs/platform/credentials/node/credentialsMainService.ts -+++ code-server/lib/vscode/src/vs/platform/credentials/node/credentialsMainService.ts -@@ -5,9 +5,18 @@ - - import { InMemoryCredentialsProvider } from 'vs/platform/credentials/common/credentials'; - import { ILogService } from 'vs/platform/log/common/log'; --import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -+import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService'; - import { IProductService } from 'vs/platform/product/common/productService'; - import { BaseCredentialsMainService, KeytarModule } from 'vs/platform/credentials/common/credentialsMainService'; -+import { generateUuid } from 'vs/base/common/uuid'; -+import { equals as arrayEquals } from 'vs/base/common/arrays'; -+ -+interface IToken { -+ accessToken: string -+ account?: { label: string } -+ id: string -+ scopes: string[] -+} - - export class CredentialsWebMainService extends BaseCredentialsMainService { - // Since we fallback to the in-memory credentials provider, we do not need to surface any Keytar load errors -@@ -16,10 +25,15 @@ export class CredentialsWebMainService e - - constructor( - @ILogService logService: ILogService, -- @INativeEnvironmentService private readonly environmentMainService: INativeEnvironmentService, -+ @IServerEnvironmentService private readonly environmentMainService: IServerEnvironmentService, - @IProductService private readonly productService: IProductService, - ) { - super(logService); -+ if (this.environmentMainService.args["github-auth"]) { -+ this.storeGitHubToken(this.environmentMainService.args["github-auth"]).catch((error) => { -+ this.logService.error('Failed to store provided GitHub token', error) -+ }) -+ } - } - - // If the credentials service is running on the server, we add a suffix -server to differentiate from the location that the -@@ -48,4 +62,59 @@ export class CredentialsWebMainService e - } - return this._keytarCache; - } -+ -+ private async storeGitHubToken(githubToken: string): Promise { -+ const extensionId = 'vscode.github-authentication'; -+ const service = `${await this.getSecretStoragePrefix()}${extensionId}`; -+ const account = 'github.auth'; -+ const scopes = [['read:user', 'user:email', 'repo']] -+ -+ // Oddly the scopes need to match exactly so we cannot just have one token -+ // with all the scopes, instead we have to duplicate the token for each -+ // expected set of scopes. -+ const tokens: IToken[] = scopes.map((scopes) => ({ -+ id: generateUuid(), -+ scopes: scopes.sort(), // Sort for comparing later. -+ accessToken: githubToken, -+ })); -+ -+ const raw = await this.getPassword(service, account) -+ -+ let existing: { -+ content: IToken[] -+ } | undefined; -+ -+ if (raw) { -+ try { -+ const json = JSON.parse(raw); -+ json.content = JSON.parse(json.content); -+ existing = json; -+ } catch (error) { -+ this.logService.error('Failed to parse existing GitHub credentials', error) -+ } -+ } -+ -+ // Keep tokens for account and scope combinations we do not have in case -+ // there is an extension that uses scopes we have not accounted for (in -+ // these cases the user will need to manually authenticate the extension -+ // through the UI) or the user has tokens for other accounts. -+ if (existing?.content) { -+ existing.content = existing.content.filter((existingToken) => { -+ const scopes = existingToken.scopes.sort(); -+ return !(tokens.find((token) => { -+ return arrayEquals(scopes, token.scopes) -+ && token.account?.label === existingToken.account?.label; -+ })) -+ }) -+ } -+ -+ return this.setPassword(service, account, JSON.stringify({ -+ extensionId, -+ ...(existing || {}), -+ content: JSON.stringify([ -+ ...tokens, -+ ...(existing?.content || []), -+ ]) -+ })); -+ } - } diff --git a/patches/series b/patches/series index dba2e20a3..898d2974a 100644 --- a/patches/series +++ b/patches/series @@ -9,7 +9,6 @@ update-check.diff logout.diff store-socket.diff proxy-uri.diff -github-auth.diff unique-db.diff local-storage.diff service-worker.diff diff --git a/src/node/routes/vscode.ts b/src/node/routes/vscode.ts index 85337611c..03cd9a4d7 100644 --- a/src/node/routes/vscode.ts +++ b/src/node/routes/vscode.ts @@ -1,5 +1,7 @@ import { logger } from "@coder/logger" +import * as crypto from "crypto" import * as express from "express" +import { promises as fs } from "fs" import * as http from "http" import * as net from "net" import * as path from "path" @@ -32,6 +34,7 @@ export class CodeServerRouteWrapper { private _wsRouterWrapper = WsRouter() private _socketProxyProvider = new SocketProxyProvider() public router = express.Router() + private mintKeyPromise: Promise | undefined public get wsRouter() { return this._wsRouterWrapper.router @@ -66,6 +69,33 @@ export class CodeServerRouteWrapper { ) } + private mintKey: express.Handler = async (req, res, next) => { + if (!this.mintKeyPromise) { + this.mintKeyPromise = new Promise(async (resolve) => { + const keyPath = path.join(req.args["user-data-dir"], "serve-web-key-half") + logger.debug(`Reading server web key half from ${keyPath}`) + try { + resolve(await fs.readFile(keyPath)) + return + } catch (error: any) { + if (error.code !== "ENOENT") { + logError(logger, `read ${keyPath}`, error) + } + } + // VS Code wants 256 bits. + const key = crypto.randomBytes(32) + try { + await fs.writeFile(keyPath, key) + } catch (error: any) { + logError(logger, `write ${keyPath}`, error) + } + resolve(key) + }) + } + const key = await this.mintKeyPromise + res.end(key) + } + private $root: express.Handler = async (req, res, next) => { const isAuthenticated = await authenticated(req) const NO_FOLDER_OR_WORKSPACE_QUERY = !req.query.folder && !req.query.workspace @@ -173,6 +203,7 @@ export class CodeServerRouteWrapper { constructor() { this.router.get("/", this.ensureCodeServerLoaded, this.$root) this.router.get("/manifest.json", this.manifest) + this.router.post("/mint-key", this.mintKey) this.router.all("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyRequest) this._wsRouterWrapper.ws("*", ensureOrigin, ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyWebsocket) }