Skip to content

Commit afc674e

Browse files
Merge master into feature/emr
2 parents 02a7105 + 2b60a35 commit afc674e

File tree

3 files changed

+56
-26
lines changed

3 files changed

+56
-26
lines changed

packages/core/src/awsService/ec2/sshKeyPair.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
import { fs } from '../../shared'
66
import { chmodSync } from 'fs'
77
import { ToolkitError } from '../../shared/errors'
8-
import { ChildProcess } from '../../shared/utilities/childProcess'
8+
import { tryRun } from '../../shared/utilities/pathFind'
99
import { Timeout } from '../../shared/utilities/timeoutUtils'
10+
import { findAsync } from '../../shared/utilities/collectionUtils'
11+
12+
type sshKeyType = 'rsa' | 'ed25519'
1013

1114
export class SshKeyPair {
1215
private publicKeyPath: string
@@ -35,13 +38,25 @@ export class SshKeyPair {
3538
}
3639

3740
public static async generateSshKeyPair(keyPath: string): Promise<void> {
38-
const process = new ChildProcess(`ssh-keygen`, ['-t', 'ed25519', '-N', '', '-q', '-f', keyPath])
39-
const result = await process.run()
40-
if (result.exitCode !== 0) {
41-
throw new ToolkitError('ec2: Failed to generate ssh key', { details: { stdout: result.stdout } })
41+
const keyGenerated = await this.tryKeyTypes(keyPath, ['ed25519', 'rsa'])
42+
if (!keyGenerated) {
43+
throw new ToolkitError('ec2: Unable to generate ssh key pair')
4244
}
4345
chmodSync(keyPath, 0o600)
4446
}
47+
/**
48+
* Attempts to generate an ssh key pair. Returns true if successful, false otherwise.
49+
* @param keyPath where to generate key.
50+
* @param keyType type of key to generate.
51+
*/
52+
public static async tryKeyGen(keyPath: string, keyType: sshKeyType): Promise<boolean> {
53+
return !(await tryRun('ssh-keygen', ['-t', keyType, '-N', '', '-q', '-f', keyPath], 'yes', 'unknown key type'))
54+
}
55+
56+
public static async tryKeyTypes(keyPath: string, keyTypes: sshKeyType[]): Promise<boolean> {
57+
const keyTypeUsed = await findAsync(keyTypes, async (type) => await this.tryKeyGen(keyPath, type))
58+
return keyTypeUsed !== undefined
59+
}
4560

4661
public getPublicKeyPath(): string {
4762
return this.publicKeyPath

packages/core/src/test/awsService/ec2/sshKeyPair.test.ts

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,36 @@ describe('SshKeyUtility', async function () {
3838
sinon.restore()
3939
})
4040

41-
describe('generateSshKeys', async function () {
42-
it('generates key in target file', async function () {
43-
const contents = await vscode.workspace.fs.readFile(vscode.Uri.file(keyPath))
44-
assert.notStrictEqual(contents.length, 0)
45-
})
46-
47-
it('generates unique key each time', async function () {
48-
const beforeContent = await vscode.workspace.fs.readFile(vscode.Uri.file(keyPath))
49-
keyPair = await SshKeyPair.getSshKeyPair(keyPath, 30000)
50-
const afterContent = await vscode.workspace.fs.readFile(vscode.Uri.file(keyPath))
51-
assert.notStrictEqual(beforeContent, afterContent)
52-
})
53-
54-
it('uses ed25519 algorithm to generate the keys', async function () {
55-
const process = new ChildProcess(`ssh-keygen`, ['-vvv', '-l', '-f', keyPath])
56-
const result = await process.run()
57-
// Check private key header for algorithm name
58-
assert.strictEqual(result.stdout.includes('[ED25519 256]'), true)
59-
})
41+
it('generates key in target file', async function () {
42+
const contents = await fs.readFile(vscode.Uri.file(keyPath))
43+
assert.notStrictEqual(contents.length, 0)
44+
})
45+
46+
it('generates unique key each time', async function () {
47+
const beforeContent = await fs.readFile(vscode.Uri.file(keyPath))
48+
keyPair = await SshKeyPair.getSshKeyPair(keyPath, 30000)
49+
const afterContent = await fs.readFile(vscode.Uri.file(keyPath))
50+
assert.notStrictEqual(beforeContent, afterContent)
51+
})
52+
53+
it('defaults to ed25519 key type', async function () {
54+
const process = new ChildProcess(`ssh-keygen`, ['-vvv', '-l', '-f', keyPath])
55+
const result = await process.run()
56+
// Check private key header for algorithm name
57+
assert.strictEqual(result.stdout.includes('[ED25519 256]'), true)
58+
})
59+
60+
it('falls back on rsa if ed25519 not available', async function () {
61+
await keyPair.delete()
62+
const stub = sinon.stub(SshKeyPair, 'tryKeyGen')
63+
stub.onFirstCall().resolves(false)
64+
stub.callThrough()
65+
keyPair = await SshKeyPair.getSshKeyPair(keyPath, 30000)
66+
const process = new ChildProcess(`ssh-keygen`, ['-vvv', '-l', '-f', keyPath])
67+
const result = await process.run()
68+
// Check private key header for algorithm name
69+
assert.strictEqual(result.stdout.includes('[RSA'), true)
70+
stub.restore()
6071
})
6172

6273
it('properly names the public key', function () {
@@ -70,10 +81,10 @@ describe('SshKeyUtility', async function () {
7081

7182
it('does overwrite existing keys on get call', async function () {
7283
const generateStub = sinon.spy(SshKeyPair, 'generateSshKeyPair')
73-
const keyBefore = await vscode.workspace.fs.readFile(vscode.Uri.file(keyPath))
84+
const keyBefore = await fs.readFile(vscode.Uri.file(keyPath))
7485
keyPair = await SshKeyPair.getSshKeyPair(keyPath, 30000)
7586

76-
const keyAfter = await vscode.workspace.fs.readFile(vscode.Uri.file(keyPath))
87+
const keyAfter = await fs.readFile(vscode.Uri.file(keyPath))
7788
sinon.assert.calledOnce(generateStub)
7889

7990
assert.notStrictEqual(keyBefore, keyAfter)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "EC2 connect: default to ed25519, but fall back on rsa if unsupported"
4+
}

0 commit comments

Comments
 (0)