Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/faq-credentials.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ Issue [aws-toolkit-vscode#3667](https://github.com/aws/aws-toolkit-vscode/issues
2. Attempt to sign in again with AWS Builder ID
3. If sign is is successful you can remove the old folder: `rm -rf ~/.aws/sso-OLD`
1. Or revert the change: `mv ~/.aws/sso-OLD ~/.aws/sso`

### AWS Shared Credentials File

When authenticating with IAM credentials, the profile name, access key, and secret key will be stored on disk at a default location of `~/.aws/credentials` on Linux and MacOS, and `%USERPROFILE%\.aws\credentials` on Windows machines. The toolkit also supports editting this file manually, with the format specified [here](https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html#file-format-creds). The credentials files also supports [role assumption](https://docs.aws.amazon.com/sdkref/latest/guide/access-assume-role.html) and [MFA](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa.html). Note that this credentials file is shared between all local AWS development tools. For more information, see the full documentation [here](https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html).
31,566 changes: 18,316 additions & 13,250 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@
"@aws-sdk/client-lambda": "^3.637.0",
"@aws-sdk/client-sso": "^3.342.0",
"@aws-sdk/client-sso-oidc": "^3.574.0",
"@aws-sdk/credential-provider-ini": "3.46.0",
"@aws-sdk/credential-provider-env": "3.696.0",
"@aws-sdk/credential-provider-process": "3.37.0",
"@aws-sdk/credential-provider-sso": "^3.345.0",
"@aws-sdk/property-provider": "3.46.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/auth/providers/ec2CredentialsProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { Credentials } from '@aws-sdk/types'
import { fromInstanceMetadata } from '@aws-sdk/credential-provider-imds'
import { fromInstanceMetadata } from '@smithy/credential-provider-imds'
import { DefaultEc2MetadataClient } from '../../shared/clients/ec2MetadataClient'
import { Ec2MetadataClient } from '../../shared/clients/ec2MetadataClient'
import { getLogger } from '../../shared/logger/logger'
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/auth/providers/ecsCredentialsProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { Credentials, CredentialProvider } from '@aws-sdk/types'
import { fromContainerMetadata } from '@aws-sdk/credential-provider-imds'
import { fromContainerMetadata } from '@smithy/credential-provider-imds'
import { EnvironmentVariables } from '../../shared/environmentVariables'
import { CredentialType } from '../../shared/telemetry/telemetry.gen'
import { getStringHash } from '../../shared/utilities/textUtilities'
Expand Down
73 changes: 48 additions & 25 deletions packages/core/src/auth/providers/sharedCredentialsProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
*/

import * as AWS from '@aws-sdk/types'
import { AssumeRoleParams, fromIni } from '@aws-sdk/credential-provider-ini'
import { fromProcess } from '@aws-sdk/credential-provider-process'
import { ParsedIniData, SharedConfigFiles } from '@smithy/shared-ini-file-loader'
import { ParsedIniData } from '@smithy/types'
import { chain } from '@aws-sdk/property-provider'
import { fromInstanceMetadata, fromContainerMetadata } from '@aws-sdk/credential-provider-imds'
import { fromInstanceMetadata, fromContainerMetadata } from '@smithy/credential-provider-imds'
import { fromEnv } from '@aws-sdk/credential-provider-env'
import { getLogger } from '../../shared/logger/logger'
import { getStringHash } from '../../shared/utilities/textUtilities'
Expand All @@ -29,9 +28,10 @@ import {
Profile,
Section,
} from '../credentials/sharedCredentials'
import { SectionName, SharedCredentialsKeys } from '../credentials/types'
import { CredentialsData, SectionName, SharedCredentialsKeys } from '../credentials/types'
import { SsoProfile, hasScopes, scopesSsoAccountAccess } from '../connection'
import { builderIdStartUrl } from '../sso/constants'
import { ToolkitError } from '../../shared/errors'

const credentialSources = {
ECS_CONTAINER: 'EcsContainer',
Expand Down Expand Up @@ -378,18 +378,6 @@ export class SharedCredentialsProvider implements CredentialsProvider {
}

private makeSharedIniFileCredentialsProvider(loadedCreds?: ParsedIniData): AWS.CredentialProvider {
const assumeRole = async (credentials: AWS.Credentials, params: AssumeRoleParams) => {
const region = this.getDefaultRegion() ?? 'us-east-1'
const stsClient = new DefaultStsClient(region, credentials)
const response = await stsClient.assumeRole(params)
return {
accessKeyId: response.Credentials!.AccessKeyId!,
secretAccessKey: response.Credentials!.SecretAccessKey!,
sessionToken: response.Credentials?.SessionToken,
expiration: response.Credentials?.Expiration,
}
}

// Our credentials logic merges profiles from the credentials and config files but SDK v3 does not
// This can cause odd behavior where the Toolkit can switch to a profile but not authenticate with it
// So the workaround is to do give the SDK the merged profiles directly
Expand All @@ -399,15 +387,50 @@ export class SharedCredentialsProvider implements CredentialsProvider {
(k) => this.getProfile(k)
)

return fromIni({
profile: this.profileName,
mfaCodeProvider: async (mfaSerial) => await getMfaTokenFromUser(mfaSerial, this.profileName),
roleAssumer: assumeRole,
loadedConfig: Promise.resolve({
credentialsFile: loadedCreds ?? profiles,
configFile: {},
} as SharedConfigFiles),
})
return async () => {
const iniData = loadedCreds ?? profiles
const profile: CredentialsData = iniData[this.profileName]
if (!profile) {
throw new ToolkitError(`auth: Profile ${this.profileName} not found`)
}
// No role to assume, return static credentials.
if (!profile.role_arn) {
return {
accessKeyId: profile.aws_access_key_id!,
secretAccessKey: profile.aws_secret_access_key!,
sessionToken: profile.aws_session_token,
}
}
if (!profile.source_profile || !iniData[profile.source_profile]) {
throw new ToolkitError(
`auth: Profile ${this.profileName} is missing source_profile for role assumption`
)
}
// Use source profile to assume IAM role based on role ARN provided.
const sourceProfile = iniData[profile.source_profile!]
const stsClient = new DefaultStsClient(this.getDefaultRegion() ?? 'us-east-1', {
accessKeyId: sourceProfile.aws_access_key_id!,
secretAccessKey: sourceProfile.aws_secret_access_key!,
})
// Prompt for MFA Token if needed.
const assumeRoleReq = {
RoleArn: profile.role_arn,
RoleSessionName: 'AssumeRoleSession',
...(profile.mfa_serial
? {
SerialNumber: profile.mfa_serial,
TokenCode: await getMfaTokenFromUser(profile.mfa_serial, this.profileName),
}
: {}),
}
const assumeRoleRsp = await stsClient.assumeRole(assumeRoleReq)
return {
accessKeyId: assumeRoleRsp.Credentials!.AccessKeyId!,
secretAccessKey: assumeRoleRsp.Credentials!.SecretAccessKey!,
sessionToken: assumeRoleRsp.Credentials?.SessionToken,
expiration: assumeRoleRsp.Credentials?.Expiration,
}
}
}

private makeSourcedCredentialsProvider(): AWS.CredentialProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { SsoClient } from '../../../auth/sso/clients'
import { stub } from '../../utilities/stubber'
import { SsoAccessTokenProvider } from '../../../auth/sso/ssoAccessTokenProvider'
import { createTestSections } from '../testUtil'
import { DefaultStsClient } from '../../../shared/clients/stsClient'
import { oneDay } from '../../../shared/datetime'
import { getTestWindow } from '../../shared/vscode/window'

const missingPropertiesFragment = 'missing properties'

Expand Down Expand Up @@ -450,6 +453,76 @@ describe('SharedCredentialsProvider', async function () {
})
})
})

describe('makeSharedIniFileCredentialsProvider', function () {
let defaultSection: string

before(function () {
defaultSection = `[profile default]
aws_access_key_id = x
aws_secret_access_key = y`
})

beforeEach(function () {
sandbox.stub(DefaultStsClient.prototype, 'assumeRole').callsFake(async (request) => {
assert.strictEqual(request.RoleArn, 'testarn')
if (request.SerialNumber) {
assert.strictEqual(request.SerialNumber, 'mfaSerialToken')
assert.strictEqual(request.TokenCode, 'mfaToken')
}
return {
Credentials: {
AccessKeyId: 'id',
SecretAccessKey: 'secret',
SessionToken: 'token',
Expiration: new Date(Date.now() + oneDay),
},
}
})
})

it('assumes role given in ini data', async function () {
const sections = await createTestSections(`
${defaultSection}
[profile assume]
source_profile = default
role_arn = testarn
`)

const sut = new SharedCredentialsProvider('assume', sections)
const creds = await sut.getCredentials()
assert.strictEqual(creds.accessKeyId, 'id')
assert.strictEqual(creds.secretAccessKey, 'secret')
assert.strictEqual(creds.sessionToken, 'token')
})

it('assumes role with mfa token', async function () {
const sections = await createTestSections(`
${defaultSection}
[profile assume]
source_profile = default
role_arn = testarn
mfa_serial= mfaSerialToken
`)
const sut = new SharedCredentialsProvider('assume', sections)

getTestWindow().onDidShowInputBox((inputBox) => {
inputBox.acceptValue('mfaToken')
})

const creds = await sut.getCredentials()
assert.strictEqual(creds.accessKeyId, 'id')
assert.strictEqual(creds.secretAccessKey, 'secret')
assert.strictEqual(creds.sessionToken, 'token')
})

it('does not assume role when no roleArn is present', async function () {
const sut = new SharedCredentialsProvider('default', await createTestSections(defaultSection))
const creds = await sut.getCredentials()
assert.strictEqual(creds.accessKeyId, 'x')
assert.strictEqual(creds.secretAccessKey, 'y')
})
})
})

function assertSubstringsInText(text: string | undefined, ...substrings: string[]) {
Expand Down
Loading