Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c771300

Browse files
committed
Merge branch 'main' into Liorba/main
2 parents f95a72f + a53b8db commit c771300

File tree

7 files changed

+147
-24
lines changed

7 files changed

+147
-24
lines changed

package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,12 @@
6060
"description": "optional.Path to a file to retrieve the token from, if set, it will ignore the the require on startup "
6161

6262
}
63-
}
64-
63+
}
64+
},
65+
"coder.insecure": {
66+
"markdownDescription": "If true, the extension will not verify the authenticity of the remote host. This is useful for self-signed certificates.",
67+
"type": "boolean",
68+
"default": false
6569
}
6670
}
6771
},

src/commands.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getAuthenticatedUser, getWorkspaces, updateWorkspaceVersion } from "cod
33
import { Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated"
44
import * as vscode from "vscode"
55
import { extractAgents } from "./api-helper"
6+
import { SelfSignedCertificateError } from "./error"
67
import { Remote } from "./remote"
78
import { Storage } from "./storage"
89
import { OpenableTreeItem } from "./workspacesProvider"
@@ -61,6 +62,14 @@ export class Commands {
6162
if (axios.isAxiosError(err) && err.response?.data) {
6263
message = err.response.data.detail
6364
}
65+
if (err instanceof SelfSignedCertificateError) {
66+
err.showInsecureNotification(this.storage)
67+
68+
return {
69+
message: err.message,
70+
severity: vscode.InputBoxValidationSeverity.Error,
71+
}
72+
}
6473
return {
6574
message: "Invalid session token! (" + message + ")",
6675
severity: vscode.InputBoxValidationSeverity.Error,
@@ -189,7 +198,10 @@ export class Commands {
189198
quickPick.items = items
190199
quickPick.busy = false
191200
})
192-
.catch(() => {
201+
.catch((ex) => {
202+
if (ex instanceof SelfSignedCertificateError) {
203+
ex.showInsecureNotification(this.storage)
204+
}
193205
return
194206
})
195207
})

src/error.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as fs from "fs/promises"
2+
import * as jsonc from "jsonc-parser"
3+
import * as vscode from "vscode"
4+
import { Storage } from "./storage"
5+
6+
export class SelfSignedCertificateError extends Error {
7+
public static Notification =
8+
"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."
9+
public static ActionAllowInsecure = "Allow Insecure"
10+
public static ActionViewMoreDetails = "View More Details"
11+
12+
constructor(message: string) {
13+
super(`Your Coder deployment is using a self-signed certificate: ${message}`)
14+
}
15+
16+
public viewMoreDetails(): Thenable<boolean> {
17+
return vscode.env.openExternal(vscode.Uri.parse("https://github.com/coder/vscode-coder/issues/105"))
18+
}
19+
20+
// allowInsecure manually reads the settings file and updates the value of the
21+
// "coder.insecure" property.
22+
public async allowInsecure(storage: Storage): Promise<void> {
23+
let settingsContent = "{}"
24+
try {
25+
settingsContent = await fs.readFile(storage.getUserSettingsPath(), "utf8")
26+
} catch (ex) {
27+
// Ignore! It's probably because the file doesn't exist.
28+
}
29+
const edits = jsonc.modify(settingsContent, ["coder.insecure"], true, {})
30+
await fs.writeFile(storage.getUserSettingsPath(), jsonc.applyEdits(settingsContent, edits))
31+
32+
vscode.window.showInformationMessage(
33+
'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.',
34+
)
35+
}
36+
37+
public async showInsecureNotification(storage: Storage): Promise<void> {
38+
const value = await vscode.window.showErrorMessage(
39+
SelfSignedCertificateError.Notification,
40+
SelfSignedCertificateError.ActionAllowInsecure,
41+
SelfSignedCertificateError.ActionViewMoreDetails,
42+
)
43+
if (value === SelfSignedCertificateError.ActionViewMoreDetails) {
44+
await this.viewMoreDetails()
45+
return
46+
}
47+
if (value === SelfSignedCertificateError.ActionAllowInsecure) {
48+
return this.allowInsecure(storage)
49+
}
50+
}
51+
}

src/extension.ts

+74-19
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,69 @@
22
import axios, { AxiosResponse } from "axios"
33
import { getAuthenticatedUser } from "coder/site/src/api/api"
44
import { readFileSync } from "fs"
5+
import * as https from "https"
56
import * as module from "module"
67
import * as os from "os"
78
import path from "path"
89
import * as vscode from "vscode"
910
import { Commands } from "./commands"
11+
import { SelfSignedCertificateError } from "./error"
1012
import { Remote } from "./remote"
1113
import { Storage } from "./storage"
1214
import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider"
1315

1416
export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
17+
// The Remote SSH extension's proposed APIs are used to override
18+
// the SSH host name in VS Code itself. It's visually unappealing
19+
// having a lengthy name!
20+
//
21+
// This is janky, but that's alright since it provides such minimal
22+
// functionality to the extension.
23+
const remoteSSHExtension = vscode.extensions.getExtension("ms-vscode-remote.remote-ssh")
24+
if (!remoteSSHExtension) {
25+
throw new Error("Remote SSH extension not found")
26+
}
27+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
28+
const vscodeProposed: typeof vscode = (module as any)._load(
29+
"vscode",
30+
{
31+
filename: remoteSSHExtension?.extensionPath,
32+
},
33+
false,
34+
)
35+
36+
// updateInsecure is called on extension activation and when the insecure
37+
// setting is changed. It updates the https agent to allow self-signed
38+
// certificates if the insecure setting is true.
39+
const applyInsecure = () => {
40+
const insecure = Boolean(vscode.workspace.getConfiguration().get("coder.insecure"))
41+
42+
axios.defaults.httpsAgent = new https.Agent({
43+
// rejectUnauthorized defaults to true, so we need to explicitly set it to false
44+
// if we want to allow self-signed certificates.
45+
rejectUnauthorized: !insecure,
46+
})
47+
}
48+
49+
axios.interceptors.response.use(
50+
(r) => r,
51+
(err) => {
52+
if (err) {
53+
const msg = err.toString() as string
54+
if (msg.indexOf("unable to verify the first certificate") !== -1) {
55+
throw new SelfSignedCertificateError(msg)
56+
}
57+
}
58+
59+
throw err
60+
},
61+
)
62+
63+
vscode.workspace.onDidChangeConfiguration((e) => {
64+
e.affectsConfiguration("coder.insecure") && applyInsecure()
65+
})
66+
applyInsecure()
67+
1568
const output = vscode.window.createOutputChannel("Coder")
1669
const storage = new Storage(output, ctx.globalState, ctx.secrets, ctx.globalStorageUri, ctx.logUri)
1770
await storage.init()
@@ -67,25 +120,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
67120
},
68121
})
69122

70-
// The Remote SSH extension's proposed APIs are used to override
71-
// the SSH host name in VS Code itself. It's visually unappealing
72-
// having a lengthy name!
73-
//
74-
// This is janky, but that's alright since it provides such minimal
75-
// functionality to the extension.
76-
const remoteSSHExtension = vscode.extensions.getExtension("ms-vscode-remote.remote-ssh")
77-
if (!remoteSSHExtension) {
78-
throw new Error("Remote SSH extension not found")
79-
}
80-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
81-
const vscodeProposed: typeof vscode = (module as any)._load(
82-
"vscode",
83-
{
84-
filename: remoteSSHExtension?.extensionPath,
85-
},
86-
false,
87-
)
88-
89123
const commands = new Commands(vscodeProposed, storage)
90124

91125
vscode.commands.registerCommand("coder.login", commands.login.bind(commands))
@@ -114,6 +148,27 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
114148
try {
115149
await remote.setup(vscodeProposed.env.remoteAuthority)
116150
} catch (ex) {
151+
if (ex instanceof SelfSignedCertificateError) {
152+
const prompt = await vscodeProposed.window.showErrorMessage(
153+
"Failed to open workspace",
154+
{
155+
detail: SelfSignedCertificateError.Notification,
156+
modal: true,
157+
useCustom: true,
158+
},
159+
SelfSignedCertificateError.ActionAllowInsecure,
160+
SelfSignedCertificateError.ActionViewMoreDetails,
161+
)
162+
if (prompt === SelfSignedCertificateError.ActionAllowInsecure) {
163+
await ex.allowInsecure(storage)
164+
await remote.reloadWindow()
165+
return
166+
}
167+
if (prompt === SelfSignedCertificateError.ActionViewMoreDetails) {
168+
await ex.viewMoreDetails()
169+
return
170+
}
171+
}
117172
await vscodeProposed.window.showErrorMessage("Failed to open workspace", {
118173
detail: (ex as string).toString(),
119174
modal: true,

src/remote.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ export class Remote {
706706
}
707707

708708
// reloadWindow reloads the current window.
709-
private async reloadWindow() {
709+
public async reloadWindow() {
710710
await vscode.commands.executeCommand("workbench.action.reloadWindow")
711711
}
712712

src/sshSupport.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { computeSSHProperties, sshSupportsSetEnv, sshVersionSupportsSetEnv } fro
33

44
const supports = {
55
"OpenSSH_8.9p1 Ubuntu-3ubuntu0.1, OpenSSL 3.0.2 15 Mar 2022": true,
6+
"OpenSSH_for_Windows_8.1p1, LibreSSL 3.0.2": true,
67
"OpenSSH_9.0p1, LibreSSL 3.3.6": true,
78
"OpenSSH_7.6p1 Ubuntu-4ubuntu0.7, OpenSSL 1.0.2n 7 Dec 2017": false,
89
"OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017": false,

src/sshSupport.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function sshSupportsSetEnv(): boolean {
1616
//
1717
// It was introduced in SSH 7.8 and not all versions support it.
1818
export function sshVersionSupportsSetEnv(sshVersionString: string): boolean {
19-
const match = sshVersionString.match(/OpenSSH_([\d.]+)[^,]*/)
19+
const match = sshVersionString.match(/OpenSSH.*_([\d.]+)[^,]*/)
2020
if (match && match[1]) {
2121
const installedVersion = match[1]
2222
const parts = installedVersion.split(".")

0 commit comments

Comments
 (0)