@@ -10,18 +10,27 @@ import { ParsedIniData, SharedConfigFiles } from '@aws-sdk/shared-ini-file-loade
10
10
import { chain } from '@aws-sdk/property-provider'
11
11
import { fromInstanceMetadata , fromContainerMetadata } from '@aws-sdk/credential-provider-imds'
12
12
import { fromEnv } from '@aws-sdk/credential-provider-env'
13
- import { Profile } from '../../shared/credentials/credentialsFile'
14
13
import { getLogger } from '../../shared/logger'
15
14
import { getStringHash } from '../../shared/utilities/textUtilities'
16
15
import { getMfaTokenFromUser } from '../credentialsCreator'
17
16
import { resolveProviderWithCancel } from '../credentialsUtilities'
18
17
import { CredentialsProvider , CredentialsProviderType , CredentialsId } from './credentials'
19
18
import { CredentialType } from '../../shared/telemetry/telemetry.gen'
20
- import { getMissingProps , hasProps } from '../../shared/utilities/tsUtils'
19
+ import { assertHasProps , getMissingProps , hasProps } from '../../shared/utilities/tsUtils'
21
20
import { DefaultStsClient } from '../../shared/clients/stsClient'
22
21
import { SsoAccessTokenProvider } from '../sso/ssoAccessTokenProvider'
23
22
import { SsoClient } from '../sso/clients'
24
23
import { toRecord } from '../../shared/utilities/collectionUtils'
24
+ import {
25
+ extractData ,
26
+ getRequiredFields ,
27
+ getSectionDataOrThrow ,
28
+ getSectionOrThrow ,
29
+ isProfileSection ,
30
+ Profile ,
31
+ Section ,
32
+ } from '../sharedCredentials'
33
+ import { hasScopes , SsoProfile } from '../auth'
25
34
26
35
const sharedCredentialProperties = {
27
36
AWS_ACCESS_KEY_ID : 'aws_access_key_id' ,
@@ -37,6 +46,8 @@ const sharedCredentialProperties = {
37
46
SSO_REGION : 'sso_region' ,
38
47
SSO_ACCOUNT_ID : 'sso_account_id' ,
39
48
SSO_ROLE_NAME : 'sso_role_name' ,
49
+ SSO_SESSION : 'sso_session' ,
50
+ SSO_REGISTRATION_SCOPES : 'sso_registration_scopes' ,
40
51
} as const
41
52
42
53
const credentialSources = {
@@ -55,6 +66,7 @@ function validateProfile(profile: Profile, ...props: string[]): string | undefin
55
66
56
67
function isSsoProfile ( profile : Profile ) : boolean {
57
68
return (
69
+ hasProps ( profile , sharedCredentialProperties . SSO_SESSION ) ||
58
70
hasProps ( profile , sharedCredentialProperties . SSO_START_URL ) ||
59
71
hasProps ( profile , sharedCredentialProperties . SSO_REGION ) ||
60
72
hasProps ( profile , sharedCredentialProperties . SSO_ROLE_NAME ) ||
@@ -66,20 +78,10 @@ function isSsoProfile(profile: Profile): boolean {
66
78
* Represents one profile from the AWS Shared Credentials files.
67
79
*/
68
80
export class SharedCredentialsProvider implements CredentialsProvider {
69
- private readonly profile : Profile
81
+ private readonly section = getSectionOrThrow ( this . sections , this . profileName , 'profile' )
82
+ private readonly profile = extractData ( this . section )
70
83
71
- public constructor (
72
- private readonly profileName : string ,
73
- private readonly allSharedCredentialProfiles : Map < string , Profile >
74
- ) {
75
- const profile = this . allSharedCredentialProfiles . get ( profileName )
76
-
77
- if ( ! profile ) {
78
- throw new Error ( `Profile not found: ${ profileName } ` )
79
- }
80
-
81
- this . profile = profile
82
- }
84
+ public constructor ( private readonly profileName : string , private readonly sections : Section [ ] ) { }
83
85
84
86
public getCredentialsId ( ) : CredentialsId {
85
87
return {
@@ -139,6 +141,37 @@ export class SharedCredentialsProvider implements CredentialsProvider {
139
141
return true
140
142
}
141
143
144
+ private getProfile ( name : string ) {
145
+ return getSectionDataOrThrow ( this . sections , name , 'profile' )
146
+ }
147
+
148
+ private getSsoProfileFromProfile ( ) : SsoProfile & { identifier ?: string } {
149
+ const defaultRegion = this . getDefaultRegion ( ) ?? 'us-east-1'
150
+ const sessionName = this . profile [ sharedCredentialProperties . SSO_SESSION ]
151
+ if ( sessionName === undefined ) {
152
+ assertHasProps ( this . profile , sharedCredentialProperties . SSO_START_URL )
153
+
154
+ return {
155
+ type : 'sso' ,
156
+ scopes : [ 'sso:account:access' ] ,
157
+ startUrl : this . profile [ sharedCredentialProperties . SSO_START_URL ] ,
158
+ ssoRegion : this . profile [ sharedCredentialProperties . SSO_REGION ] ?? defaultRegion ,
159
+ }
160
+ }
161
+
162
+ const sessionData = getSectionDataOrThrow ( this . sections , sessionName , 'sso-session' )
163
+ const scopes = sessionData [ sharedCredentialProperties . SSO_REGISTRATION_SCOPES ]
164
+ assertHasProps ( sessionData , sharedCredentialProperties . SSO_START_URL )
165
+
166
+ return {
167
+ type : 'sso' ,
168
+ identifier : sessionName ,
169
+ scopes : scopes ?. split ( ',' ) . map ( s => s . trim ( ) ) ,
170
+ startUrl : sessionData [ sharedCredentialProperties . SSO_START_URL ] ,
171
+ ssoRegion : sessionData [ sharedCredentialProperties . SSO_REGION ] ?? defaultRegion ,
172
+ }
173
+ }
174
+
142
175
/**
143
176
* Returns undefined if the Profile is valid, else a string indicating what is invalid
144
177
*/
@@ -161,13 +194,7 @@ export class SharedCredentialsProvider implements CredentialsProvider {
161
194
sharedCredentialProperties . AWS_SECRET_ACCESS_KEY
162
195
)
163
196
} else if ( isSsoProfile ( this . profile ) ) {
164
- return validateProfile (
165
- this . profile ,
166
- sharedCredentialProperties . SSO_START_URL ,
167
- sharedCredentialProperties . SSO_REGION ,
168
- sharedCredentialProperties . SSO_ROLE_NAME ,
169
- sharedCredentialProperties . SSO_ACCOUNT_ID
170
- )
197
+ return undefined
171
198
} else {
172
199
return 'not supported by the Toolkit'
173
200
}
@@ -191,7 +218,7 @@ export class SharedCredentialsProvider implements CredentialsProvider {
191
218
192
219
const source = new SharedCredentialsProvider (
193
220
this . profile [ sharedCredentialProperties . SOURCE_PROFILE ] ! ,
194
- this . allSharedCredentialProfiles
221
+ this . sections
195
222
)
196
223
const creds = await source . getCredentials ( )
197
224
loadedCreds [ this . profile [ sharedCredentialProperties . SOURCE_PROFILE ] ! ] = {
@@ -252,13 +279,13 @@ export class SharedCredentialsProvider implements CredentialsProvider {
252
279
profilesTraversed . push ( profileName )
253
280
254
281
// Missing reference
255
- if ( ! this . allSharedCredentialProfiles . has ( profileName ) ) {
282
+ if ( ! this . sections . find ( s => s . name === profileName && s . type === 'profile' ) ) {
256
283
return `Shared Credentials Profile ${ profileName } not found. Reference chain: ${ profilesTraversed . join (
257
284
' -> '
258
285
) } `
259
286
}
260
287
261
- profile = this . allSharedCredentialProfiles . get ( profileName ) !
288
+ profile = this . getProfile ( profileName )
262
289
}
263
290
}
264
291
@@ -315,31 +342,44 @@ export class SharedCredentialsProvider implements CredentialsProvider {
315
342
return this . makeSharedIniFileCredentialsProvider ( loadedCreds )
316
343
}
317
344
318
- if ( hasProps ( this . profile , sharedCredentialProperties . SSO_START_URL ) ) {
319
- logger . verbose (
320
- `Profile ${ this . profileName } contains ${ sharedCredentialProperties . SSO_START_URL } - treating as SSO Credentials`
321
- )
322
-
323
- const region = this . profile [ sharedCredentialProperties . SSO_REGION ] !
324
- const startUrl = this . profile [ sharedCredentialProperties . SSO_START_URL ] !
325
- const accountId = this . profile [ sharedCredentialProperties . SSO_ACCOUNT_ID ] !
326
- const roleName = this . profile [ sharedCredentialProperties . SSO_ROLE_NAME ] !
327
- const tokenProvider = new SsoAccessTokenProvider ( { region, startUrl } )
328
- const client = SsoClient . create ( region , tokenProvider )
329
-
330
- return async ( ) => {
331
- if ( ( await tokenProvider . getToken ( ) ) === undefined ) {
332
- await tokenProvider . createToken ( )
333
- }
345
+ if ( isSsoProfile ( this . profile ) ) {
346
+ logger . verbose ( `Profile ${ this . profileName } is an SSO profile - treating as SSO Credentials` )
334
347
335
- return client . getRoleCredentials ( { accountId, roleName } )
336
- }
348
+ return this . makeSsoCredentaislProvider ( )
337
349
}
338
350
339
351
logger . error ( `Profile ${ this . profileName } did not contain any supported properties` )
340
352
throw new Error ( `Shared Credentials profile ${ this . profileName } is not supported` )
341
353
}
342
354
355
+ private makeSsoCredentaislProvider ( ) {
356
+ const ssoProfile = this . getSsoProfileFromProfile ( )
357
+ if ( ! hasScopes ( ssoProfile , [ 'sso:account:access' ] ) ) {
358
+ throw new Error ( `Session for "${ this . profileName } " is missing required scope: sso:account:access` )
359
+ }
360
+
361
+ const region = ssoProfile . ssoRegion
362
+ const tokenProvider = new SsoAccessTokenProvider ( { ...ssoProfile , region } )
363
+ const client = SsoClient . create ( region , tokenProvider )
364
+
365
+ return async ( ) => {
366
+ if ( ( await tokenProvider . getToken ( ) ) === undefined ) {
367
+ await tokenProvider . createToken ( )
368
+ }
369
+
370
+ const data = getRequiredFields (
371
+ this . section ,
372
+ sharedCredentialProperties . SSO_ACCOUNT_ID ,
373
+ sharedCredentialProperties . SSO_ROLE_NAME
374
+ )
375
+
376
+ return client . getRoleCredentials ( {
377
+ accountId : data [ sharedCredentialProperties . SSO_ACCOUNT_ID ] ,
378
+ roleName : data [ sharedCredentialProperties . SSO_ROLE_NAME ] ,
379
+ } )
380
+ }
381
+ }
382
+
343
383
private makeSharedIniFileCredentialsProvider ( loadedCreds ?: ParsedIniData ) : AWS . CredentialProvider {
344
384
const assumeRole = async ( credentials : AWS . Credentials , params : AssumeRoleParams ) => {
345
385
const region = this . getDefaultRegion ( ) ?? 'us-east-1'
@@ -356,7 +396,11 @@ export class SharedCredentialsProvider implements CredentialsProvider {
356
396
// Our credentials logic merges profiles from the credentials and config files but SDK v3 does not
357
397
// This can cause odd behavior where the Toolkit can switch to a profile but not authenticate with it
358
398
// So the workaround is to do give the SDK the merged profiles directly
359
- const profiles = toRecord ( this . allSharedCredentialProfiles . keys ( ) , k => this . allSharedCredentialProfiles . get ( k ) )
399
+ const profileSections = this . sections . filter ( isProfileSection )
400
+ const profiles = toRecord (
401
+ profileSections . map ( s => s . name ) ,
402
+ k => this . getProfile ( k )
403
+ )
360
404
361
405
return fromIni ( {
362
406
profile : this . profileName ,
0 commit comments