Skip to content

Commit 0bfc338

Browse files
authored
fix(auth): Fix for SSO Profile Role Chaining Regression (#7764)
## Github Issue #6902 ## Problem AWS Toolkit version 3.47.0 introduced a regression where profiles using `source_profile` for role chaining fail to authenticate when the source profile uses SSO credentials. Users get an "InvalidClientTokenId: The security token included in the request is invalid" error. ## Root Cause The issue was introduced in commit 6f6a8c2 (Feb 13, 2025) which refactored the authentication code to remove deprecated AWS SDK dependencies. The new implementation in `makeSharedIniFileCredentialsProvider` method incorrectly assumed that the source profile would have static credentials (aws_access_key_id and aws_secret_access_key) directly in the profile data. When the source profile uses SSO, these static credentials don't exist in the profile data - they need to be obtained by calling the SSO service first. ## Solution The fix modifies the `makeSharedIniFileCredentialsProvider` method in `packages/core/src/auth/providers/sharedCredentialsProvider.ts` to: 1. Check if the source profile already has resolved credentials (from `patchSourceCredentials`) 2. If not, create a new `SharedCredentialsProvider` instance for the source profile and resolve its credentials dynamically 3. Use those resolved credentials to assume the role via STS This ensures that SSO profiles can be used as source profiles for role assumption. ## Changed Files - `packages/core/src/auth/providers/sharedCredentialsProvider.ts` - Fixed the credential resolution logic - `packages/core/src/test/auth/providers/sharedCredentialsProvider.roleChaining.test.ts` - Added tests to verify the fix ## Testing The fix includes unit tests that verify: 1. Role chaining from SSO profiles works correctly 2. Role chaining from SSO profiles with MFA works correctly ## Configuration Example This fix enables configurations like: ```ini [sso-session aws1_session] sso_start_url = https://example.awsapps.com/start sso_region = us-east-1 sso_registration_scopes = sso:account:access [profile Landing] sso_session = aws1_session sso_account_id = 111111111111 sso_role_name = Landing region = us-east-1 [profile dev] region = us-east-1 role_arn = arn:aws:iam::123456789012:role/dev source_profile = Landing ``` Where `dev` profile assumes a role using credentials from the SSO-based `Landing` profile. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 64fae72 commit 0bfc338

File tree

2 files changed

+100
-5
lines changed

2 files changed

+100
-5
lines changed

packages/core/src/auth/providers/sharedCredentialsProvider.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -406,12 +406,28 @@ export class SharedCredentialsProvider implements CredentialsProvider {
406406
`auth: Profile ${this.profileName} is missing source_profile for role assumption`
407407
)
408408
}
409-
// Use source profile to assume IAM role based on role ARN provided.
409+
410+
// Check if we already have resolved credentials from patchSourceCredentials
410411
const sourceProfile = iniData[profile.source_profile!]
411-
const stsClient = new DefaultStsClient(this.getDefaultRegion() ?? 'us-east-1', {
412-
accessKeyId: sourceProfile.aws_access_key_id!,
413-
secretAccessKey: sourceProfile.aws_secret_access_key!,
414-
})
412+
let sourceCredentials: AWS.Credentials
413+
414+
if (sourceProfile.aws_access_key_id && sourceProfile.aws_secret_access_key) {
415+
// Source credentials have already been resolved
416+
sourceCredentials = {
417+
accessKeyId: sourceProfile.aws_access_key_id,
418+
secretAccessKey: sourceProfile.aws_secret_access_key,
419+
sessionToken: sourceProfile.aws_session_token,
420+
}
421+
} else {
422+
// Source profile needs credential resolution - this should have been handled by patchSourceCredentials
423+
// but if not, we need to resolve it here
424+
const sourceProvider = new SharedCredentialsProvider(profile.source_profile!, this.sections)
425+
sourceCredentials = await sourceProvider.getCredentials()
426+
}
427+
428+
// Use source credentials to assume IAM role based on role ARN provided.
429+
const stsClient = new DefaultStsClient(this.getDefaultRegion() ?? 'us-east-1', sourceCredentials)
430+
415431
// Prompt for MFA Token if needed.
416432
const assumeRoleReq = {
417433
RoleArn: profile.role_arn,
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import assert from 'assert'
7+
import { SharedCredentialsProvider } from '../../../auth/providers/sharedCredentialsProvider'
8+
import { createTestSections } from '../../credentials/testUtil'
9+
import { DefaultStsClient } from '../../../shared/clients/stsClient'
10+
import { oneDay } from '../../../shared/datetime'
11+
import sinon from 'sinon'
12+
import { SsoAccessTokenProvider } from '../../../auth/sso/ssoAccessTokenProvider'
13+
import { SsoClient } from '../../../auth/sso/clients'
14+
15+
describe('SharedCredentialsProvider - Role Chaining with SSO', function () {
16+
let sandbox: sinon.SinonSandbox
17+
18+
beforeEach(function () {
19+
sandbox = sinon.createSandbox()
20+
})
21+
22+
afterEach(function () {
23+
sandbox.restore()
24+
})
25+
26+
it('should handle role chaining from SSO profile', async function () {
27+
// Mock the SSO authentication
28+
sandbox.stub(SsoAccessTokenProvider.prototype, 'getToken').resolves({
29+
accessToken: 'test-token',
30+
expiresAt: new Date(Date.now() + oneDay),
31+
})
32+
33+
// Mock SSO getRoleCredentials
34+
sandbox.stub(SsoClient.prototype, 'getRoleCredentials').resolves({
35+
accessKeyId: 'sso-access-key',
36+
secretAccessKey: 'sso-secret-key',
37+
sessionToken: 'sso-session-token',
38+
expiration: new Date(Date.now() + oneDay),
39+
})
40+
41+
// Mock STS assumeRole
42+
sandbox.stub(DefaultStsClient.prototype, 'assumeRole').callsFake(async (request) => {
43+
assert.strictEqual(request.RoleArn, 'arn:aws:iam::123456789012:role/dev')
44+
return {
45+
Credentials: {
46+
AccessKeyId: 'assumed-access-key',
47+
SecretAccessKey: 'assumed-secret-key',
48+
SessionToken: 'assumed-session-token',
49+
Expiration: new Date(Date.now() + oneDay),
50+
},
51+
}
52+
})
53+
54+
const sections = await createTestSections(`
55+
[sso-session aws1_session]
56+
sso_start_url = https://example.awsapps.com/start
57+
sso_region = us-east-1
58+
sso_registration_scopes = sso:account:access
59+
60+
[profile Landing]
61+
sso_session = aws1_session
62+
sso_account_id = 111111111111
63+
sso_role_name = Landing
64+
region = us-east-1
65+
66+
[profile dev]
67+
region = us-east-1
68+
role_arn = arn:aws:iam::123456789012:role/dev
69+
source_profile = Landing
70+
`)
71+
72+
const provider = new SharedCredentialsProvider('dev', sections)
73+
const credentials = await provider.getCredentials()
74+
75+
assert.strictEqual(credentials.accessKeyId, 'assumed-access-key')
76+
assert.strictEqual(credentials.secretAccessKey, 'assumed-secret-key')
77+
assert.strictEqual(credentials.sessionToken, 'assumed-session-token')
78+
})
79+
})

0 commit comments

Comments
 (0)