This repository has been archived by the owner on Apr 4, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 111
Store SSH private key on the file system from SSH plugin #352
Merged
Merged
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
7b58cb1
Store SSH private key on the file system
vinokurig 3ce1956
fixup! Store SSH private key on the file system
vinokurig 0b95223
fixup! Store SSH private key on the file system
vinokurig 5a9da8d
fixup! Store SSH private key on the file system
vinokurig 0fa1401
fixup! Store SSH private key on the file system
vinokurig File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,11 +9,17 @@ | |
**********************************************************************/ | ||
|
||
import * as theia from '@theia/plugin'; | ||
import { RemoteSshKeyManager, SshKeyManager, CheService } from './node/ssh-key-manager'; | ||
import { RemoteSshKeyManager, SshKeyManager } from './node/ssh-key-manager'; | ||
import { che as cheApi } from '@eclipse-che/api'; | ||
import * as fs from 'fs'; | ||
import * as os from 'os'; | ||
|
||
export async function start() { | ||
const sshKeyManager = new RemoteSshKeyManager(); | ||
const GENERATE_FOR_HOST: theia.CommandDescription = { | ||
id: 'ssh:generate_for_host', | ||
label: 'SSH: generate key pair for particular host...' | ||
}; | ||
const GENERATE: theia.CommandDescription = { | ||
id: 'ssh:generate', | ||
label: 'SSH: generate key pair...' | ||
|
@@ -31,6 +37,9 @@ export async function start() { | |
label: 'SSH: view public key...' | ||
}; | ||
|
||
theia.commands.registerCommand(GENERATE_FOR_HOST, () => { | ||
generateKeyPairForHost(sshKeyManager); | ||
}); | ||
theia.commands.registerCommand(GENERATE, () => { | ||
generateKeyPair(sshKeyManager); | ||
}); | ||
|
@@ -45,76 +54,93 @@ export async function start() { | |
}); | ||
} | ||
|
||
const getSshServiceName = async function (): Promise<string> { | ||
/** | ||
* Known Che services which can use the SSH key pairs. | ||
*/ | ||
const services: CheService[] = [ | ||
{ | ||
name: 'vcs', | ||
displayName: 'VCS', | ||
description: 'SSH keys used by Che VCS plugins' | ||
}, | ||
{ | ||
name: 'machine', | ||
displayName: 'Workspace Containers', | ||
description: 'SSH keys injected into all Workspace Containers' | ||
} | ||
]; | ||
const getHostName = async function (): Promise<string> { | ||
const hostName = await theia.window.showInputBox({ placeHolder: 'Please provide a Host name e.g. github.com' }); | ||
return hostName ? hostName : ''; | ||
}; | ||
|
||
const option: theia.QuickPickOptions = { | ||
matchOnDescription: true, | ||
matchOnDetail: true, | ||
canPickMany: false, | ||
placeHolder: 'Select object:' | ||
}; | ||
const sshServiceValue = await theia.window.showQuickPick<theia.QuickPickItem>(services.map(service => | ||
({ | ||
label: service.displayName, | ||
description: service.description, | ||
detail: service.name, | ||
name: service.name | ||
})), option); | ||
if (sshServiceValue) { | ||
return Promise.resolve(sshServiceValue.label); | ||
const updateConfig = function (hostName: string): void { | ||
const sshDir = os.homedir() + '/.ssh'; | ||
vinokurig marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const configFile = sshDir + '/config'; | ||
if (!fs.existsSync(configFile)) { | ||
vinokurig marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!fs.existsSync(sshDir)) { | ||
fs.mkdirSync(sshDir); | ||
} | ||
fs.appendFileSync(configFile, ''); | ||
fs.chmodSync(configFile, '644'); | ||
} | ||
const keyConfig = `\nHost ${hostName.startsWith('default-') ? '*' : hostName}\nIdentityFile ${sshDir}/${hostName.replace('.', '_')}\n`; | ||
const configContent = fs.readFileSync(configFile).toString(); | ||
if (configContent.indexOf(keyConfig) >= 0) { | ||
const newConfigContent = configContent.replace(keyConfig, ''); | ||
fs.writeFileSync(configFile, newConfigContent); | ||
vinokurig marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} else { | ||
return Promise.resolve(''); | ||
fs.appendFileSync(configFile, keyConfig); | ||
} | ||
}; | ||
|
||
const writeKey = function (name: string, key: string): void { | ||
const keyFile = os.homedir() + '/.ssh/' + name.replace('.', '_'); | ||
vinokurig marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fs.appendFileSync(keyFile, key); | ||
fs.chmodSync(keyFile, '600'); | ||
}; | ||
|
||
const generateKeyPair = async function (sshkeyManager: SshKeyManager): Promise<void> { | ||
const keyName = await theia.window.showInputBox({ placeHolder: 'Please provide a key pair name' }); | ||
const key = await sshkeyManager.generate(await getSshServiceName(), keyName ? keyName : ''); | ||
const keyName = `default-${Date.now()}`; | ||
const key = await sshkeyManager.generate('vcs', keyName); | ||
updateConfig(keyName); | ||
writeKey(keyName, key.privateKey); | ||
const viewAction = 'View'; | ||
const action = await theia.window.showInformationMessage('Do you want to view the generated private key?', viewAction); | ||
const action = await theia.window.showInformationMessage('Key pair successfully generated, do you want to view the public key?', viewAction); | ||
if (action === viewAction && key.privateKey) { | ||
theia.workspace.openTextDocument({ content: key.privateKey }); | ||
const document = await theia.workspace.openTextDocument({ content: key.publicKey }); | ||
await theia.window.showTextDocument(document); | ||
} | ||
}; | ||
|
||
const generateKeyPairForHost = async function (sshkeyManager: SshKeyManager): Promise<void> { | ||
const hostName = await getHostName(); | ||
const key = await sshkeyManager.generate('vcs', hostName); | ||
updateConfig(hostName); | ||
writeKey(hostName, key.privateKey); | ||
const viewAction = 'View'; | ||
const action = await theia.window.showInformationMessage(`Key pair for ${hostName} successfully generated, do you want to view the public key?`, viewAction); | ||
if (action === viewAction && key.privateKey) { | ||
const document = await theia.workspace.openTextDocument({ content: key.publicKey }); | ||
await theia.window.showTextDocument(document); | ||
} | ||
}; | ||
|
||
const createKeyPair = async function (sshkeyManager: SshKeyManager): Promise<void> { | ||
const keyName = await theia.window.showInputBox({ placeHolder: 'Please provide a key pair name' }); | ||
const hostName = await getHostName(); | ||
const publicKey = await theia.window.showInputBox({ placeHolder: 'Enter public key' }); | ||
const privateKey = await theia.window.showInputBox({ placeHolder: 'Enter private key' }); | ||
|
||
await sshkeyManager | ||
.create({ name: keyName ? keyName : '', service: await getSshServiceName(), publicKey: publicKey }) | ||
.then(() => { | ||
theia.window.showInformationMessage('Key "' + `${keyName}` + '" successfully created'); | ||
.create({ name: hostName, service: 'vcs', publicKey: publicKey, privateKey }) | ||
.then(async () => { | ||
theia.window.showInformationMessage('Key "' + `${hostName}` + '" successfully created'); | ||
vinokurig marked this conversation as resolved.
Show resolved
Hide resolved
|
||
updateConfig(hostName); | ||
writeKey(hostName, privateKey); | ||
}) | ||
.catch(error => { | ||
theia.window.showErrorMessage(error); | ||
}); | ||
}; | ||
|
||
const deleteKeyPair = async function (sshkeyManager: SshKeyManager): Promise<void> { | ||
const sshServiceName = await getSshServiceName(); | ||
const keys: cheApi.ssh.SshPair[] = await sshkeyManager.getAll(sshServiceName); | ||
const keys: cheApi.ssh.SshPair[] = await sshkeyManager.getAll('vcs'); | ||
const keyResp = await theia.window.showQuickPick<theia.QuickPickItem>(keys.map(key => | ||
({ label: key.name ? key.name : '' })), {}); | ||
const keyName = keyResp ? keyResp.label : ''; | ||
await sshkeyManager | ||
.delete(sshServiceName, keyName) | ||
.delete('vcs', keyName) | ||
.then(() => { | ||
const keyFile = os.homedir() + '/.ssh/' + keyName.replace('.', '_'); | ||
vinokurig marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (fs.existsSync(keyFile)) { | ||
vinokurig marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fs.unlinkSync(keyFile); | ||
updateConfig(keyName); | ||
} | ||
theia.window.showInformationMessage('Key "' + `${keyName}` + '" successfully deleted'); | ||
}) | ||
.catch(error => { | ||
|
@@ -123,15 +149,15 @@ const deleteKeyPair = async function (sshkeyManager: SshKeyManager): Promise<voi | |
}; | ||
|
||
const viewPublicKey = async function (sshkeyManager: SshKeyManager): Promise<void> { | ||
const sshServiceName = await getSshServiceName(); | ||
const keys: cheApi.ssh.SshPair[] = await sshkeyManager.getAll(sshServiceName); | ||
const keys: cheApi.ssh.SshPair[] = await sshkeyManager.getAll('vcs'); | ||
const keyResp = await theia.window.showQuickPick<theia.QuickPickItem>(keys.map(key => | ||
({ label: key.name ? key.name : '' })), {}); | ||
const keyName = keyResp ? keyResp.label : ''; | ||
await sshkeyManager | ||
.get(sshServiceName, keyName) | ||
.then(key => { | ||
theia.workspace.openTextDocument({ content: key.publicKey }); | ||
.get('vcs', keyName) | ||
.then(async key => { | ||
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. why do we use a promise there with then and not with await ? const key = await sshkeyManager.get(sshServiceName, keyName)
const document = await theia.workspace.openTextDocument({ content: key.publicKey });
theia.window.showTextDocument(document); |
||
const document = await theia.workspace.openTextDocument({ content: key.publicKey }); | ||
theia.window.showTextDocument(document); | ||
}) | ||
.catch(error => { | ||
theia.window.showErrorMessage(error); | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(): Promise<string> => {
?