-
Notifications
You must be signed in to change notification settings - Fork 751
fix(ec2): avoid wiping authorized_keys files on each connection
#6197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 24 commits
c3d4ebb
22e1d1a
018d719
d03a2da
4fb8e66
cdecab6
f2900e5
902de77
b0d1823
bf03fd3
7c602a3
b907682
3d31db4
eb6142a
fa1b53b
d30c057
f960481
dbbfc09
ad13206
f171bb9
5498f25
4cd5400
9a2cc2c
84c4258
4b2c01f
d96a7e9
8b4954d
3309fb3
c9152a1
5092323
0234303
37d64ae
f988458
8f8237a
1bc5bdc
9e57d28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,6 +44,12 @@ export interface Ec2RemoteEnv extends VscodeRemoteConnection { | |
| ssmSession: SSM.StartSessionResponse | ||
| } | ||
|
|
||
| export type Ec2OS = 'Amazon Linux' | 'Ubuntu' | 'macOS' | ||
| interface RemoteUser { | ||
| os: Ec2OS | ||
| name: string | ||
| } | ||
|
|
||
| export class Ec2Connecter implements vscode.Disposable { | ||
| protected ssmClient: SsmClient | ||
| protected ec2Client: Ec2Client | ||
|
|
@@ -198,10 +204,16 @@ export class Ec2Connecter implements vscode.Disposable { | |
| remoteEnv.SessionProcess, | ||
| remoteEnv.hostname, | ||
| remoteEnv.sshPath, | ||
| remoteUser, | ||
| remoteUser.name, | ||
| testSession | ||
| ) | ||
| await startVscodeRemote(remoteEnv.SessionProcess, remoteEnv.hostname, '/', remoteEnv.vscPath, remoteUser) | ||
| await startVscodeRemote( | ||
| remoteEnv.SessionProcess, | ||
| remoteEnv.hostname, | ||
| '/', | ||
| remoteEnv.vscPath, | ||
| remoteUser.name | ||
| ) | ||
| } catch (err) { | ||
| const message = err instanceof SshError ? 'Testing SSH connection to instance failed' : '' | ||
| this.throwConnectionError(message, selection, err as Error) | ||
|
|
@@ -210,7 +222,10 @@ export class Ec2Connecter implements vscode.Disposable { | |
| } | ||
| } | ||
|
|
||
| public async prepareEc2RemoteEnvWithProgress(selection: Ec2Selection, remoteUser: string): Promise<Ec2RemoteEnv> { | ||
| public async prepareEc2RemoteEnvWithProgress( | ||
| selection: Ec2Selection, | ||
| remoteUser: RemoteUser | ||
| ): Promise<Ec2RemoteEnv> { | ||
| const timeout = new Timeout(60000) | ||
| await showMessageWithCancel('AWS: Opening remote connection...', timeout) | ||
| const remoteEnv = await this.prepareEc2RemoteEnv(selection, remoteUser).finally(() => timeout.cancel()) | ||
|
|
@@ -223,7 +238,7 @@ export class Ec2Connecter implements vscode.Disposable { | |
| return ssmSession | ||
| } | ||
|
|
||
| public async prepareEc2RemoteEnv(selection: Ec2Selection, remoteUser: string): Promise<Ec2RemoteEnv> { | ||
| public async prepareEc2RemoteEnv(selection: Ec2Selection, remoteUser: RemoteUser): Promise<Ec2RemoteEnv> { | ||
| const logger = this.configureRemoteConnectionLogger(selection.instanceId) | ||
| const { ssm, vsc, ssh } = (await ensureDependencies()).unwrap() | ||
| const keyPair = await this.configureSshKeys(selection, remoteUser) | ||
|
|
@@ -271,38 +286,74 @@ export class Ec2Connecter implements vscode.Disposable { | |
| return logger | ||
| } | ||
|
|
||
| public async configureSshKeys(selection: Ec2Selection, remoteUser: string): Promise<SshKeyPair> { | ||
| public async configureSshKeys(selection: Ec2Selection, remoteUser: RemoteUser): Promise<SshKeyPair> { | ||
| const keyPair = await SshKeyPair.getSshKeyPair(`aws-ec2-key`, 30000) | ||
| await this.sendSshKeyToInstance(selection, keyPair, remoteUser) | ||
| return keyPair | ||
| } | ||
|
|
||
| public async attemptToCleanKeys( | ||
Hweinstock marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| instanceId: string, | ||
| hintComment: string, | ||
| hostOS: Ec2OS, | ||
| remoteAuthorizedKeysPath: string | ||
| ) { | ||
| try { | ||
| const deleteExistingKeyCommand = getRemoveLinesCommand(hintComment, hostOS, remoteAuthorizedKeysPath) | ||
| await this.sendCommandAndWait(instanceId, deleteExistingKeyCommand) | ||
| } catch (e) { | ||
| getLogger().warn(`ec2: failed to clean keys: %O`, e) | ||
| } | ||
| } | ||
|
|
||
| private async sendCommandAndWait(instanceId: string, command: string) { | ||
| return await this.ssmClient.sendCommandAndWait(instanceId, 'AWS-RunShellScript', { | ||
| commands: [command], | ||
| }) | ||
| } | ||
|
|
||
| public async sendSshKeyToInstance( | ||
| selection: Ec2Selection, | ||
| sshKeyPair: SshKeyPair, | ||
| remoteUser: string | ||
| remoteUser: RemoteUser | ||
| ): Promise<void> { | ||
| const sshPubKey = await sshKeyPair.getPublicKey() | ||
| const hintComment = '#AWSToolkitForVSCode' | ||
|
|
||
| const remoteAuthorizedKeysPaths = `/home/${remoteUser}/.ssh/authorized_keys` | ||
| const command = `echo "${sshPubKey}" > ${remoteAuthorizedKeysPaths}` | ||
| const documentName = 'AWS-RunShellScript' | ||
| const remoteAuthorizedKeysPath = `/home/${remoteUser.name}/.ssh/authorized_keys` | ||
|
|
||
| await this.ssmClient.sendCommandAndWait(selection.instanceId, documentName, { | ||
| commands: [command], | ||
| }) | ||
| const appendStr = (s: string) => `echo "${s}" >> ${remoteAuthorizedKeysPath}` | ||
| const writeKeyCommand = appendStr([sshPubKey.replace('\n', ''), hintComment].join(' ')) | ||
|
|
||
| await this.attemptToCleanKeys(selection.instanceId, hintComment, remoteUser.os, remoteAuthorizedKeysPath) | ||
| await this.sendCommandAndWait(selection.instanceId, writeKeyCommand) | ||
| } | ||
|
|
||
| public async getRemoteUser(instanceId: string) { | ||
| const osName = await this.ssmClient.getTargetPlatformName(instanceId) | ||
| if (osName === 'Amazon Linux') { | ||
| return 'ec2-user' | ||
| public async getRemoteUser(instanceId: string): Promise<RemoteUser> { | ||
| const os = await this.ssmClient.getTargetPlatformName(instanceId) | ||
| if (os === 'Amazon Linux') { | ||
| return { name: 'ec2-user', os } | ||
| } | ||
|
|
||
| if (osName === 'Ubuntu') { | ||
| return 'ubuntu' | ||
| if (os === 'Ubuntu') { | ||
| return { name: 'ubuntu', os } | ||
| } | ||
|
|
||
| throw new ToolkitError(`Unrecognized OS name ${osName} on instance ${instanceId}`, { code: 'UnknownEc2OS' }) | ||
| throw new ToolkitError(`Unrecognized OS name ${os} on instance ${instanceId}`, { code: 'UnknownEc2OS' }) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Generate bash command (as string) to remove lines containing `pattern`. | ||
| * @param pattern pattern for deleted lines. | ||
| * @param filepath filepath (as string) to target with the command. | ||
| * @returns bash command to remove lines from file. | ||
| */ | ||
| export function getRemoveLinesCommand(pattern: string, hostOS: Ec2OS, filepath: string): string { | ||
| // Linux allows not passing extension to -i, whereas macOS requires zero length extension. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also any BSD flavor. Does the '' work on Linux so we can just always do
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I saw it in the docs, and double checked I wasn't able to get it to work on Its annoying :( I also thought about not using the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, another option is to actually pass something like
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I wasn't sure if there would be any problems leaving backup files around. My thinking was to leave as little state/artifacts as possible on the remote machine, but having different commands for different OS is annoying. |
||
| return `sed -i${isLinux(hostOS) ? '' : " ''"} /${pattern}/d ${filepath}` | ||
Hweinstock marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| function isLinux(os: Ec2OS): boolean { | ||
| return os === 'Amazon Linux' || os === 'Ubuntu' | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "type": "Bug Fix", | ||
| "description": "EC2: avoid overwriting authorized_keys file on remote" | ||
| } |

Uh oh!
There was an error while loading. Please reload this page.