Skip to content

Commit 4c37680

Browse files
authored
feat: validate ssh properties before launching workspaces (#96)
1 parent c0a1871 commit 4c37680

File tree

3 files changed

+104
-4
lines changed

3 files changed

+104
-4
lines changed

src/remote.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import * as semver from "semver"
2020
import * as vscode from "vscode"
2121
import * as ws from "ws"
2222
import { SSHConfig, SSHValues, defaultSSHConfigResponse, mergeSSHConfigValues } from "./sshConfig"
23-
import { sshSupportsSetEnv } from "./sshSupport"
23+
import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport"
2424
import { Storage } from "./storage"
2525

2626
export class Remote {
@@ -411,7 +411,7 @@ export class Remote {
411411
//
412412
// If we didn't write to the SSH config file, connecting would fail with
413413
// "Host not found".
414-
await this.updateSSHConfig()
414+
await this.updateSSHConfig(authorityParts[1])
415415

416416
this.findSSHProcessID().then((pid) => {
417417
if (!pid) {
@@ -440,7 +440,7 @@ export class Remote {
440440

441441
// updateSSHConfig updates the SSH configuration with a wildcard that handles
442442
// all Coder entries.
443-
private async updateSSHConfig() {
443+
private async updateSSHConfig(hostName: string) {
444444
let deploymentSSHConfig = defaultSSHConfigResponse
445445
try {
446446
const deploymentConfig = await getDeploymentSSHConfig()
@@ -528,6 +528,34 @@ export class Remote {
528528
}
529529

530530
await sshConfig.update(sshValues, sshConfigOverrides)
531+
532+
// A user can provide a "Host *" entry in their SSH config to add options
533+
// to all hosts. We need to ensure that the options we set are not
534+
// overridden by the user's config.
535+
const computedProperties = computeSSHProperties(hostName, sshConfig.getRaw())
536+
const keysToMatch: Array<keyof SSHValues> = ["ProxyCommand", "UserKnownHostsFile", "StrictHostKeyChecking"]
537+
for (let i = 0; i < keysToMatch.length; i++) {
538+
const key = keysToMatch[i]
539+
if (computedProperties[key] === sshValues[key]) {
540+
continue
541+
}
542+
543+
const result = await this.vscodeProposed.window.showErrorMessage(
544+
"Unexpected SSH Config Option",
545+
{
546+
useCustom: true,
547+
modal: true,
548+
detail: `Your SSH config is overriding the "${key}" property to "${computedProperties[key]}" when it expected "${sshValues[key]}" for the "${hostName}" host. Please fix this and try again!`,
549+
},
550+
"Reload Window",
551+
)
552+
if (result === "Reload Window") {
553+
await this.reloadWindow()
554+
}
555+
await this.closeRemote()
556+
}
557+
558+
return sshConfig.getRaw()
531559
}
532560

533561
// showNetworkUpdates finds the SSH process ID that is being used by this

src/sshSupport.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { it, expect } from "vitest"
2-
import { sshSupportsSetEnv, sshVersionSupportsSetEnv } from "./sshSupport"
2+
import { computeSSHProperties, sshSupportsSetEnv, sshVersionSupportsSetEnv } from "./sshSupport"
33

44
const supports = {
55
"OpenSSH_8.9p1 Ubuntu-3ubuntu0.1, OpenSSL 3.0.2 15 Mar 2022": true,
@@ -17,3 +17,23 @@ Object.entries(supports).forEach(([version, expected]) => {
1717
it("current shell supports ssh", () => {
1818
expect(sshSupportsSetEnv()).toBeTruthy()
1919
})
20+
21+
it("computes the config for a host", () => {
22+
const properties = computeSSHProperties(
23+
"coder-vscode--testing",
24+
`Host *
25+
StrictHostKeyChecking yes
26+
27+
# --- START CODER VSCODE ---
28+
Host coder-vscode--*
29+
StrictHostKeyChecking no
30+
Another=true
31+
# --- END CODER VSCODE ---
32+
`,
33+
)
34+
35+
expect(properties).toEqual({
36+
Another: "true",
37+
StrictHostKeyChecking: "yes",
38+
})
39+
})

src/sshSupport.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,55 @@ export function sshVersionSupportsSetEnv(sshVersionString: string): boolean {
3636
}
3737
return false
3838
}
39+
40+
// computeSSHProperties accepts an SSH config and a host name and returns
41+
// the properties that should be set for that host.
42+
export function computeSSHProperties(host: string, config: string): Record<string, string> {
43+
let currentConfig:
44+
| {
45+
Host: string
46+
properties: Record<string, string>
47+
}
48+
| undefined
49+
const configs: Array<typeof currentConfig> = []
50+
config.split("\n").forEach((line) => {
51+
line = line.trim()
52+
if (line === "") {
53+
return
54+
}
55+
const [key, ...valueParts] = line.split(/\s+|=/)
56+
if (key.startsWith("#")) {
57+
// Ignore comments!
58+
return
59+
}
60+
if (key === "Host") {
61+
if (currentConfig) {
62+
configs.push(currentConfig)
63+
}
64+
currentConfig = {
65+
Host: valueParts.join(" "),
66+
properties: {},
67+
}
68+
return
69+
}
70+
if (!currentConfig) {
71+
return
72+
}
73+
currentConfig.properties[key] = valueParts.join(" ")
74+
})
75+
if (currentConfig) {
76+
configs.push(currentConfig)
77+
}
78+
79+
const merged: Record<string, string> = {}
80+
configs.reverse().forEach((config) => {
81+
if (!config) {
82+
return
83+
}
84+
if (!new RegExp("^" + config?.Host.replace(/\*/g, ".*") + "$").test(host)) {
85+
return
86+
}
87+
Object.assign(merged, config.properties)
88+
})
89+
return merged
90+
}

0 commit comments

Comments
 (0)
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