mirror of
https://github.com/Eugeny/tabby.git
synced 2025-10-04 14:04:56 +00:00
allow storing private keys in the vault
This commit is contained in:
@@ -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) {
|
||||
|
@@ -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()')
|
||||
|
@@ -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) {
|
||||
|
Reference in New Issue
Block a user