Skip to content

Commit

Permalink
Cache and reuse SSH passwords in the current session (#9654)
Browse files Browse the repository at this point in the history
* Cache and reuse SSH passwords in the current session
  • Loading branch information
xisui-MSFT authored Aug 5, 2022
1 parent 853a4f3 commit b885e2c
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 20 deletions.
7 changes: 6 additions & 1 deletion Extension/src/Debugger/configurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,12 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
logger.getOutputChannelLogger().showErrorMessage(localize("vs.code.1.69+.required", "'deploySteps' require VS Code 1.69+."));
return undefined;
}
const deploySucceeded: boolean = await this.deploySteps(config, token);

const deploySucceeded: boolean = await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: localize("running.deploy.steps", "Running deploy steps...")
}, async () => this.deploySteps(config, token));

if (!deploySucceeded || token?.isCancellationRequested) {
return undefined;
}
Expand Down
30 changes: 24 additions & 6 deletions Extension/src/SSH/commandInteractors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@
* ------------------------------------------------------------------------------------------ */

import * as vscode from 'vscode';
import { stripEscapeSequences, isWindows, escapeStringForRegex } from '../common';
import { stripEscapeSequences, isWindows, escapeStringForRegex, ISshHostInfo, getFullHostAddress, extensionContext } from '../common';

/**
* The users that we autofilled their passwords.
* If a user's password is already used and yet we still get the same prompt, we probably got a wrong password.
* Needs to be reset for each command.
*/
export const autoFilledPasswordForUsers: Set<string> = new Set<string>();

export type IDifferingHostConfirmationProvider =
(message: string, cancelToken?: vscode.CancellationToken) => Promise<string | undefined>;
Expand Down Expand Up @@ -208,7 +215,7 @@ function getPasswordPrompt(data: string, details?: IInteractorDataDetails): { us
export class PasswordInteractor implements IInteractor {
static ID = 'password';

constructor(private readonly passwordProvider: IStringProvider) { }
constructor(private readonly host: ISshHostInfo, private readonly passwordProvider: IStringProvider) { }

get id(): string {
return PasswordInteractor.ID;
Expand All @@ -219,12 +226,23 @@ export class PasswordInteractor implements IInteractor {
const pwPrompt: { user?: string; message?: string } | undefined = getPasswordPrompt(data, extraDetails);
if (pwPrompt && typeof pwPrompt.user === 'string') {
result.postAction = 'consume';
const password: string | undefined = await this.passwordProvider(pwPrompt.user, pwPrompt.message, cancelToken);
if (typeof password === 'string') {
result.response = password;
const actualUser: string = pwPrompt.user === '' ? getFullHostAddress(this.host) : pwPrompt.user;
const passwordCacheKey: string = `SSH:${actualUser}`;
const cachedPassword: string | undefined = await extensionContext?.secrets?.get(passwordCacheKey);
if (cachedPassword !== undefined && !autoFilledPasswordForUsers.has(actualUser)) {
autoFilledPasswordForUsers.add(actualUser);
result.response = cachedPassword;
result.isPassword = true;
} else {
result.canceled = true;
const password: string | undefined = await this.passwordProvider(pwPrompt.user, pwPrompt.message, cancelToken);
if (typeof password === 'string') {
await extensionContext?.secrets?.store(passwordCacheKey, password);
autoFilledPasswordForUsers.add(actualUser);
result.response = password;
result.isPassword = true;
} else {
result.canceled = true;
}
}
}

Expand Down
12 changes: 1 addition & 11 deletions Extension/src/SSH/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { ISshHostInfo, ISshLocalForwardInfo, ProcessReturnType } from '../common';
import { getFullHostAddress, getFullHostAddressNoPort, ISshHostInfo, ISshLocalForwardInfo, ProcessReturnType } from '../common';
import { defaultSystemInteractor } from './commandInteractors';
import { runSshTerminalCommandWithLogin } from './sshCommandRunner';

Expand Down Expand Up @@ -86,13 +86,3 @@ function localForwardToArgs(localForward: ISshLocalForwardInfo): string[] {

return ['-L', arg];
}

/** user@host */
function getFullHostAddressNoPort(host: ISshHostInfo): string {
return host.user ? `${host.user}@${host.hostName}` : `${host.hostName}`;
}

function getFullHostAddress(host: ISshHostInfo): string {
const fullHostName: string = getFullHostAddressNoPort(host);
return host.port ? `${fullHostName}:${host.port}` : fullHostName;
}
6 changes: 4 additions & 2 deletions Extension/src/SSH/sshCommandRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
TwoFacInteractor,
ContinueOnInteractor,
ISystemInteractor,
IInteraction
IInteraction,
autoFilledPasswordForUsers
} from './commandInteractors';
import { isWindows, ISshHostInfo, splitLines, stripEscapeSequences, ProcessReturnType } from '../common';
import { getOutputChannelLogger } from '../logger';
Expand Down Expand Up @@ -194,12 +195,13 @@ export async function runSshTerminalCommandWithLogin(
}

if (!showLoginTerminal) {
autoFilledPasswordForUsers.clear();
interactors.push(
new MitmInteractor(),
new FingerprintInteractor(host.hostName, showHostKeyConfirmation),
new PassphraseInteractor(showPassphraseInputBox),
new DifferingHostKeyInteractor(showDifferingHostConfirmation),
new PasswordInteractor(showPasswordInputBox),
new PasswordInteractor(host, showPasswordInputBox),
new TwoFacInteractor(showVerificationCodeInputBox),
new DuoTwoFacInteractor(showVerificationCodeInputBox)
);
Expand Down
10 changes: 10 additions & 0 deletions Extension/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,16 @@ export interface ISshHostInfo {
port?: number | string;
}

/** user@host */
export function getFullHostAddressNoPort(host: ISshHostInfo): string {
return host.user ? `${host.user}@${host.hostName}` : `${host.hostName}`;
}

export function getFullHostAddress(host: ISshHostInfo): string {
const fullHostName: string = getFullHostAddressNoPort(host);
return host.port ? `${fullHostName}:${host.port}` : fullHostName;
}

export interface ISshLocalForwardInfo {
bindAddress?: string;
port?: number | string;
Expand Down

0 comments on commit b885e2c

Please sign in to comment.