@@ -15,7 +15,7 @@ import * as fs from 'fs';
15
15
import * as http from 'http' ;
16
16
import * as net from 'net' ;
17
17
import * as crypto from 'crypto' ;
18
- import { Client as sshClient , OpenSSHAgent , utils as sshUtils } from 'ssh2' ;
18
+ import { utils as sshUtils } from 'ssh2' ;
19
19
import { ParsedKey } from 'ssh2-streams' ;
20
20
import * as tmp from 'tmp' ;
21
21
import * as path from 'path' ;
@@ -25,12 +25,9 @@ import { Disposable } from './common/dispose';
25
25
import { withServerApi } from './internalApi' ;
26
26
import TelemetryReporter from './telemetryReporter' ;
27
27
import { addHostToHostFile , checkNewHostInHostkeys } from './ssh/hostfile' ;
28
- import { DEFAULT_IDENTITY_FILES } from './ssh/identityFiles' ;
29
28
import { HeartbeatManager } from './heartbeat' ;
30
29
import { getGitpodVersion , GitpodVersion , isFeatureSupported , isOauthInspectSupported , ScopeFeature } from './featureSupport' ;
31
30
import SSHConfiguration from './ssh/sshConfig' ;
32
- import { isWindows } from './common/platform' ;
33
- import { untildify } from './common/files' ;
34
31
import { ExperimentalSettings , isUserOverrideSetting } from './experiments' ;
35
32
import { ISyncExtension , NoSettingsSyncSession , NoSyncStoreError , parseSyncData , SettingsSync , SyncResource } from './settingsSync' ;
36
33
import { retry } from './common/async' ;
@@ -39,6 +36,8 @@ import { NotificationService } from './notification';
39
36
import { UserFlowTelemetry } from './common/telemetry' ;
40
37
import { GitpodPublicApi } from './publicApi' ;
41
38
import { SSHKey } from '@gitpod/public-api/lib/gitpod/experimental/v1/user_pb' ;
39
+ import { getAgentSock , SSHError , testSSHConnection } from './sshTestConnection' ;
40
+ import { gatherIdentityFiles } from './ssh/identityFiles' ;
42
41
43
42
interface SSHConnectionParams {
44
43
workspaceId : string ;
@@ -100,15 +99,6 @@ class LocalAppError extends Error {
100
99
}
101
100
}
102
101
103
- class SSHError extends Error {
104
- constructor ( cause : Error ) {
105
- super ( ) ;
106
- this . name = cause . name ;
107
- this . message = cause . message ;
108
- this . stack = cause . stack ;
109
- }
110
- }
111
-
112
102
class NoRunningInstanceError extends Error {
113
103
constructor ( readonly workspaceId : string ) {
114
104
super ( `Failed to connect to ${ workspaceId } Gitpod workspace, workspace not running` ) ;
@@ -453,86 +443,6 @@ export default class RemoteConnector extends Disposable {
453
443
}
454
444
}
455
445
456
- // From https://github.com/openssh/openssh-portable/blob/acb2059febaddd71ee06c2ebf63dcf211d9ab9f2/sshconnect2.c#L1689-L1690
457
- private async getIdentityKeys ( hostConfig : Record < string , string > ) {
458
- const identityFiles : string [ ] = ( ( hostConfig [ 'IdentityFile' ] as unknown as string [ ] ) || [ ] ) . map ( untildify ) . map ( i => i . replace ( / \. p u b $ / , '' ) ) ;
459
- if ( identityFiles . length === 0 ) {
460
- identityFiles . push ( ...DEFAULT_IDENTITY_FILES ) ;
461
- }
462
-
463
- const identityFileContentsResult = await Promise . allSettled ( identityFiles . map ( async path => fs . promises . readFile ( path + '.pub' ) ) ) ;
464
- const fileKeys = identityFileContentsResult . map ( ( result , i ) => {
465
- if ( result . status === 'rejected' ) {
466
- return undefined ;
467
- }
468
-
469
- const parsedResult = sshUtils . parseKey ( result . value ) ;
470
- if ( parsedResult instanceof Error || ! parsedResult ) {
471
- this . logger . error ( `Error while parsing SSH public key ${ identityFiles [ i ] + '.pub' } :` , parsedResult ) ;
472
- return undefined ;
473
- }
474
-
475
- const parsedKey = Array . isArray ( parsedResult ) ? parsedResult [ 0 ] : parsedResult ;
476
- const fingerprint = crypto . createHash ( 'sha256' ) . update ( parsedKey . getPublicSSH ( ) ) . digest ( 'base64' ) ;
477
-
478
- return {
479
- filename : identityFiles [ i ] ,
480
- parsedKey,
481
- fingerprint
482
- } ;
483
- } ) . filter ( < T > ( v : T | undefined ) : v is T => ! ! v ) ;
484
-
485
- let sshAgentParsedKeys : ParsedKey [ ] = [ ] ;
486
- try {
487
- let sshAgentSock = isWindows ? '\\\\.\\pipe\\openssh-ssh-agent' : ( hostConfig [ 'IdentityAgent' ] || process . env [ 'SSH_AUTH_SOCK' ] ) ;
488
- if ( ! sshAgentSock ) {
489
- throw new Error ( `SSH_AUTH_SOCK environment variable not defined` ) ;
490
- }
491
- sshAgentSock = untildify ( sshAgentSock ) ;
492
-
493
- sshAgentParsedKeys = await new Promise < ParsedKey [ ] > ( ( resolve , reject ) => {
494
- const sshAgent = new OpenSSHAgent ( sshAgentSock ! ) ;
495
- sshAgent . getIdentities ( ( err , publicKeys ) => {
496
- if ( err ) {
497
- reject ( err ) ;
498
- } else {
499
- resolve ( publicKeys || [ ] ) ;
500
- }
501
- } ) ;
502
- } ) ;
503
- } catch ( e ) {
504
- this . logger . error ( `Couldn't get identities from OpenSSH agent` , e ) ;
505
- }
506
-
507
- const sshAgentKeys = sshAgentParsedKeys . map ( parsedKey => {
508
- const fingerprint = crypto . createHash ( 'sha256' ) . update ( parsedKey . getPublicSSH ( ) ) . digest ( 'base64' ) ;
509
- return {
510
- filename : parsedKey . comment ,
511
- parsedKey,
512
- fingerprint
513
- } ;
514
- } ) ;
515
-
516
- const identitiesOnly = ( hostConfig [ 'IdentitiesOnly' ] || '' ) . toLowerCase ( ) === 'yes' ;
517
- const agentKeys : { filename : string ; parsedKey : ParsedKey ; fingerprint : string } [ ] = [ ] ;
518
- const preferredIdentityKeys : { filename : string ; parsedKey : ParsedKey ; fingerprint : string } [ ] = [ ] ;
519
- for ( const agentKey of sshAgentKeys ) {
520
- const foundIdx = fileKeys . findIndex ( k => agentKey . parsedKey . type === k . parsedKey . type && agentKey . fingerprint === k . fingerprint ) ;
521
- if ( foundIdx >= 0 ) {
522
- preferredIdentityKeys . push ( fileKeys [ foundIdx ] ) ;
523
- fileKeys . splice ( foundIdx , 1 ) ;
524
- } else if ( ! identitiesOnly ) {
525
- agentKeys . push ( agentKey ) ;
526
- }
527
- }
528
- preferredIdentityKeys . push ( ...agentKeys ) ;
529
- preferredIdentityKeys . push ( ...fileKeys ) ;
530
-
531
- this . logger . trace ( `Identity keys:` , preferredIdentityKeys . length ? preferredIdentityKeys . map ( k => `${ k . filename } ${ k . parsedKey . type } SHA256:${ k . fingerprint } ` ) . join ( '\n' ) : 'None' ) ;
532
-
533
- return preferredIdentityKeys ;
534
- }
535
-
536
446
private async getWorkspaceSSHDestination ( session : vscode . AuthenticationSession , { workspaceId, gitpodHost, debugWorkspace } : SSHConnectionParams ) : Promise < { destination : string ; password ?: string } > {
537
447
const serviceUrl = new URL ( gitpodHost ) ;
538
448
const sshKeysSupported = session . scopes . includes ( ScopeFeature . SSHPublicKeys ) ;
@@ -571,35 +481,14 @@ const sshHostKeys = (await sshHostKeyResponse.json()) as { type: string; host_ke
571
481
}
572
482
const sshDestInfo = { user, hostName } ;
573
483
574
- let verifiedHostKey : Buffer | undefined ;
575
- // Test ssh connection first
576
- await new Promise < void > ( ( resolve , reject ) => {
577
- const conn = new sshClient ( ) ;
578
- conn . on ( 'ready' , ( ) => {
579
- conn . end ( ) ;
580
- resolve ( ) ;
581
- } ) . on ( 'error' , err => {
582
- reject ( new SSHError ( err ) ) ;
583
- } ) . connect ( {
584
- host : sshDestInfo . hostName ,
585
- username : sshDestInfo . user ,
586
- readyTimeout : 40000 ,
587
- authHandler ( _methodsLeft , _partialSuccess , _callback ) {
588
- return {
589
- type : 'password' ,
590
- username : user ,
591
- password : ownerToken ,
592
- } ;
593
- } ,
594
- hostVerifier ( hostKey ) {
595
- // We didn't specify `hostHash` so `hashedKey` is a Buffer object
596
- verifiedHostKey = ( hostKey as any as Buffer ) ;
597
- const encodedKey = verifiedHostKey . toString ( 'base64' ) ;
598
- return sshHostKeys . some ( keyData => keyData . host_key === encodedKey ) ;
599
- }
600
- } ) ;
601
- } ) ;
602
- this . logger . info ( `SSH test connection to '${ sshDestInfo . hostName } ' host successful` ) ;
484
+ const sshConfiguration = await SSHConfiguration . loadFromFS ( ) ;
485
+
486
+ const verifiedHostKey = await testSSHConnection ( {
487
+ host : sshDestInfo . hostName ,
488
+ username : sshDestInfo . user ,
489
+ readyTimeout : 40000 ,
490
+ password : ownerToken
491
+ } , sshHostKeys , sshConfiguration , this . logger ) ;
603
492
604
493
// SSH connection successful, write host to known_hosts
605
494
try {
@@ -616,10 +505,8 @@ const sshHostKeys = (await sshHostKeyResponse.json()) as { type: string; host_ke
616
505
this . logger . error ( `Couldn't write '${ sshDestInfo . hostName } ' host to known_hosts file:` , e ) ;
617
506
}
618
507
619
- const sshConfiguration = await SSHConfiguration . loadFromFS ( ) ;
620
508
const hostConfiguration = sshConfiguration . getHostConfiguration ( sshDestInfo . hostName ) ;
621
-
622
- let identityKeys = await this . getIdentityKeys ( hostConfiguration ) ;
509
+ let identityKeys = await gatherIdentityFiles ( [ ] , getAgentSock ( hostConfiguration ) , false , this . logger ) ;
623
510
624
511
if ( registeredSSHKeys ) {
625
512
const registeredKeys = this . publicApi
0 commit comments