Skip to content

Commit d1ce6de

Browse files
authored
Credentials: partial migration to SDK v3 aws#1766
## Problem SDK v2 has some known issues in regards to credentials from source_profile. SDK v3 does not fix them out-right, however, it does expose some functionality that allows us to fix the bugs. ## Solution Migrate the credentials subsystem to use SDK v3, then add additional logic to handle source_profile resolution. The new SDK credentials API allows us to 'pre-load' credentials when calling relevant APIs. We can use this functionality to load source profile credentials ourselves, letting the SDK treat them as static credentials. Any valid profile should now be usable as a base profile as well (e.g. processes, SSO, MFA, etc.)
1 parent 740c4bb commit d1ce6de

File tree

53 files changed

+4734
-2647
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+4734
-2647
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Credentials: correctly handle different `source_profile` combinations"
4+
}

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ src/shared/telemetry/clienttelemetry.d.ts
2727

2828
# Generated by tests
2929
src/testFixtures/**/bin
30-
src/testFixtures/**/obj
30+
src/testFixtures/**/obj

.gitpod.yml

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
tasks:
2-
- init: npm install
2+
- init: npm install
33

44
vscode:
5-
extensions:
6-
- dbaeumer.vscode-eslint
7-
- eg2.vscode-npm-script
8-
5+
extensions:
6+
- dbaeumer.vscode-eslint
7+
- eg2.vscode-npm-script

package-lock.json

+4,182-2,344
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+5
Original file line numberDiff line numberDiff line change
@@ -2116,6 +2116,11 @@
21162116
"webpack-cli": "^4.7.2"
21172117
},
21182118
"dependencies": {
2119+
"@aws-sdk/client-sso": "^3.16.0",
2120+
"@aws-sdk/client-sso-oidc": "^3.16.0",
2121+
"@aws-sdk/credential-provider-ini": "^3.15.0",
2122+
"@aws-sdk/credential-provider-process": "^3.15.0",
2123+
"@aws-sdk/credential-provider-sso": "^3.16.0",
21192124
"adm-zip": "^0.4.13",
21202125
"amazon-states-language-service": "^1.6.4",
21212126
"async-lock": "^1.3.0",

src/awsexplorer/awsExplorer.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ export class AwsExplorer implements vscode.TreeDataProvider<AWSTreeNodeBase>, Re
3737
localize('AWS.explorerNode.addRegion', 'Add a region to {0} Explorer...', getIdeProperties().company),
3838
'aws.showRegion',
3939
undefined,
40-
localize('AWS.explorerNode.addRegion.tooltip', 'Click here to add a region to {0} Explorer.', getIdeProperties().company)
40+
localize(
41+
'AWS.explorerNode.addRegion.tooltip',
42+
'Click here to add a region to {0} Explorer.',
43+
getIdeProperties().company
44+
)
4145
)
4246

4347
public constructor(
@@ -51,7 +55,11 @@ export class AwsExplorer implements vscode.TreeDataProvider<AWSTreeNodeBase>, Re
5155
this.extContext.subscriptions.push(
5256
this.awsContext.onDidChangeContext(e => {
5357
if (!e.accountId) {
54-
this.ROOT_NODE_SIGN_IN.label = localize('AWS.explorerNode.signIn', 'Connect to {0}...', getIdeProperties().company)
58+
this.ROOT_NODE_SIGN_IN.label = localize(
59+
'AWS.explorerNode.signIn',
60+
'Connect to {0}...',
61+
getIdeProperties().company
62+
)
5563
this.ROOT_NODE_SIGN_IN.tooltip = localize(
5664
'AWS.explorerNode.signIn.tooltip',
5765
'Click here to select credentials for the {0} Toolkit',

src/awsexplorer/commands/copyArn.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ export async function copyArnCommand(
4242
const logsItem = localize('AWS.generic.message.viewLogs', 'View Logs...')
4343
window
4444
.showErrorMessage(
45-
localize('AWS.explorerNode.noArnFound', 'Could not find an ARN for selected {0} Explorer node', getIdeProperties().company),
45+
localize(
46+
'AWS.explorerNode.noArnFound',
47+
'Could not find an ARN for selected {0} Explorer node',
48+
getIdeProperties().company
49+
),
4650
logsItem
4751
)
4852
.then(selection => {

src/credentials/awsCredentialsStatusBarItem.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,22 @@ export async function initializeAwsCredentialsStatusBarItem(
4242
}
4343

4444
// Resolves when the status bar reaches its final state
45-
export async function updateCredentialsStatusBarItem(statusBarItem: vscode.StatusBarItem, credentialsId?: string): Promise<void> {
45+
export async function updateCredentialsStatusBarItem(
46+
statusBarItem: vscode.StatusBarItem,
47+
credentialsId?: string
48+
): Promise<void> {
4649
clearTimeout(timeoutID)
4750

4851
// Shows confirmation text in the status bar message
4952
let delay = 0
5053
if (credentialsId) {
5154
delay = STATUSBAR_CONNECTED_DELAY
52-
statusBarItem.text = localize('AWS.credentials.statusbar.text', '{0}: {1}', getIdeProperties().company, STATUSBAR_TEXT_CONNECTED)
55+
statusBarItem.text = localize(
56+
'AWS.credentials.statusbar.text',
57+
'{0}: {1}',
58+
getIdeProperties().company,
59+
STATUSBAR_TEXT_CONNECTED
60+
)
5361
}
5462

5563
return new Promise<void>(

src/credentials/credentialsCreator.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ const ERROR_MESSAGE_USER_CANCELLED = localize(
2525
*/
2626
export async function getMfaTokenFromUser(
2727
mfaSerial: string,
28-
profileName: string,
29-
callback: (err?: Error, token?: string) => void
30-
): Promise<void> {
28+
profileName: string
29+
): Promise<string> {
3130
try {
3231
const inputBox = createInputBox({
3332
options: {
@@ -45,9 +44,8 @@ export async function getMfaTokenFromUser(
4544
throw new Error(ERROR_MESSAGE_USER_CANCELLED)
4645
}
4746

48-
callback(undefined, token)
47+
return token
4948
} catch (err) {
50-
const error = err as Error
51-
callback(error)
49+
throw err as Error
5250
}
5351
}

src/credentials/credentialsStore.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import * as AWS from 'aws-sdk'
6+
import * as AWS from '@aws-sdk/types'
77
import { getLogger } from '../shared/logger/logger'
88
import { asString, CredentialsProvider, CredentialsId } from './providers/credentials'
99
import { CredentialsProviderManager } from './providers/credentialsProviderManager'
@@ -23,14 +23,25 @@ export class CredentialsStore {
2323
this.credentialsCache = {}
2424
}
2525

26+
/**
27+
* Checks if the stored credentials are valid. Non-existent or expired credentials returns false.
28+
*
29+
* If the expiration property does not exist, it is assumed to never expire.
30+
*/
31+
public isValid(key: string): boolean {
32+
if (this.credentialsCache[key]) {
33+
const expiration = this.credentialsCache[key].credentials.expiration
34+
return expiration !== undefined ? expiration >= new Date() : true
35+
}
36+
37+
return false
38+
}
39+
2640
/**
2741
* Returns undefined if the specified credentials are expired or not found.
2842
*/
2943
public async getCredentials(credentials: CredentialsId): Promise<CachedCredentials | undefined> {
30-
if (
31-
this.credentialsCache[asString(credentials)] &&
32-
!this.credentialsCache[asString(credentials)].credentials.expired
33-
) {
44+
if (this.isValid(asString(credentials))) {
3445
return this.credentialsCache[asString(credentials)]
3546
} else {
3647
return undefined

src/credentials/credentialsUtilities.ts

+8-9
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as nls from 'vscode-nls'
77
const localize = nls.loadMessageBundle()
88

99
import * as vscode from 'vscode'
10-
import { Credentials } from 'aws-sdk'
10+
import { Credentials } from '@aws-sdk/types'
1111
import { credentialHelpUrl } from '../shared/constants'
1212
import { Profile } from '../shared/credentials/credentialsFile'
1313
import { isCloud9 } from '../shared/extensionUtilities'
@@ -64,16 +64,16 @@ export function hasProfileProperty(profile: Profile, propertyName: string): bool
6464
* User cancellation or timeout expiration will cause rejection.
6565
*
6666
* @param profile Profile name to display for the progress message
67-
* @param provider This can be a Promise that returns Credentials, or void if using 'refresh'
67+
* @param provider A promise that resolves in Credentials
6868
* @param timeout How long to wait for resolution without user intervention (default: 5 minutes)
6969
*
7070
* @returns The resolved Credentials or undefined if the the provider was a 'refresh' Promise
7171
*/
72-
export async function resolveProviderWithCancel<T extends AWS.Credentials | void>(
72+
export async function resolveProviderWithCancel(
7373
profile: string,
74-
provider: Promise<T>,
74+
provider: Promise<Credentials>,
7575
timeout: Timeout | number = CREDENTIALS_TIMEOUT
76-
): Promise<T> {
76+
): Promise<Credentials> {
7777
if (typeof timeout === 'number') {
7878
timeout = new Timeout(timeout)
7979
}
@@ -88,14 +88,13 @@ export async function resolveProviderWithCancel<T extends AWS.Credentials | void
8888
}
8989
}, CREDENTIALS_PROGRESS_DELAY)
9090

91-
await waitTimeout(provider, timeout, {
91+
return (await waitTimeout(provider, timeout, {
9292
onCancel: () => {
9393
throw new Error(`Request to get credentials for "${profile}" cancelled`)
9494
},
9595
onExpire: () => {
9696
throw new Error(`Request to get credentials for "${profile}" expired`)
9797
},
98-
})
99-
100-
return provider
98+
allowUndefined: false,
99+
})) as Credentials
101100
}

src/credentials/providers/credentials.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
import * as AWS from '@aws-sdk/types'
67
import * as telemetry from '../../shared/telemetry/telemetry.gen'
78

89
const CREDENTIALS_PROVIDER_ID_SEPARATOR = ':'

src/credentials/providers/credentialsProviderFactory.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { CredentialsProvider, CredentialsProviderType , CredentialsId, isEqual } from './credentials'
6+
import { CredentialsProvider, CredentialsProviderType, CredentialsId, isEqual } from './credentials'
77

88
/**
99
* Responsible for producing CredentialsProvider objects for a Credential Type
@@ -19,7 +19,8 @@ export interface CredentialsProviderFactory {
1919
}
2020

2121
export abstract class BaseCredentialsProviderFactory<T extends CredentialsProvider>
22-
implements CredentialsProviderFactory {
22+
implements CredentialsProviderFactory
23+
{
2324
protected providers: T[] = []
2425

2526
public getProviderType(): CredentialsProviderType | undefined {

src/credentials/providers/ec2CredentialsProvider.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { Credentials, EC2MetadataCredentials } from 'aws-sdk'
6+
import { Credentials } from '@aws-sdk/types'
7+
import { fromInstanceMetadata } from '@aws-sdk/credential-provider-imds'
78
import { DefaultEc2MetadataClient } from '../../shared/clients/ec2MetadataClient'
89
import { Ec2MetadataClient } from '../../shared/clients/ec2MetadataClient'
910
import { getLogger } from '../../shared/logger'
@@ -17,7 +18,7 @@ import { CredentialsId, CredentialsProvider, CredentialsProviderType } from './c
1718
* @see CredentialsProviderType
1819
*/
1920
export class Ec2CredentialsProvider implements CredentialsProvider {
20-
private credentials: EC2MetadataCredentials | undefined
21+
private credentials: Credentials | undefined
2122
private region: string | undefined
2223
private available: boolean | undefined
2324

@@ -86,7 +87,7 @@ export class Ec2CredentialsProvider implements CredentialsProvider {
8687

8788
public async getCredentials(): Promise<Credentials> {
8889
if (!this.credentials) {
89-
this.credentials = new EC2MetadataCredentials()
90+
this.credentials = await fromInstanceMetadata()()
9091
}
9192
return this.credentials
9293
}

src/credentials/providers/ecsCredentialsProvider.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { Credentials, ECSCredentials } from 'aws-sdk'
6+
import { Credentials } from '@aws-sdk/types'
7+
import { fromContainerMetadata } from '@aws-sdk/credential-provider-imds'
78
import { EnvironmentVariables } from '../../shared/environmentVariables'
89
import { CredentialType } from '../../shared/telemetry/telemetry.gen'
910
import { getStringHash } from '../../shared/utilities/textUtilities'
@@ -15,7 +16,7 @@ import { CredentialsId, CredentialsProvider, CredentialsProviderType } from './c
1516
* @see CredentialsProviderType
1617
*/
1718
export class EcsCredentialsProvider implements CredentialsProvider {
18-
private credentials: ECSCredentials | undefined
19+
private credentials: Credentials | undefined
1920

2021
public async isAvailable(): Promise<boolean> {
2122
const env = process.env as EnvironmentVariables
@@ -56,7 +57,7 @@ export class EcsCredentialsProvider implements CredentialsProvider {
5657

5758
public async getCredentials(): Promise<Credentials> {
5859
if (!this.credentials) {
59-
this.credentials = new ECSCredentials()
60+
this.credentials = await fromContainerMetadata()()
6061
}
6162
return this.credentials
6263
}

src/credentials/providers/envVarsCredentialsProvider.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { Credentials, EnvironmentCredentials } from 'aws-sdk'
6+
import { Credentials } from '@aws-sdk/types'
7+
import { fromEnv } from '@aws-sdk/credential-provider-env'
78
import { EnvironmentVariables } from '../../shared/environmentVariables'
89
import { CredentialType } from '../../shared/telemetry/telemetry.gen'
910
import { getStringHash } from '../../shared/utilities/textUtilities'
@@ -15,9 +16,7 @@ import { CredentialsId, CredentialsProvider, CredentialsProviderType } from './c
1516
* @see CredentialsProviderType
1617
*/
1718
export class EnvVarsCredentialsProvider implements CredentialsProvider {
18-
public static readonly AWS_ENV_VAR_PREFIX: string = 'AWS'
19-
20-
private credentials: EnvironmentCredentials | undefined
19+
private credentials: Credentials | undefined
2120

2221
public async isAvailable(): Promise<boolean> {
2322
const env = process.env as EnvironmentVariables
@@ -58,7 +57,7 @@ export class EnvVarsCredentialsProvider implements CredentialsProvider {
5857

5958
public async getCredentials(): Promise<Credentials> {
6059
if (!this.credentials) {
61-
this.credentials = new EnvironmentCredentials(EnvVarsCredentialsProvider.AWS_ENV_VAR_PREFIX)
60+
this.credentials = await fromEnv()()
6261
}
6362
return this.credentials
6463
}

0 commit comments

Comments
 (0)