1
0
mirror of https://github.com/Eugeny/tabby.git synced 2025-06-21 11:59:53 +00:00

bump russh for jump host fix - fixes

This commit is contained in:
Eugene 2025-01-08 18:37:56 +01:00
parent c43ee28a0c
commit ddbb2feb9c
No known key found for this signature in database
GPG Key ID: 5896FCBBDD1CF4F4
2 changed files with 38 additions and 17 deletions
app
tabby-ssh/src/session

@ -30,7 +30,7 @@
"native-process-working-directory": "^1.0.2", "native-process-working-directory": "^1.0.2",
"npm": "6", "npm": "6",
"rxjs": "^7.5.7", "rxjs": "^7.5.7",
"russh": "0.1.9", "russh": "0.1.10",
"source-map-support": "^0.5.20", "source-map-support": "^0.5.20",
"v8-compile-cache": "^2.3.0", "v8-compile-cache": "^2.3.0",
"yargs": "^17.7.2" "yargs": "^17.7.2"

@ -87,7 +87,7 @@ export class SSHSession {
ssh: russh.SSHClient|russh.AuthenticatedSSHClient ssh: russh.SSHClient|russh.AuthenticatedSSHClient
sftp?: russh.SFTP sftp?: russh.SFTP
forwardedPorts: ForwardedPort[] = [] forwardedPorts: ForwardedPort[] = []
jumpChannel: russh.Channel|null = null jumpChannel: russh.NewChannel|null = null
savedPassword?: string savedPassword?: string
get serviceMessage$ (): Observable<string> { return this.serviceMessage } get serviceMessage$ (): Observable<string> { return this.serviceMessage }
get keyboardInteractivePrompt$ (): Observable<KeyboardInteractivePrompt> { return this.keyboardInteractivePrompt } get keyboardInteractivePrompt$ (): Observable<KeyboardInteractivePrompt> { return this.keyboardInteractivePrompt }
@ -244,7 +244,7 @@ export class SSHSession {
throw new Error('Cannot open SFTP session before auth') throw new Error('Cannot open SFTP session before auth')
} }
if (!this.sftp) { if (!this.sftp) {
this.sftp = await this.ssh.openSFTPChannel() this.sftp = await this.ssh.activateSFTP(await this.ssh.openSessionChannel())
} }
return new SFTPSession(this.sftp, this.injector) return new SFTPSession(this.sftp, this.injector)
} }
@ -368,22 +368,31 @@ export class SSHSession {
this.ssh.tcpChannelOpen$.subscribe(async event => { this.ssh.tcpChannelOpen$.subscribe(async event => {
this.logger.info(`Incoming forwarded connection: ${event.clientAddress}:${event.clientPort} -> ${event.targetAddress}:${event.targetPort}`) this.logger.info(`Incoming forwarded connection: ${event.clientAddress}:${event.clientPort} -> ${event.targetAddress}:${event.targetPort}`)
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open agent channel before auth')
}
const channel = await this.ssh.activateChannel(event.channel)
const forward = this.forwardedPorts.find(x => x.port === event.targetPort && x.host === event.targetAddress) const forward = this.forwardedPorts.find(x => x.port === event.targetPort && x.host === event.targetAddress)
if (!forward) { if (!forward) {
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Rejected incoming forwarded connection for unrecognized port ${event.targetAddress}:${event.targetPort}`) this.emitServiceMessage(colors.bgRed.black(' X ') + ` Rejected incoming forwarded connection for unrecognized port ${event.targetAddress}:${event.targetPort}`)
channel.close()
return return
} }
const socket = new Socket() const socket = new Socket()
socket.connect(forward.targetPort, forward.targetAddress) socket.connect(forward.targetPort, forward.targetAddress)
socket.on('error', e => { socket.on('error', e => {
// eslint-disable-next-line @typescript-eslint/no-base-to-string // eslint-disable-next-line @typescript-eslint/no-base-to-string
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not forward the remote connection to ${forward.targetAddress}:${forward.targetPort}: ${e}`) this.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not forward the remote connection to ${forward.targetAddress}:${forward.targetPort}: ${e}`)
event.channel.close() channel.close()
}) })
event.channel.data$.subscribe(data => socket.write(data)) channel.data$.subscribe(data => socket.write(data))
socket.on('data', data => event.channel.write(Uint8Array.from(data))) socket.on('data', data => channel.write(Uint8Array.from(data)))
event.channel.closed$.subscribe(() => socket.destroy()) channel.closed$.subscribe(() => socket.destroy())
socket.on('close', () => event.channel.close()) socket.on('close', () => channel.close())
socket.on('connect', () => { socket.on('connect', () => {
this.logger.info('Connection forwarded') this.logger.info('Connection forwarded')
}) })
@ -394,22 +403,28 @@ export class SSHSession {
const displaySpec = (this.config.store.ssh.x11Display || process.env.DISPLAY) ?? 'localhost:0' const displaySpec = (this.config.store.ssh.x11Display || process.env.DISPLAY) ?? 'localhost:0'
this.logger.debug(`Trying display ${displaySpec}`) this.logger.debug(`Trying display ${displaySpec}`)
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open agent channel before auth')
}
const channel = await this.ssh.activateChannel(event.channel)
const socket = new X11Socket() const socket = new X11Socket()
try { try {
const x11Stream = await socket.connect(displaySpec) const x11Stream = await socket.connect(displaySpec)
this.logger.info('Connection forwarded') this.logger.info('Connection forwarded')
event.channel.data$.subscribe(data => { channel.data$.subscribe(data => {
x11Stream.write(data) x11Stream.write(data)
}) })
x11Stream.on('data', data => { x11Stream.on('data', data => {
event.channel.write(Uint8Array.from(data)) channel.write(Uint8Array.from(data))
}) })
event.channel.closed$.subscribe(() => { channel.closed$.subscribe(() => {
socket.destroy() socket.destroy()
}) })
x11Stream.on('close', () => { x11Stream.on('close', () => {
event.channel.close() channel.close()
}) })
} catch (e) { } catch (e) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string // eslint-disable-next-line @typescript-eslint/no-base-to-string
@ -420,11 +435,17 @@ export class SSHSession {
this.emitServiceMessage(' * VcXsrv: https://sourceforge.net/projects/vcxsrv/') this.emitServiceMessage(' * VcXsrv: https://sourceforge.net/projects/vcxsrv/')
this.emitServiceMessage(' * Xming: https://sourceforge.net/projects/xming/') this.emitServiceMessage(' * Xming: https://sourceforge.net/projects/xming/')
} }
event.channel.close() channel.close()
} }
}) })
this.ssh.agentChannelOpen$.subscribe(async channel => { this.ssh.agentChannelOpen$.subscribe(async newChannel => {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open agent channel before auth')
}
const channel = await this.ssh.activateChannel(newChannel)
const spec = await this.getAgentConnectionSpec() const spec = await this.getAgentConnectionSpec()
if (!spec) { if (!spec) {
await channel.close() await channel.close()
@ -622,7 +643,7 @@ export class SSHSession {
reject() reject()
return return
} }
const channel = await this.ssh.openTCPForwardChannel({ const channel = await this.ssh.activateChannel(await this.ssh.openTCPForwardChannel({
addressToConnectTo: targetAddress, addressToConnectTo: targetAddress,
portToConnectTo: targetPort, portToConnectTo: targetPort,
originatorAddress: sourceAddress ?? '127.0.0.1', originatorAddress: sourceAddress ?? '127.0.0.1',
@ -631,7 +652,7 @@ export class SSHSession {
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`) this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`)
reject() reject()
throw err throw err
}) }))
const socket = accept() const socket = accept()
channel.data$.subscribe(data => socket.write(data)) channel.data$.subscribe(data => socket.write(data))
socket.on('data', data => channel.write(Uint8Array.from(data))) socket.on('data', data => channel.write(Uint8Array.from(data)))
@ -688,7 +709,7 @@ export class SSHSession {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) { if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open shell channel before auth') throw new Error('Cannot open shell channel before auth')
} }
const ch = await this.ssh.openSessionChannel() const ch = await this.ssh.activateChannel(await this.ssh.openSessionChannel())
await ch.requestPTY('xterm-256color', { await ch.requestPTY('xterm-256color', {
columns: 80, columns: 80,
rows: 24, rows: 24,