allow storing private keys in the vault

This commit is contained in:
Eugene Pankov
2021-06-19 21:54:27 +02:00
parent e41fe9f4c0
commit 51f62c9719
17 changed files with 567 additions and 44 deletions

View File

@@ -10,7 +10,7 @@ import stripAnsi from 'strip-ansi'
import socksv5 from 'socksv5'
import { Injector, NgZone } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { HostAppService, Logger, NotificationsService, Platform, PlatformService, wrapPromise } from 'terminus-core'
import { FileProvidersService, HostAppService, Logger, NotificationsService, Platform, PlatformService, wrapPromise } from 'terminus-core'
import { BaseSession } from 'terminus-terminal'
import { Server, Socket, createServer, createConnection } from 'net'
import { Client, ClientChannel, SFTPWrapper } from 'ssh2'
@@ -138,7 +138,8 @@ export class ForwardedPort implements ForwardedPortConfig {
interface AuthMethod {
type: 'none'|'publickey'|'agent'|'password'|'keyboard-interactive'|'hostbased'
path?: string
name?: string
contents?: Buffer
}
export interface SFTPFile {
@@ -279,10 +280,11 @@ export class SSHSession extends BaseSession {
private platform: PlatformService
private notifications: NotificationsService
private zone: NgZone
private fileProviders: FileProvidersService
constructor (
injector: Injector,
public connection: SSHConnection
public connection: SSHConnection,
) {
super()
this.passwordStorage = injector.get(PasswordStorageService)
@@ -291,6 +293,7 @@ export class SSHSession extends BaseSession {
this.platform = injector.get(PlatformService)
this.notifications = injector.get(NotificationsService)
this.zone = injector.get(NgZone)
this.fileProviders = injector.get(FileProvidersService)
this.scripts = connection.scripts ?? []
this.destroyed$.subscribe(() => {
@@ -318,11 +321,14 @@ export class SSHSession extends BaseSession {
this.remainingAuthMethods = [{ type: 'none' }]
if (!this.connection.auth || this.connection.auth === 'publicKey') {
for (const pk of this.connection.privateKeys ?? []) {
if (await fs.exists(pk)) {
try {
this.remainingAuthMethods.push({
type: 'publickey',
path: pk,
name: pk,
contents: await this.fileProviders.retrieveFile(pk),
})
} catch (error) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Could not load private key ${pk}: ${error}`)
}
}
}
@@ -561,14 +567,14 @@ export class SSHSession extends BaseSession {
}
if (method.type === 'publickey') {
try {
const key = await this.loadPrivateKey(method.path!)
const key = await this.loadPrivateKey(method.contents)
return {
type: 'publickey',
username: this.connection.user,
key,
}
} catch (e) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to load private key ${method.path}: ${e}`)
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to load private key ${method.name}: ${e}`)
continue
}
}
@@ -706,23 +712,22 @@ export class SSHSession extends BaseSession {
}
}
async loadPrivateKey (privateKeyPath: string): Promise<string|null> {
if (!privateKeyPath) {
async loadPrivateKey (privateKeyContents?: Buffer): Promise<string|null> {
if (!privateKeyContents) {
const userKeyPath = path.join(process.env.HOME!, '.ssh', 'id_rsa')
if (await fs.exists(userKeyPath)) {
this.emitServiceMessage('Using user\'s default private key')
privateKeyPath = userKeyPath
privateKeyContents = fs.readFile(userKeyPath, { encoding: null })
}
}
if (!privateKeyPath) {
if (!privateKeyContents) {
return null
}
this.emitServiceMessage('Loading private key from ' + colors.bgWhite.blackBright(' ' + privateKeyPath + ' '))
this.emitServiceMessage('Loading private key')
try {
const privateKeyContents = (await fs.readFile(privateKeyPath)).toString()
const parsedKey = await this.parsePrivateKey(privateKeyContents)
const parsedKey = await this.parsePrivateKey(privateKeyContents.toString())
this.activePrivateKey = parsedKey.toString('openssh')
return this.activePrivateKey
} catch (error) {

View File

@@ -91,7 +91,7 @@
.list-group.mb-2
.list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let path of connection.privateKeys')
i.fas.fa-key
.mr-auto {{path}}
.no-wrap.mr-auto {{path}}
button.btn.btn-link((click)='removePrivateKey(path)')
i.fas.fa-trash
button.btn.btn-secondary((click)='addPrivateKey()')

View File

@@ -4,7 +4,7 @@ import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { Observable } from 'rxjs'
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
import { ElectronService, ConfigService, PlatformService } from 'terminus-core'
import { ConfigService, PlatformService, FileProvidersService } from 'terminus-core'
import { PasswordStorageService } from '../services/passwordStorage.service'
import { SSHConnection, LoginScript, ForwardedPortConfig, SSHAlgorithmType, ALGORITHM_BLACKLIST } from '../api'
import { PromptModalComponent } from './promptModal.component'
@@ -28,10 +28,10 @@ export class EditConnectionModalComponent {
constructor (
public config: ConfigService,
private modalInstance: NgbActiveModal,
private electron: ElectronService,
private platform: PlatformService,
private passwordStorage: PasswordStorageService,
private ngbModal: NgbModal,
private fileProviders: FileProvidersService,
) {
for (const k of Object.values(SSHAlgorithmType)) {
const supportedAlg = {
@@ -101,20 +101,12 @@ export class EditConnectionModalComponent {
this.passwordStorage.deletePassword(this.connection)
}
addPrivateKey () {
this.electron.dialog.showOpenDialog(
{
defaultPath: this.connection.privateKeys![0],
title: 'Select private key',
}
).then(result => {
if (!result.canceled) {
this.connection.privateKeys = [
...this.connection.privateKeys!,
...result.filePaths,
]
}
})
async addPrivateKey () {
const ref = await this.fileProviders.selectAndStoreFile(`private key for ${this.connection.name}`)
this.connection.privateKeys = [
...this.connection.privateKeys!,
ref,
]
}
removePrivateKey (path: string) {