Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions client/src/components/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export interface SSHProfile extends BaseProfile {
saspath: string;
port: number;
username: string;
identityFile: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be made as an optional field? I think having it as optional is more on brand for the overall approaches we recommend for this in the markdown changes. It should also clean up the tests a bit so that the "original" tests dont have to be burdened with keeping up with default values. In my mind we would write a separate set of tests that isolates how this new member gets passed around etc.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With apologies, this is the first time I've ever used either typescript, or VS Code. In my head this is already "optional", in that when the profile is created, you can leave it blank.

I intuit you mean "optional" like "the TS type system affords making it explicit that this field is optional", but it's not obvious to me how to implement that in a way that aligns with the rest of the codebase. I would appreciate pointers to the right path.

}

export interface COMProfile extends BaseProfile {
Expand Down Expand Up @@ -573,6 +574,14 @@ export class ProfileConfig {
return;
}

profileClone.identityFile = await createInputTextBox(
ProfilePromptType.IdentityFile,
profileClone.identityFile,
);
if (profileClone.identityFile === undefined) {
return;
}

profileClone.port = parseInt(
await createInputTextBox(ProfilePromptType.Port, DEFAULT_SSH_PORT),
);
Expand Down Expand Up @@ -657,6 +666,7 @@ export enum ProfilePromptType {
SASPath,
Port,
Username,
IdentityFile,
}

/**
Expand Down Expand Up @@ -794,6 +804,13 @@ const input: ProfilePromptInput = {
placeholder: l10n.t("Enter your username"),
description: l10n.t("Enter your SAS server username."),
},
[ProfilePromptType.IdentityFile]: {
title: l10n.t("SAS Server Private Key"),
placeholder: l10n.t("(Optional) Enter your private key path"),
description: l10n.t(
"(Optional) Enter path to private key for your SAS server.",
),
},
};

/**
Expand Down
18 changes: 16 additions & 2 deletions client/src/connection/ssh/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { l10n } from "vscode";

import { readFileSync } from "fs";
import { Client, ClientChannel, ConnectConfig } from "ssh2";

import { BaseConfig, RunResult } from "..";
Expand All @@ -18,12 +19,18 @@ export interface Config extends BaseConfig {
username: string;
saspath: string;
port: number;
identityFile: string;
}

export function getSession(c: Config): Session {
if (!process.env.SSH_AUTH_SOCK) {
if (
!process.env.SSH_AUTH_SOCK &&
!(c.identityFile && c.identityFile.length)
) {
throw new Error(
l10n.t("SSH_AUTH_SOCK not set. Check Environment Variables."),
l10n.t(
"SSH_AUTH_SOCK not set and no identityFile provided. Check Environment Variables and profile.",
),
);
}

Expand Down Expand Up @@ -67,12 +74,19 @@ export class SSHSession extends Session {
return;
}

// Exception bubbles up usefully if this is set, but the file doesn't exist
let privateKey = undefined;
if (this._config.identityFile && this._config.identityFile.length) {
privateKey = readFileSync(this._config.identityFile);
}

const cfg: ConnectConfig = {
host: this._config.host,
port: this._config.port,
username: this._config.username,
readyTimeout: sasLaunchTimeout,
agent: process.env.SSH_AUTH_SOCK || undefined,
privateKey: privateKey,
};

this.conn
Expand Down
1 change: 1 addition & 0 deletions client/test/components/profile/profile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ describe("Profiles", async function () {
sasOptions: ["-nonews"],
saspath: "/sas/path",
username: "username",
identityFile: "",
};
// Arrange
// Act
Expand Down
7 changes: 7 additions & 0 deletions client/test/connection/ssh/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe("ssh connection", () => {
saspath: "/path/to/sas_u8",
sasOptions: [],
agentSocket: "/agent/socket",
identityFile: "",
};

session = new SSHSession(config);
Expand All @@ -46,6 +47,7 @@ describe("ssh connection", () => {
port: 22,
saspath: "/path/to/sas_u8",
sasOptions: [],
identityFile: "",
};

sandbox.stub(Client.prototype, "connect").callsFake(function () {
Expand Down Expand Up @@ -85,6 +87,7 @@ describe("ssh connection", () => {
saspath: "/path/to/sas_u8",
sasOptions: [],
agentSocket: "/agent/socket",
identityFile: "",
};

sandbox.stub(Client.prototype, "connect").callsFake(function () {
Expand All @@ -106,6 +109,7 @@ describe("ssh connection", () => {
saspath: "/path/to/sas_u8",
sasOptions: [],
agentSocket: "/agent/socket",
identityFile: "",
};

sandbox.stub(Client.prototype, "connect").callsFake(function () {
Expand Down Expand Up @@ -133,6 +137,7 @@ describe("ssh connection", () => {
saspath: "/path/to/sas_u8",
sasOptions: [],
agentSocket: "/agent/socket",
identityFile: "",
};

sandbox.stub(Client.prototype, "connect").callsFake(function () {
Expand All @@ -159,6 +164,7 @@ describe("ssh connection", () => {
saspath: "/path/to/sas_u8",
sasOptions: [],
agentSocket: "/agent/socket",
identityFile: "",
};

const session = new SSHSession(config);
Expand Down Expand Up @@ -339,6 +345,7 @@ describe("ssh connection", () => {
saspath: "saspath",
sasOptions: ["-nonews"],
port: 22,
identityFile: "",
};
});

Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@
"host",
"saspath",
"username",
"port"
"port",
"identityFile"
],
"properties": {
"host": {
Expand All @@ -321,6 +322,11 @@
"description": "%configuration.SAS.connectionProfiles.profiles.ssh.port%",
"exclusiveMinimum": 1,
"exclusiveMaximum": 65535
},
"identityFile": {
"type": "string",
"default": "",
"description": "%configuration.SAS.connectionProfiles.profiles.ssh.identityFile%"
}
}
}
Expand Down
39 changes: 30 additions & 9 deletions website/docs/Configurations/Profiles/sas9ssh.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@ sidebar_position: 4

# SAS 9.4 (remote - SSH) Connection Profile

For a secure connection to SAS 9.4 (remote - SSH) server, a public / private SSH key pair is required. The socket defined in the environment variable `SSH_AUTH_SOCK` is used to communicate with ssh-agent to authenticate the SSH session. The private key must be registered with the ssh-agent. The steps for configuring SSH follow.
For a secure connection to SAS 9.4 (remote - SSH) server, a public / private SSH key pair is required. There are two ways to provide a Private Key:

1. (Preferred) The socket defined in the environment variable `SSH_AUTH_SOCK` is used to communicate with ssh-agent to authenticate the SSH session. The private key must be registered with the ssh-agent. The steps for configuring SSH follow.
1. (Non-Preferred) When creating an SSH profile, you will be asked for an identityFile. This is the full path to a private key. Note that this private key must be created without a passphrase protecting it (hence, this is the non-preferred path). Leave this blank if using ssh-agent

## Profile Anatomy

A SAS 9.4 (remote – SSH) connection profile includes the following parameters:

`"connectionType": "ssh"`

| Name | Description | Additional Notes |
| ---------- | ------------------------------------ | -------------------------------------------------------------------------- |
| `host` | SSH Server Host | Appears when hovering over the status bar. |
| `username` | SSH Server Username | The username to establish the SSH connection to the server. |
| `port` | SSH Server Port | The SSH port of the SSH server. The default value is 22. |
| `saspath` | Path to SAS Executable on the server | Must be a fully qualified path on the SSH server to a SAS executable file. |
| Name | Description | Additional Notes |
| -------------- | ------------------------------------ | -------------------------------------------------------------------------- |
| `host` | SSH Server Host | Appears when hovering over the status bar. |
| `username` | SSH Server Username | The username to establish the SSH connection to the server. |
| `port` | SSH Server Port | The SSH port of the SSH server. The default value is 22. |
| `saspath` | Path to SAS Executable on the server | Must be a fully qualified path on the SSH server to a SAS executable file. |
| `identityFile` | Full path to private key | Must be a fully qualified path on local host to an unshielded private key. |

## Required setup for connection to SAS 9.4 (remote - SSH)

Expand Down Expand Up @@ -72,7 +76,7 @@ Note: if ~/.ssh/config does not exist, run the following Powershell command to c

Note: the default path to the SAS executable (saspath) is /opt/sasinside/SASHome/SASFoundation/9.4/bin/sas_u8. Check with your SAS administrator for the exact path.

9. Add the public part of the keypair to the SAS server. Add the contents of the key file to the ~/.ssh/authorized_keys file.
9. Add the public part of the keypair to the SAS server. Add the contents of the key file to the ~/.ssh/authorized_keys file. If you have it on your system, you can use "ssh-copy-id" command to make this easy. By default, `ssh-copy-id host.machine.name` will probably do what you need.

### Mac

Expand Down Expand Up @@ -111,4 +115,21 @@ Host host.machine.name
}
```

6. Add the public part of the keypair to the SAS server. Add the contents of the key file to the ~/.ssh/authorized_keys file.
6. Add the public part of the keypair to the SAS server. Add the contents of the key file to the ~/.ssh/authorized_keys file. If you have it on your system, you can use "ssh-copy-id" command to make this easy. By default, `ssh-copy-id host.machine.name` will probably do what you need.

## Windows/Mac/Linux using only identityFile

1. Create a keypair and copy to the server, as described in sections above
1. It must be created without a passphrase
1. In the profile, provide the full path to your private key:

```json
"ssh_test": {
"connectionType": "ssh",
"host": "host.machine.name",
"saspath": "/path/to/sas/executable",
"username": "username",
"port": 22,
"identityFile": "c:\\Users\\username\\.ssh\\id_ed25519"
}
```