From 037f0df6627392629a05e7491e040096195d43fc Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 4 May 2023 12:17:29 -0500 Subject: [PATCH] feat: validate ssh properties before launching workspaces This was reported by a member in Discord, and displayed a poor error by VS Code when it failed before. Users cannot override our config options otherwise connections may fail or have unexpected behavior. --- src/remote.ts | 34 ++++++++++++++++++++++++--- src/sshSupport.test.ts | 22 +++++++++++++++++- src/sshSupport.ts | 52 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/remote.ts b/src/remote.ts index dfdd54ee..af8c039c 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -20,7 +20,7 @@ import * as semver from "semver" import * as vscode from "vscode" import * as ws from "ws" import { SSHConfig, SSHValues, defaultSSHConfigResponse, mergeSSHConfigValues } from "./sshConfig" -import { sshSupportsSetEnv } from "./sshSupport" +import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport" import { Storage } from "./storage" export class Remote { @@ -411,7 +411,7 @@ export class Remote { // // If we didn't write to the SSH config file, connecting would fail with // "Host not found". - await this.updateSSHConfig() + await this.updateSSHConfig(authorityParts[1]) this.findSSHProcessID().then((pid) => { if (!pid) { @@ -440,7 +440,7 @@ export class Remote { // updateSSHConfig updates the SSH configuration with a wildcard that handles // all Coder entries. - private async updateSSHConfig() { + private async updateSSHConfig(hostName: string) { let deploymentSSHConfig = defaultSSHConfigResponse try { const deploymentConfig = await getDeploymentSSHConfig() @@ -528,6 +528,34 @@ export class Remote { } await sshConfig.update(sshValues, sshConfigOverrides) + + // A user can provide a "Host *" entry in their SSH config to add options + // to all hosts. We need to ensure that the options we set are not + // overridden by the user's config. + const computedProperties = computeSSHProperties(hostName, sshConfig.getRaw()) + const keysToMatch: Array = ["ProxyCommand", "UserKnownHostsFile", "StrictHostKeyChecking"] + for (let i = 0; i < keysToMatch.length; i++) { + const key = keysToMatch[i] + if (computedProperties[key] === sshValues[key]) { + continue + } + + const result = await this.vscodeProposed.window.showErrorMessage( + "Unexpected SSH Config Option", + { + useCustom: true, + modal: true, + 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!`, + }, + "Reload Window", + ) + if (result === "Reload Window") { + await this.reloadWindow() + } + await this.closeRemote() + } + + return sshConfig.getRaw() } // showNetworkUpdates finds the SSH process ID that is being used by this diff --git a/src/sshSupport.test.ts b/src/sshSupport.test.ts index 4761f6ec..dbcde14f 100644 --- a/src/sshSupport.test.ts +++ b/src/sshSupport.test.ts @@ -1,5 +1,5 @@ import { it, expect } from "vitest" -import { sshSupportsSetEnv, sshVersionSupportsSetEnv } from "./sshSupport" +import { computeSSHProperties, sshSupportsSetEnv, sshVersionSupportsSetEnv } from "./sshSupport" const supports = { "OpenSSH_8.9p1 Ubuntu-3ubuntu0.1, OpenSSL 3.0.2 15 Mar 2022": true, @@ -17,3 +17,23 @@ Object.entries(supports).forEach(([version, expected]) => { it("current shell supports ssh", () => { expect(sshSupportsSetEnv()).toBeTruthy() }) + +it("computes the config for a host", () => { + const properties = computeSSHProperties( + "coder-vscode--testing", + `Host * + StrictHostKeyChecking yes + +# --- START CODER VSCODE --- +Host coder-vscode--* + StrictHostKeyChecking no + Another=true +# --- END CODER VSCODE --- +`, + ) + + expect(properties).toEqual({ + Another: "true", + StrictHostKeyChecking: "yes", + }) +}) diff --git a/src/sshSupport.ts b/src/sshSupport.ts index 2121adad..5726070a 100644 --- a/src/sshSupport.ts +++ b/src/sshSupport.ts @@ -36,3 +36,55 @@ export function sshVersionSupportsSetEnv(sshVersionString: string): boolean { } return false } + +// computeSSHProperties accepts an SSH config and a host name and returns +// the properties that should be set for that host. +export function computeSSHProperties(host: string, config: string): Record { + let currentConfig: + | { + Host: string + properties: Record + } + | undefined + const configs: Array = [] + config.split("\n").forEach((line) => { + line = line.trim() + if (line === "") { + return + } + const [key, ...valueParts] = line.split(/\s+|=/) + if (key.startsWith("#")) { + // Ignore comments! + return + } + if (key === "Host") { + if (currentConfig) { + configs.push(currentConfig) + } + currentConfig = { + Host: valueParts.join(" "), + properties: {}, + } + return + } + if (!currentConfig) { + return + } + currentConfig.properties[key] = valueParts.join(" ") + }) + if (currentConfig) { + configs.push(currentConfig) + } + + const merged: Record = {} + configs.reverse().forEach((config) => { + if (!config) { + return + } + if (!new RegExp("^" + config?.Host.replace(/\*/g, ".*") + "$").test(host)) { + return + } + Object.assign(merged, config.properties) + }) + return merged +} 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