mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-29 15:59:54 +00:00
ssh jump hosts - fixes #737
This commit is contained in:
parent
129a7c1a09
commit
7f55d6f1e2
@ -35,6 +35,7 @@ export interface SSHConnection {
|
|||||||
x11?: boolean
|
x11?: boolean
|
||||||
skipBanner?: boolean
|
skipBanner?: boolean
|
||||||
disableDynamicTitle?: boolean
|
disableDynamicTitle?: boolean
|
||||||
|
jumpHost?: string
|
||||||
|
|
||||||
algorithms?: {[t: string]: string[]}
|
algorithms?: {[t: string]: string[]}
|
||||||
}
|
}
|
||||||
@ -80,6 +81,7 @@ export class SSHSession extends BaseSession {
|
|||||||
ssh: Client
|
ssh: Client
|
||||||
forwardedPorts: ForwardedPort[] = []
|
forwardedPorts: ForwardedPort[] = []
|
||||||
logger: Logger
|
logger: Logger
|
||||||
|
jumpStream: any
|
||||||
|
|
||||||
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
||||||
private serviceMessage = new Subject<string>()
|
private serviceMessage = new Subject<string>()
|
||||||
|
@ -70,6 +70,12 @@
|
|||||||
ngb-tab(id='advanced')
|
ngb-tab(id='advanced')
|
||||||
ng-template(ngbTabTitle) Advanced
|
ng-template(ngbTabTitle) Advanced
|
||||||
ng-template(ngbTabContent)
|
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
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title X11 forwarding
|
.title X11 forwarding
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
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 { PasswordStorageService } from '../services/passwordStorage.service'
|
||||||
import { SSHConnection, LoginScript, SSHAlgorithmType } from '../api'
|
import { SSHConnection, LoginScript, SSHAlgorithmType } from '../api'
|
||||||
import { PromptModalComponent } from './promptModal.component'
|
import { PromptModalComponent } from './promptModal.component'
|
||||||
@ -20,6 +20,7 @@ export class EditConnectionModalComponent {
|
|||||||
algorithms: {[id: string]: {[a: string]: boolean}} = {}
|
algorithms: {[id: string]: {[a: string]: boolean}} = {}
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
|
public config: ConfigService,
|
||||||
private modalInstance: NgbActiveModal,
|
private modalInstance: NgbActiveModal,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
|
@ -10,6 +10,7 @@ import { SSHConnection, SSHSession } from '../api'
|
|||||||
import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component'
|
import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
|
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ssh-tab',
|
selector: 'ssh-tab',
|
||||||
@ -20,6 +21,7 @@ import { Subscription } from 'rxjs'
|
|||||||
export class SSHTabComponent extends BaseTerminalTabComponent {
|
export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||||
connection: SSHConnection
|
connection: SSHConnection
|
||||||
session: SSHSession
|
session: SSHSession
|
||||||
|
private sessionStack: SSHSession[] = []
|
||||||
private homeEndSubscription: Subscription
|
private homeEndSubscription: Subscription
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@ -60,19 +62,40 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async initializeSession (): Promise<void> {
|
async setupOneSession (session: SSHSession): Promise<void> {
|
||||||
if (!this.connection) {
|
if (session.connection.jumpHost) {
|
||||||
this.logger.error('No SSH connection info supplied')
|
const jumpConnection = this.config.store.ssh.connections.find(x => x.name === session.connection.jumpHost)
|
||||||
return
|
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.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({
|
const spinner = new Spinner({
|
||||||
text: 'Connecting',
|
text: 'Connecting',
|
||||||
@ -84,7 +107,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
spinner.start()
|
spinner.start()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.ssh.connectSession(this.session, (message: string) => {
|
await this.ssh.connectSession(session, (message: string) => {
|
||||||
spinner.stop(true)
|
spinner.stop(true)
|
||||||
this.write(message + '\r\n')
|
this.write(message + '\r\n')
|
||||||
spinner.start()
|
spinner.start()
|
||||||
@ -95,6 +118,20 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
|
this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
|
||||||
return
|
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()
|
await this.session.start()
|
||||||
this.session.resize(this.size.columns, this.size.rows)
|
this.session.resize(this.size.columns, this.size.rows)
|
||||||
}
|
}
|
||||||
|
@ -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 () => {
|
ssh.on('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => this.zone.run(async () => {
|
||||||
log(colors.bgBlackBright(' ') + ` Keyboard-interactive auth requested: ${name}`)
|
log(colors.bgBlackBright(' ') + ` Keyboard-interactive auth requested: ${name}`)
|
||||||
this.logger.info('Keyboard-interactive auth:', name, instructions, instructionsLang)
|
this.logger.info('Keyboard-interactive auth:', name, instructions, instructionsLang)
|
||||||
@ -211,6 +217,7 @@ export class SSHService {
|
|||||||
},
|
},
|
||||||
hostHash: 'sha256' as any,
|
hostHash: 'sha256' as any,
|
||||||
algorithms: session.connection.algorithms,
|
algorithms: session.connection.algorithms,
|
||||||
|
sock: session.jumpStream,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.toastr.error(e.message)
|
this.toastr.error(e.message)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user