From 7f55d6f1e25c8d90ee88e23042da5367686999e9 Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Sat, 11 Apr 2020 14:26:20 +0200 Subject: [PATCH] ssh jump hosts - fixes #737 --- terminus-ssh/src/api.ts | 2 + .../editConnectionModal.component.pug | 6 ++ .../editConnectionModal.component.ts | 3 +- .../src/components/sshTab.component.ts | 57 +++++++++++++++---- terminus-ssh/src/services/ssh.service.ts | 7 +++ 5 files changed, 64 insertions(+), 11 deletions(-) diff --git a/terminus-ssh/src/api.ts b/terminus-ssh/src/api.ts index d363ac56..4693edf6 100644 --- a/terminus-ssh/src/api.ts +++ b/terminus-ssh/src/api.ts @@ -35,6 +35,7 @@ export interface SSHConnection { x11?: boolean skipBanner?: boolean disableDynamicTitle?: boolean + jumpHost?: string algorithms?: {[t: string]: string[]} } @@ -80,6 +81,7 @@ export class SSHSession extends BaseSession { ssh: Client forwardedPorts: ForwardedPort[] = [] logger: Logger + jumpStream: any get serviceMessage$ (): Observable { return this.serviceMessage } private serviceMessage = new Subject() diff --git a/terminus-ssh/src/components/editConnectionModal.component.pug b/terminus-ssh/src/components/editConnectionModal.component.pug index 7bc6bcc8..26e1f707 100644 --- a/terminus-ssh/src/components/editConnectionModal.component.pug +++ b/terminus-ssh/src/components/editConnectionModal.component.pug @@ -70,6 +70,12 @@ ngb-tab(id='advanced') ng-template(ngbTabTitle) Advanced ng-template(ngbTabContent) + .form-line + .header + .title Jump host + select.form-control([(ngModel)]='connection.jumpHost') + option([value]='x.name', *ngFor='let x of config.store.ssh.connections') {{x.name}} + .form-line .header .title X11 forwarding diff --git a/terminus-ssh/src/components/editConnectionModal.component.ts b/terminus-ssh/src/components/editConnectionModal.component.ts index 821650ab..49d8be3c 100644 --- a/terminus-ssh/src/components/editConnectionModal.component.ts +++ b/terminus-ssh/src/components/editConnectionModal.component.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { Component } from '@angular/core' import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' -import { ElectronService, HostAppService } from 'terminus-core' +import { ElectronService, HostAppService, ConfigService } from 'terminus-core' import { PasswordStorageService } from '../services/passwordStorage.service' import { SSHConnection, LoginScript, SSHAlgorithmType } from '../api' import { PromptModalComponent } from './promptModal.component' @@ -20,6 +20,7 @@ export class EditConnectionModalComponent { algorithms: {[id: string]: {[a: string]: boolean}} = {} constructor ( + public config: ConfigService, private modalInstance: NgbActiveModal, private electron: ElectronService, private hostApp: HostAppService, diff --git a/terminus-ssh/src/components/sshTab.component.ts b/terminus-ssh/src/components/sshTab.component.ts index 775502d4..17a646a0 100644 --- a/terminus-ssh/src/components/sshTab.component.ts +++ b/terminus-ssh/src/components/sshTab.component.ts @@ -10,6 +10,7 @@ import { SSHConnection, SSHSession } from '../api' import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component' import { Subscription } from 'rxjs' + /** @hidden */ @Component({ selector: 'ssh-tab', @@ -20,6 +21,7 @@ import { Subscription } from 'rxjs' export class SSHTabComponent extends BaseTerminalTabComponent { connection: SSHConnection session: SSHSession + private sessionStack: SSHSession[] = [] private homeEndSubscription: Subscription constructor ( @@ -60,19 +62,40 @@ export class SSHTabComponent extends BaseTerminalTabComponent { }) } - async initializeSession (): Promise { - if (!this.connection) { - this.logger.error('No SSH connection info supplied') - return + async setupOneSession (session: SSHSession): Promise { + if (session.connection.jumpHost) { + const jumpConnection = this.config.store.ssh.connections.find(x => x.name === session.connection.jumpHost) + const jumpSession = this.ssh.createSession(jumpConnection) + + await this.setupOneSession(jumpSession) + + jumpSession.destroyed$.subscribe(() => session.destroy()) + + session.jumpStream = await new Promise((resolve, reject) => jumpSession.ssh.forwardOut( + '127.0.0.1', 0, session.connection.host, session.connection.port, + (err, stream) => { + if (err) { + jumpSession.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not set up port forward on ${jumpConnection.name}`) + return reject(err) + } + resolve(stream) + } + )) + + session.jumpStream.on('close', () => { + jumpSession.destroy() + }) + + this.sessionStack.push(session) } - this.session = this.ssh.createSession(this.connection) - this.session.serviceMessage$.subscribe(msg => { + + session.serviceMessage$.subscribe(msg => { this.write('\r\n' + colors.black.bgWhite(' SSH ') + ' ' + msg + '\r\n') - this.session.resize(this.size.columns, this.size.rows) + session.resize(this.size.columns, this.size.rows) }) - this.attachSessionHandlers() - this.write(`Connecting to ${this.connection.host}`) + + this.write('\r\n' + colors.black.bgCyan(' SSH ') + ` Connecting to ${session.connection.host}\r\n`) const spinner = new Spinner({ text: 'Connecting', @@ -84,7 +107,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent { spinner.start() try { - await this.ssh.connectSession(this.session, (message: string) => { + await this.ssh.connectSession(session, (message: string) => { spinner.stop(true) this.write(message + '\r\n') spinner.start() @@ -95,6 +118,20 @@ export class SSHTabComponent extends BaseTerminalTabComponent { this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n') return } + } + + async initializeSession (): Promise { + if (!this.connection) { + this.logger.error('No SSH connection info supplied') + return + } + + this.session = this.ssh.createSession(this.connection) + + await this.setupOneSession(this.session) + + this.attachSessionHandlers() + await this.session.start() this.session.resize(this.size.columns, this.size.rows) } diff --git a/terminus-ssh/src/services/ssh.service.ts b/terminus-ssh/src/services/ssh.service.ts index e47eb33c..1e7446f3 100644 --- a/terminus-ssh/src/services/ssh.service.ts +++ b/terminus-ssh/src/services/ssh.service.ts @@ -151,6 +151,12 @@ export class SSHService { } }) }) + ssh.on('close', () => { + if (session.open) { + session.destroy() + } + }) + ssh.on('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => this.zone.run(async () => { log(colors.bgBlackBright(' ') + ` Keyboard-interactive auth requested: ${name}`) this.logger.info('Keyboard-interactive auth:', name, instructions, instructionsLang) @@ -211,6 +217,7 @@ export class SSHService { }, hostHash: 'sha256' as any, algorithms: session.connection.algorithms, + sock: session.jumpStream, }) } catch (e) { this.toastr.error(e.message)