From dd2b9060618af154f142c2720a969688fd9086f4 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 9 Jun 2023 11:09:49 -0500 Subject: [PATCH 1/3] feat: add handling for insecure requests --- package.json | 7 ++++++- src/extension.ts | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b0829fa..7c63f0b6 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,11 @@ }, "scope": "machine", "default": [] + }, + "coder.insecure": { + "markdownDescription": "If true, the extension will not verify the authenticity of the remote host. This is useful for self-signed certificates.", + "type": "boolean", + "default": false } } }, @@ -241,4 +246,4 @@ "yaml": "^1.10.0", "zod": "^3.21.4" } -} +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index e88928d6..a2f145ee 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,5 +1,7 @@ "use strict" +import axios from "axios" import { getAuthenticatedUser } from "coder/site/src/api/api" +import * as https from "https" import * as module from "module" import * as vscode from "vscode" import { Commands } from "./commands" @@ -83,6 +85,27 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { const commands = new Commands(vscodeProposed, storage) + // updateInsecure is called on extension activation and when the insecure + // setting is changed. It updates the https agent to allow self-signed + // certificates if the insecure setting is true. + const updateInsecure = () => { + const insecure = Boolean(vscode.workspace.getConfiguration().get("coder.insecure")) + axios.defaults.httpsAgent = new https.Agent({ + // rejectUnauthorized defaults to true, so we need to explicitly set it to false + // if we want to allow self-signed certificates. + rejectUnauthorized: !insecure, + }) + } + + axios.interceptors.response.use(undefined, (error) => { + // if (error) + return error + }) + + vscode.workspace.onDidChangeConfiguration((e) => { + e.affectsConfiguration("coder.insecure") && updateInsecure() + }) + vscode.commands.registerCommand("coder.login", commands.login.bind(commands)) vscode.commands.registerCommand("coder.logout", commands.logout.bind(commands)) vscode.commands.registerCommand("coder.open", commands.open.bind(commands)) From a61b7f00abae7acc43913a20fb9e2d8918c5d891 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 9 Jun 2023 13:05:50 -0500 Subject: [PATCH 2/3] Add support for the insecure property --- src/commands.ts | 14 +++++- src/error.ts | 44 +++++++++++++++++ src/extension.ts | 106 +++++++++++++++++++++++++---------------- src/remote.ts | 2 +- src/sshSupport.test.ts | 1 + src/sshSupport.ts | 2 +- 6 files changed, 126 insertions(+), 43 deletions(-) create mode 100644 src/error.ts diff --git a/src/commands.ts b/src/commands.ts index 23c03d11..bc7ccf1d 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -6,6 +6,7 @@ import { extractAgents } from "./api-helper" import { Remote } from "./remote" import { Storage } from "./storage" import { OpenableTreeItem } from "./workspacesProvider" +import { SelfSignedCertificateError } from "./error" export class Commands { public constructor(private readonly vscodeProposed: typeof vscode, private readonly storage: Storage) {} @@ -61,6 +62,14 @@ export class Commands { if (axios.isAxiosError(err) && err.response?.data) { message = err.response.data.detail } + if (err instanceof SelfSignedCertificateError) { + err.showInsecureNotification(this.storage) + + return { + message: err.message, + severity: vscode.InputBoxValidationSeverity.Error, + } + } return { message: "Invalid session token! (" + message + ")", severity: vscode.InputBoxValidationSeverity.Error, @@ -189,7 +198,10 @@ export class Commands { quickPick.items = items quickPick.busy = false }) - .catch(() => { + .catch((ex) => { + if (ex instanceof SelfSignedCertificateError) { + ex.showInsecureNotification(this.storage) + } return }) }) diff --git a/src/error.ts b/src/error.ts new file mode 100644 index 00000000..c238caa8 --- /dev/null +++ b/src/error.ts @@ -0,0 +1,44 @@ +import * as vscode from "vscode" +import * as fs from "fs/promises" +import * as jsonc from "jsonc-parser" +import { Storage } from "./storage" + +export class SelfSignedCertificateError extends Error { + public static Notification = "Your Coder deployment is using a self-signed certificate. VS Code uses a version of Electron that does not support registering self-signed intermediate certificates with extensions." + public static ActionAllowInsecure = "Allow Insecure" + public static ActionViewMoreDetails = "View More Details" + + constructor(message: string) { + super(`Your Coder deployment is using a self-signed certificate: ${message}`) + } + + public viewMoreDetails(): Thenable { + return vscode.env.openExternal(vscode.Uri.parse("https://github.com/coder/vscode-coder/issues/105")) + } + + // allowInsecure manually reads the settings file and updates the value of the + // "coder.insecure" property. + public async allowInsecure(storage: Storage): Promise { + let settingsContent = "{}" + try { + settingsContent = await fs.readFile(storage.getUserSettingsPath(), "utf8") + } catch (ex) { + // Ignore! It's probably because the file doesn't exist. + } + const edits = jsonc.modify(settingsContent, ["coder.insecure"], true, {}) + await fs.writeFile(storage.getUserSettingsPath(), jsonc.applyEdits(settingsContent, edits)) + + vscode.window.showInformationMessage("The Coder extension will no longer verify TLS on HTTPS requests. You can change this at any time with the \"coder.insecure\" property in your VS Code settings.") + } + + public async showInsecureNotification(storage: Storage): Promise { + const value = await vscode.window.showErrorMessage(SelfSignedCertificateError.Notification, SelfSignedCertificateError.ActionAllowInsecure, SelfSignedCertificateError.ActionViewMoreDetails) + if (value === SelfSignedCertificateError.ActionViewMoreDetails) { + await this.viewMoreDetails() + return + } + if (value === SelfSignedCertificateError.ActionAllowInsecure) { + return this.allowInsecure(storage) + } + } +} diff --git a/src/extension.ts b/src/extension.ts index a2f145ee..daf1a478 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -8,8 +8,58 @@ import { Commands } from "./commands" import { Remote } from "./remote" import { Storage } from "./storage" import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider" +import { SelfSignedCertificateError } from "./error" export async function activate(ctx: vscode.ExtensionContext): Promise { + // The Remote SSH extension's proposed APIs are used to override + // the SSH host name in VS Code itself. It's visually unappealing + // having a lengthy name! + // + // This is janky, but that's alright since it provides such minimal + // functionality to the extension. + const remoteSSHExtension = vscode.extensions.getExtension("ms-vscode-remote.remote-ssh") + if (!remoteSSHExtension) { + throw new Error("Remote SSH extension not found") + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const vscodeProposed: typeof vscode = (module as any)._load( + "vscode", + { + filename: remoteSSHExtension?.extensionPath, + }, + false, + ) + + // updateInsecure is called on extension activation and when the insecure + // setting is changed. It updates the https agent to allow self-signed + // certificates if the insecure setting is true. + const applyInsecure = () => { + const insecure = Boolean(vscode.workspace.getConfiguration().get("coder.insecure")) + console.log("updating insecure", insecure) + + axios.defaults.httpsAgent = new https.Agent({ + // rejectUnauthorized defaults to true, so we need to explicitly set it to false + // if we want to allow self-signed certificates. + rejectUnauthorized: !insecure, + }) + } + + axios.interceptors.response.use(r => r, (err) => { + if (err) { + const msg = err.toString() as string + if (msg.indexOf("unable to verify the first certificate") !== -1) { + throw new SelfSignedCertificateError(msg) + } + } + + throw err + }) + + vscode.workspace.onDidChangeConfiguration((e) => { + e.affectsConfiguration("coder.insecure") && applyInsecure() + }) + applyInsecure() + const output = vscode.window.createOutputChannel("Coder") const storage = new Storage(output, ctx.globalState, ctx.secrets, ctx.globalStorageUri, ctx.logUri) await storage.init() @@ -64,48 +114,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { }, }) - // The Remote SSH extension's proposed APIs are used to override - // the SSH host name in VS Code itself. It's visually unappealing - // having a lengthy name! - // - // This is janky, but that's alright since it provides such minimal - // functionality to the extension. - const remoteSSHExtension = vscode.extensions.getExtension("ms-vscode-remote.remote-ssh") - if (!remoteSSHExtension) { - throw new Error("Remote SSH extension not found") - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const vscodeProposed: typeof vscode = (module as any)._load( - "vscode", - { - filename: remoteSSHExtension?.extensionPath, - }, - false, - ) - const commands = new Commands(vscodeProposed, storage) - // updateInsecure is called on extension activation and when the insecure - // setting is changed. It updates the https agent to allow self-signed - // certificates if the insecure setting is true. - const updateInsecure = () => { - const insecure = Boolean(vscode.workspace.getConfiguration().get("coder.insecure")) - axios.defaults.httpsAgent = new https.Agent({ - // rejectUnauthorized defaults to true, so we need to explicitly set it to false - // if we want to allow self-signed certificates. - rejectUnauthorized: !insecure, - }) - } - - axios.interceptors.response.use(undefined, (error) => { - // if (error) - return error - }) - - vscode.workspace.onDidChangeConfiguration((e) => { - e.affectsConfiguration("coder.insecure") && updateInsecure() - }) - vscode.commands.registerCommand("coder.login", commands.login.bind(commands)) vscode.commands.registerCommand("coder.logout", commands.logout.bind(commands)) vscode.commands.registerCommand("coder.open", commands.open.bind(commands)) @@ -132,6 +142,22 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { try { await remote.setup(vscodeProposed.env.remoteAuthority) } catch (ex) { + if (ex instanceof SelfSignedCertificateError) { + const prompt = await vscodeProposed.window.showErrorMessage("Failed to open workspace", { + detail: SelfSignedCertificateError.Notification, + modal: true, + useCustom: true, + }, SelfSignedCertificateError.ActionAllowInsecure, SelfSignedCertificateError.ActionViewMoreDetails) + if (prompt === SelfSignedCertificateError.ActionAllowInsecure) { + await ex.allowInsecure(storage) + await remote.reloadWindow() + return + } + if (prompt === SelfSignedCertificateError.ActionViewMoreDetails) { + await ex.viewMoreDetails() + return + } + } await vscodeProposed.window.showErrorMessage("Failed to open workspace", { detail: (ex as string).toString(), modal: true, diff --git a/src/remote.ts b/src/remote.ts index c97430e1..5d2f1134 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -696,7 +696,7 @@ export class Remote { } // reloadWindow reloads the current window. - private async reloadWindow() { + public async reloadWindow() { await vscode.commands.executeCommand("workbench.action.reloadWindow") } diff --git a/src/sshSupport.test.ts b/src/sshSupport.test.ts index dbcde14f..8bf2ac58 100644 --- a/src/sshSupport.test.ts +++ b/src/sshSupport.test.ts @@ -3,6 +3,7 @@ import { computeSSHProperties, sshSupportsSetEnv, sshVersionSupportsSetEnv } fro const supports = { "OpenSSH_8.9p1 Ubuntu-3ubuntu0.1, OpenSSL 3.0.2 15 Mar 2022": true, + "OpenSSH_for_Windows_8.1p1, LibreSSL 3.0.2": true, "OpenSSH_9.0p1, LibreSSL 3.3.6": true, "OpenSSH_7.6p1 Ubuntu-4ubuntu0.7, OpenSSL 1.0.2n 7 Dec 2017": false, "OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017": false, diff --git a/src/sshSupport.ts b/src/sshSupport.ts index 5726070a..12b65dd3 100644 --- a/src/sshSupport.ts +++ b/src/sshSupport.ts @@ -16,7 +16,7 @@ export function sshSupportsSetEnv(): boolean { // // It was introduced in SSH 7.8 and not all versions support it. export function sshVersionSupportsSetEnv(sshVersionString: string): boolean { - const match = sshVersionString.match(/OpenSSH_([\d.]+)[^,]*/) + const match = sshVersionString.match(/OpenSSH.*_([\d.]+)[^,]*/) if (match && match[1]) { const installedVersion = match[1] const parts = installedVersion.split(".") From 5985fceaffc5ff98d7a353adb66d835fee3a8089 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 9 Jun 2023 13:08:31 -0500 Subject: [PATCH 3/3] Fix linting --- src/commands.ts | 4 ++-- src/error.ts | 15 +++++++++++---- src/extension.ts | 37 ++++++++++++++++++++++--------------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index bc7ccf1d..079aac20 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -3,10 +3,10 @@ import { getAuthenticatedUser, getWorkspaces, updateWorkspaceVersion } from "cod import { Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated" import * as vscode from "vscode" import { extractAgents } from "./api-helper" +import { SelfSignedCertificateError } from "./error" import { Remote } from "./remote" import { Storage } from "./storage" import { OpenableTreeItem } from "./workspacesProvider" -import { SelfSignedCertificateError } from "./error" export class Commands { public constructor(private readonly vscodeProposed: typeof vscode, private readonly storage: Storage) {} @@ -64,7 +64,7 @@ export class Commands { } if (err instanceof SelfSignedCertificateError) { err.showInsecureNotification(this.storage) - + return { message: err.message, severity: vscode.InputBoxValidationSeverity.Error, diff --git a/src/error.ts b/src/error.ts index c238caa8..4c247836 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,10 +1,11 @@ -import * as vscode from "vscode" import * as fs from "fs/promises" import * as jsonc from "jsonc-parser" +import * as vscode from "vscode" import { Storage } from "./storage" export class SelfSignedCertificateError extends Error { - public static Notification = "Your Coder deployment is using a self-signed certificate. VS Code uses a version of Electron that does not support registering self-signed intermediate certificates with extensions." + public static Notification = + "Your Coder deployment is using a self-signed certificate. VS Code uses a version of Electron that does not support registering self-signed intermediate certificates with extensions." public static ActionAllowInsecure = "Allow Insecure" public static ActionViewMoreDetails = "View More Details" @@ -28,11 +29,17 @@ export class SelfSignedCertificateError extends Error { const edits = jsonc.modify(settingsContent, ["coder.insecure"], true, {}) await fs.writeFile(storage.getUserSettingsPath(), jsonc.applyEdits(settingsContent, edits)) - vscode.window.showInformationMessage("The Coder extension will no longer verify TLS on HTTPS requests. You can change this at any time with the \"coder.insecure\" property in your VS Code settings.") + vscode.window.showInformationMessage( + 'The Coder extension will no longer verify TLS on HTTPS requests. You can change this at any time with the "coder.insecure" property in your VS Code settings.', + ) } public async showInsecureNotification(storage: Storage): Promise { - const value = await vscode.window.showErrorMessage(SelfSignedCertificateError.Notification, SelfSignedCertificateError.ActionAllowInsecure, SelfSignedCertificateError.ActionViewMoreDetails) + const value = await vscode.window.showErrorMessage( + SelfSignedCertificateError.Notification, + SelfSignedCertificateError.ActionAllowInsecure, + SelfSignedCertificateError.ActionViewMoreDetails, + ) if (value === SelfSignedCertificateError.ActionViewMoreDetails) { await this.viewMoreDetails() return diff --git a/src/extension.ts b/src/extension.ts index daf1a478..b5aaf62c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,10 +5,10 @@ import * as https from "https" import * as module from "module" import * as vscode from "vscode" import { Commands } from "./commands" +import { SelfSignedCertificateError } from "./error" import { Remote } from "./remote" import { Storage } from "./storage" import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider" -import { SelfSignedCertificateError } from "./error" export async function activate(ctx: vscode.ExtensionContext): Promise { // The Remote SSH extension's proposed APIs are used to override @@ -35,7 +35,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { // certificates if the insecure setting is true. const applyInsecure = () => { const insecure = Boolean(vscode.workspace.getConfiguration().get("coder.insecure")) - console.log("updating insecure", insecure) axios.defaults.httpsAgent = new https.Agent({ // rejectUnauthorized defaults to true, so we need to explicitly set it to false @@ -44,16 +43,19 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { }) } - axios.interceptors.response.use(r => r, (err) => { - if (err) { - const msg = err.toString() as string - if (msg.indexOf("unable to verify the first certificate") !== -1) { - throw new SelfSignedCertificateError(msg) + axios.interceptors.response.use( + (r) => r, + (err) => { + if (err) { + const msg = err.toString() as string + if (msg.indexOf("unable to verify the first certificate") !== -1) { + throw new SelfSignedCertificateError(msg) + } } - } - throw err - }) + throw err + }, + ) vscode.workspace.onDidChangeConfiguration((e) => { e.affectsConfiguration("coder.insecure") && applyInsecure() @@ -143,11 +145,16 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { await remote.setup(vscodeProposed.env.remoteAuthority) } catch (ex) { if (ex instanceof SelfSignedCertificateError) { - const prompt = await vscodeProposed.window.showErrorMessage("Failed to open workspace", { - detail: SelfSignedCertificateError.Notification, - modal: true, - useCustom: true, - }, SelfSignedCertificateError.ActionAllowInsecure, SelfSignedCertificateError.ActionViewMoreDetails) + const prompt = await vscodeProposed.window.showErrorMessage( + "Failed to open workspace", + { + detail: SelfSignedCertificateError.Notification, + modal: true, + useCustom: true, + }, + SelfSignedCertificateError.ActionAllowInsecure, + SelfSignedCertificateError.ActionViewMoreDetails, + ) if (prompt === SelfSignedCertificateError.ActionAllowInsecure) { await ex.allowInsecure(storage) await remote.reloadWindow() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy