33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6+ import * as os from 'os' ;
7+ import * as path from 'path' ;
8+
69interface ClientOptions {
710 host : string ;
11+ gitpodHost : string ;
812 extIpcPort : number ;
913 machineID : string ;
14+ debug : boolean ;
1015}
1116
1217function getClientOptions ( ) : ClientOptions {
1318 const args = process . argv . slice ( 2 ) ;
1419 // %h is in the form of <ws_id>.vss.<gitpod_host>'
1520 // add `https://` prefix since our gitpodHost is actually a url not host
16- const host = 'https://' + args [ 0 ] . split ( '.' ) . splice ( 2 ) . join ( '.' ) ;
21+ const host = args [ 0 ] ;
22+ const gitpodHost = 'https://' + args [ 0 ] . split ( '.' ) . splice ( 2 ) . join ( '.' ) ;
1723 return {
1824 host,
25+ gitpodHost,
1926 extIpcPort : Number . parseInt ( args [ 1 ] , 10 ) ,
2027 machineID : args [ 2 ] ?? '' ,
28+ debug : args [ 3 ] === 'debug' ,
2129 } ;
2230}
2331
@@ -26,26 +34,22 @@ if (!options) {
2634 process . exit ( 1 ) ;
2735}
2836
29- import { NopeLogger } from './logger' ;
30- const logService = new NopeLogger ( ) ;
31-
32- // DO NOT PUSH CHANGES BELOW TO PRODUCTION
33- // import { DebugLogger } from './logger';
34- // const logService = new DebugLogger();
37+ import { NopeLogger , DebugLogger } from './logger' ;
38+ const logService = options . debug ? new DebugLogger ( path . join ( os . tmpdir ( ) , `lssh-${ options . host } .log` ) ) : new NopeLogger ( ) ;
3539
3640import { TelemetryService } from './telemetryService' ;
3741const telemetryService = new TelemetryService (
3842 process . env . SEGMENT_KEY ! ,
3943 options . machineID ,
4044 process . env . EXT_NAME ! ,
4145 process . env . EXT_VERSION ! ,
42- options . host ,
46+ options . gitpodHost ,
4347 logService
4448) ;
4549
4650const flow : SSHUserFlowTelemetry = {
4751 flow : 'local_ssh' ,
48- gitpodHost : options . host ,
52+ gitpodHost : options . gitpodHost ,
4953 workspaceId : '' ,
5054 processId : process . pid ,
5155} ;
@@ -71,7 +75,7 @@ const exitProcess = async (forceExit: boolean, signal?: NodeJS.Signals) => {
7175} ;
7276
7377import { SshClient } from '@microsoft/dev-tunnels-ssh-tcp' ;
74- import { NodeStream , ObjectDisposedError , SshChannelError , SshChannelOpenFailureReason , SshClientCredentials , SshClientSession , SshConnectionError , SshDisconnectReason , SshReconnectError , SshReconnectFailureReason , SshServerSession , SshSessionConfiguration , Stream , WebSocketStream } from '@microsoft/dev-tunnels-ssh' ;
78+ import { NodeStream , ObjectDisposedError , SshChannelError , SshChannelOpenFailureReason , SshClientCredentials , SshClientSession , SshConnectionError , SshDisconnectReason , SshReconnectError , SshReconnectFailureReason , SshServerSession , SshSessionConfiguration , Stream , TraceLevel , WebSocketStream } from '@microsoft/dev-tunnels-ssh' ;
7579import { importKey , importKeyBytes } from '@microsoft/dev-tunnels-ssh-keys' ;
7680import { ExtensionServiceDefinition , GetWorkspaceAuthInfoResponse } from '../proto/typescript/ipc/v1/ipc' ;
7781import { Client , ClientError , Status , createChannel , createClient } from 'nice-grpc' ;
@@ -165,6 +169,7 @@ class WebSocketSSHProxy {
165169 // Seems there's a bug in the ssh library that could hang forever when the stream gets closed
166170 // so the below `await pipePromise` will never return and the node process will never exit.
167171 // So let's just force kill here
172+ pipeSession ?. close ( SshDisconnectReason . byApplication ) ;
168173 setTimeout ( ( ) => {
169174 exitProcess ( true ) ;
170175 } , 50 ) ;
@@ -180,17 +185,22 @@ class WebSocketSSHProxy {
180185 const keys = await importKeyBytes ( getHostKey ( ) ) ;
181186 const config = new SshSessionConfiguration ( ) ;
182187 config . maxClientAuthenticationAttempts = 1 ;
183- const session = new SshServerSession ( config ) ;
184- session . credentials . publicKeys . push ( keys ) ;
188+ const localSession = new SshServerSession ( config ) ;
189+ localSession . credentials . publicKeys . push ( keys ) ;
190+ localSession . trace = ( _ : TraceLevel , eventId : number , msg : string , err ?: Error ) => {
191+ this . logService . trace ( `sshsession [local] eventId[${ eventId } ]` , msg , err ) ;
192+ } ;
185193
194+ let pipeSession : SshClientSession | undefined ;
186195 let pipePromise : Promise < void > | undefined ;
187- session . onAuthenticating ( async ( e ) => {
196+ localSession . onAuthenticating ( async ( e ) => {
188197 this . flow . workspaceId = e . username ?? '' ;
189198 this . sendUserStatusFlow ( 'connecting' ) ;
190199 e . authenticationPromise = this . authenticateClient ( e . username ?? '' )
191- . then ( async pipeSession => {
200+ . then ( async session => {
192201 this . sendUserStatusFlow ( 'connected' ) ;
193- pipePromise = session . pipe ( pipeSession ) ;
202+ pipeSession = session ;
203+ pipePromise = localSession . pipe ( pipeSession ) ;
194204 return { } ;
195205 } ) . catch ( async err => {
196206 this . logService . error ( 'failed to authenticate proxy with username: ' + e . username ?? '' , err ) ;
@@ -209,21 +219,21 @@ class WebSocketSSHProxy {
209219 // Await a few seconds to delay showing ssh extension error modal dialog
210220 await timeout ( 5000 ) ;
211221
212- await session . close ( SshDisconnectReason . byApplication , err . toString ( ) , err instanceof Error ? err : undefined ) ;
222+ await localSession . close ( SshDisconnectReason . byApplication , err . toString ( ) , err instanceof Error ? err : undefined ) ;
213223 return null ;
214224 } ) ;
215225 } ) ;
216226 try {
217- await session . connect ( new NodeStream ( sshStream ) ) ;
227+ await localSession . connect ( new NodeStream ( sshStream ) ) ;
218228 await pipePromise ;
219229 } catch ( e ) {
220- if ( session . isClosed ) {
230+ if ( localSession . isClosed ) {
221231 return ;
222232 }
223233 e = fixSSHErrorName ( e ) ;
224234 this . logService . error ( e , 'failed to connect to client' ) ;
225235 this . sendErrorReport ( this . flow , e , 'failed to connect to client' ) ;
226- await session . close ( SshDisconnectReason . byApplication , e . toString ( ) , e instanceof Error ? e : undefined ) ;
236+ await localSession . close ( SshDisconnectReason . byApplication , e . toString ( ) , e instanceof Error ? e : undefined ) ;
227237 }
228238 }
229239
@@ -325,6 +335,9 @@ class WebSocketSSHProxy {
325335 const config = new SshSessionConfiguration ( ) ;
326336 const session = new SshClientSession ( config ) ;
327337 session . onAuthenticating ( ( e ) => e . authenticationPromise = Promise . resolve ( { } ) ) ;
338+ session . trace = ( _ : TraceLevel , eventId : number , msg : string , err ?: Error ) => {
339+ this . logService . trace ( `sshsession [websocket] eventId[${ eventId } ]` , msg , err ) ;
340+ } ;
328341
329342 await session . connect ( stream ) ;
330343
@@ -340,7 +353,7 @@ class WebSocketSSHProxy {
340353
341354 async retryGetWorkspaceInfo ( username : string ) {
342355 return retry ( async ( ) => {
343- return this . extensionIpc . getWorkspaceAuthInfo ( { workspaceId : username , gitpodHost : this . options . host } ) . catch ( e => {
356+ return this . extensionIpc . getWorkspaceAuthInfo ( { workspaceId : username , gitpodHost : this . options . gitpodHost } ) . catch ( e => {
344357 let failureCode = 'FailedToGetAuthInfo' ;
345358 if ( e instanceof ClientError ) {
346359 if ( e . code === Status . FAILED_PRECONDITION && e . message . includes ( 'gitpod host mismatch' ) ) {
@@ -377,7 +390,7 @@ const metricsReporter = new LocalSSHMetricsReporter(logService);
377390const proxy = new WebSocketSSHProxy ( options , telemetryService , metricsReporter , logService , flow ) ;
378391proxy . start ( ) . catch ( e => {
379392 const err = new WrapError ( 'Uncaught exception on start method' , e ) ;
380- telemetryService . sendTelemetryException ( err , { gitpodHost : options . host } ) ;
393+ telemetryService . sendTelemetryException ( err , { gitpodHost : options . gitpodHost } ) ;
381394} ) ;
382395
383396function fixSSHErrorName ( err : any ) {
0 commit comments