Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Store SSH private key on the file system from SSH plugin #352

Merged
merged 5 commits into from
Jul 19, 2019
Merged
Changes from 3 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
124 changes: 75 additions & 49 deletions plugins/ssh-plugin/src/ssh-plugin-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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...'
Expand All @@ -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);
});
Expand All @@ -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> {
Copy link
Contributor

Choose a reason for hiding this comment

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

(): 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 => {
Expand All @@ -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 => {
Copy link
Contributor

Choose a reason for hiding this comment

The 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);
Expand Down