1
1
mirror of https://github.com/Eugeny/tabby.git synced 2024-12-02 02:23:12 +03:00

ssh jump hosts - fixes #737

This commit is contained in:
Eugene Pankov 2020-04-11 14:26:20 +02:00
parent 129a7c1a09
commit 7f55d6f1e2
5 changed files with 64 additions and 11 deletions

View File

@ -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<string> { return this.serviceMessage }
private serviceMessage = new Subject<string>()

View File

@ -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

View File

@ -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,

View File

@ -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<void> {
if (!this.connection) {
this.logger.error('No SSH connection info supplied')
return
async setupOneSession (session: SSHSession): Promise<void> {
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<void> {
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)
}

View File

@ -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)