Skip to content

Commit 0401a71

Browse files
authored
Merge pull request aws#7997 from valerena/endpoint-url-plus-localstack
2 parents bfee0a9 + a27844f commit 0401a71

File tree

60 files changed

+1753
-406
lines changed

Some content is hidden

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

60 files changed

+1753
-406
lines changed

packages/core/package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@
478478
"AWS.toolkit.lambda.walkthrough.title": "Get started building your application",
479479
"AWS.toolkit.lambda.walkthrough.description": "Your quick guide to build an application visually, iterate locally, and deploy to the cloud!",
480480
"AWS.toolkit.lambda.walkthrough.toolInstall.title": "Complete installation",
481-
"AWS.toolkit.lambda.walkthrough.toolInstall.description": "The AWS Command Line Interface (AWS CLI) is an open source tool that enables you to interact with AWS services using commands in your command-line shell. It is required to create and interact with AWS resources. \n\n[Install AWS CLI](command:aws.toolkit.installAWSCLI)\n\n Use the Serverless Application Model (SAM) CLI to locally build, invoke, and deploy your functions. Version 1.98+ is required. \n\n[Install SAM CLI](command:aws.toolkit.installSAMCLI)\n\n Use Docker to locally emulate a Lambda environment. Docker is optional. However, if you want to invoke locally, Docker is required so Lambda can locally emulate the execution environment. \n\n[Install Docker (optional)](command:aws.toolkit.installDocker)",
481+
"AWS.toolkit.lambda.walkthrough.toolInstall.description": "Manage your AWS services and resources with the AWS Command Line Interface (AWS CLI). \n\n[Install AWS CLI](command:aws.toolkit.installAWSCLI)\n\nBuild locally, invoke, and deploy your functions with the Serverless Application Model (SAM) CLI. \n\n[Install SAM CLI](command:aws.toolkit.installSAMCLI)\n\nDocker is an optional, third party tool that assists with local AWS Lambda runtime emulation. Docker is required to invoke Lambda functions on your local machine. \n\n[Install Docker (optional)](command:aws.toolkit.installDocker)\n\nEmulate your AWS cloud services locally with LocalStack to streamline testing in VS Code and CI environments. [Learn more](https://docs.localstack.cloud/aws/). \n\n[Install LocalStack (optional)](command:aws.toolkit.installLocalStack)",
482482
"AWS.toolkit.lambda.walkthrough.chooseTemplate.title": "Choose your application template",
483483
"AWS.toolkit.lambda.walkthrough.chooseTemplate.description": "Select a starter application, visually compose an application from scratch, open an existing application, or browse more application examples. \n\nInfrastructure Composer allows you to visually compose modern applications in the cloud. It will define the necessary permissions between resources when you drag a connection between them. \n\n[Initialize your project](command:aws.toolkit.lambda.initializeWalkthroughProject)",
484484
"AWS.toolkit.lambda.walkthrough.step1.title": "Iterate locally",

packages/core/src/auth/auth.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ export class Auth implements AuthService, ConnectionManager {
215215
const provider = await this.getCredentialsProvider(id, profile)
216216
await this.authenticate(id, () => this.createCachedCredentials(provider), shouldInvalidate)
217217

218-
return this.getIamConnection(id, profile)
218+
return await this.getIamConnection(id, profile)
219219
}
220220
}
221221

@@ -253,7 +253,8 @@ export class Auth implements AuthService, ConnectionManager {
253253
if (profile === undefined) {
254254
throw new Error(`Connection does not exist: ${id}`)
255255
}
256-
const conn = profile.type === 'sso' ? this.getSsoConnection(id, profile) : this.getIamConnection(id, profile)
256+
const conn =
257+
profile.type === 'sso' ? this.getSsoConnection(id, profile) : await this.getIamConnection(id, profile)
257258

258259
this.#activeConnection = conn
259260
this.#onDidChangeActiveConnection.fire(conn)
@@ -705,7 +706,7 @@ export class Auth implements AuthService, ConnectionManager {
705706
if (profile.type === 'sso') {
706707
return this.getSsoConnection(id, profile)
707708
} else {
708-
return this.getIamConnection(id, profile)
709+
return await this.getIamConnection(id, profile)
709710
}
710711
}
711712

@@ -805,17 +806,21 @@ export class Auth implements AuthService, ConnectionManager {
805806
)
806807
}
807808

808-
private getIamConnection(
809+
private async getIamConnection(
809810
id: Connection['id'],
810811
profile: StoredProfile<IamProfile>
811-
): IamConnection & StatefulConnection {
812+
): Promise<IamConnection & StatefulConnection> {
813+
// Get the provider to extract the endpoint URL
814+
const provider = await this.getCredentialsProvider(id, profile)
815+
const endpointUrl = provider.getEndpointUrl?.()
812816
return {
813817
id,
814818
type: 'iam',
815819
state: profile.metadata.connectionState,
816820
label:
817821
profile.metadata.label ?? (profile.type === 'iam' && profile.subtype === 'linked' ? profile.name : id),
818822
getCredentials: async () => this.getCredentials(id, await this.getCredentialsProvider(id, profile)),
823+
endpointUrl,
819824
}
820825
}
821826

@@ -832,6 +837,8 @@ export class Auth implements AuthService, ConnectionManager {
832837
label: profile.metadata?.label ?? this.getSsoProfileLabel(profile),
833838
getToken: () => this.getToken(id, provider),
834839
getRegistration: () => provider.getClientRegistration(),
840+
// SsoConnection is managed internally in the AWS Toolkit, so the endpointUrl can't be configured
841+
endpointUrl: undefined,
835842
}
836843
}
837844

@@ -856,8 +863,8 @@ export class Auth implements AuthService, ConnectionManager {
856863
private async createCachedCredentials(provider: CredentialsProvider) {
857864
const providerId = provider.getCredentialsId()
858865
globals.loginManager.store.invalidateCredentials(providerId)
859-
const { credentials } = await globals.loginManager.store.upsertCredentials(providerId, provider)
860-
await globals.loginManager.validateCredentials(credentials, provider.getDefaultRegion())
866+
const { credentials, endpointUrl } = await globals.loginManager.store.upsertCredentials(providerId, provider)
867+
await globals.loginManager.validateCredentials(credentials, endpointUrl, provider.getDefaultRegion())
861868

862869
return credentials
863870
}

packages/core/src/auth/connection.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export function createSsoProfile(
111111
export interface SsoConnection extends SsoProfile {
112112
readonly id: string
113113
readonly label: string
114+
readonly endpointUrl?: string | undefined
114115

115116
/**
116117
* Retrieves a bearer token, refreshing or re-authenticating as-needed.
@@ -129,6 +130,7 @@ export interface IamConnection {
129130
// This may change in the future after refactoring legacy implementations
130131
readonly id: string
131132
readonly label: string
133+
readonly endpointUrl: string | undefined
132134
getCredentials(): Promise<Credentials>
133135
}
134136

packages/core/src/auth/credentials/store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { CredentialsProviderManager } from '../providers/credentialsProviderMana
1212
export interface CachedCredentials {
1313
credentials: AWS.Credentials
1414
credentialsHashCode: string
15+
endpointUrl?: string
1516
}
1617

1718
/**
@@ -92,6 +93,7 @@ export class CredentialsStore {
9293
const credentials = {
9394
credentials: await credentialsProvider.getCredentials(),
9495
credentialsHashCode: credentialsProvider.getHashCode(),
96+
endpointUrl: credentialsProvider.getEndpointUrl?.(),
9597
}
9698

9799
this.credentialsCache[asString(credentialsId)] = credentials

packages/core/src/auth/credentials/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const SharedCredentialsKeys = {
1212
AWS_SESSION_TOKEN: 'aws_session_token',
1313
CREDENTIAL_PROCESS: 'credential_process',
1414
CREDENTIAL_SOURCE: 'credential_source',
15+
ENDPOINT_URL: 'endpoint_url',
1516
REGION: 'region',
1617
ROLE_ARN: 'role_arn',
1718
SOURCE_PROFILE: 'source_profile',

packages/core/src/auth/credentials/utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { isValidResponse } from '../../shared/wizards/wizard'
2121
const credentialsTimeout = 300000 // 5 minutes
2222
const credentialsProgressDelay = 1000
2323

24-
export function asEnvironmentVariables(credentials: Credentials): NodeJS.ProcessEnv {
24+
export function asEnvironmentVariables(credentials: Credentials, endpointUrl?: string): NodeJS.ProcessEnv {
2525
const environmentVariables: NodeJS.ProcessEnv = {}
2626

2727
environmentVariables.AWS_ACCESS_KEY = credentials.accessKeyId
@@ -30,6 +30,9 @@ export function asEnvironmentVariables(credentials: Credentials): NodeJS.Process
3030
environmentVariables.AWS_SECRET_ACCESS_KEY = credentials.secretAccessKey
3131
environmentVariables.AWS_SESSION_TOKEN = credentials.sessionToken
3232
environmentVariables.AWS_SECURITY_TOKEN = credentials.sessionToken
33+
if (endpointUrl !== undefined) {
34+
environmentVariables.AWS_ENDPOINT_URL = endpointUrl
35+
}
3336

3437
return environmentVariables
3538
}

packages/core/src/auth/deprecated/loginManager.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ import { isAutomation } from '../../shared/vscode/env'
3030
import { Credentials } from '@aws-sdk/types'
3131
import { ToolkitError } from '../../shared/errors'
3232
import * as localizedText from '../../shared/localizedText'
33-
import { DefaultStsClient } from '../../shared/clients/stsClient'
33+
import { DefaultStsClient, type GetCallerIdentityResponse } from '../../shared/clients/stsClient'
3434
import { findAsync } from '../../shared/utilities/collectionUtils'
3535
import { telemetry } from '../../shared/telemetry/telemetry'
3636
import { withTelemetryContext } from '../../shared/telemetry/util'
37+
import { localStackConnectionHeader, localStackConnectionString } from '../utils'
3738

3839
const loginManagerClassName = 'LoginManager'
3940
/**
@@ -65,19 +66,19 @@ export class LoginManager {
6566

6667
try {
6768
provider = await getProvider(args.providerId)
68-
69-
const credentials = (await this.store.upsertCredentials(args.providerId, provider))?.credentials
69+
const { credentials, endpointUrl } = await this.store.upsertCredentials(args.providerId, provider)
7070
if (!credentials) {
7171
throw new Error(`No credentials found for id ${asString(args.providerId)}`)
7272
}
7373

74-
const accountId = await this.validateCredentials(credentials, provider.getDefaultRegion())
74+
const accountId = await this.validateCredentials(credentials, endpointUrl, provider.getDefaultRegion())
7575
this.awsContext.credentialsShim = createCredentialsShim(this.store, args.providerId, credentials)
7676
await this.awsContext.setCredentials({
7777
credentials,
7878
accountId: accountId,
7979
credentialsId: asString(args.providerId),
8080
defaultRegion: provider.getDefaultRegion(),
81+
endpointUrl: provider.getEndpointUrl?.(),
8182
})
8283

8384
telemetryResult = 'Succeeded'
@@ -111,16 +112,40 @@ export class LoginManager {
111112
}
112113
}
113114

114-
public async validateCredentials(credentials: Credentials, region = this.defaultCredentialsRegion) {
115-
const stsClient = new DefaultStsClient(region, credentials)
116-
const accountId = (await stsClient.getCallerIdentity()).Account
115+
public async validateCredentials(
116+
credentials: Credentials,
117+
endpointUrl?: string,
118+
region = this.defaultCredentialsRegion
119+
) {
120+
const stsClient = new DefaultStsClient(region, credentials, endpointUrl)
121+
const callerIdentity = await stsClient.getCallerIdentity()
122+
await this.detectExternalConnection(callerIdentity)
123+
// Validate presence of Account Id
124+
const accountId = callerIdentity.Account
117125
if (!accountId) {
126+
if (endpointUrl !== undefined) {
127+
telemetry.auth_customEndpoint.emit({ source: 'validateCredentials', result: 'Failed' })
128+
}
118129
throw new Error('Could not determine Account Id for credentials')
119130
}
131+
if (endpointUrl !== undefined) {
132+
telemetry.auth_customEndpoint.emit({ source: 'validateCredentials', result: 'Succeeded' })
133+
}
120134

121135
return accountId
122136
}
123137

138+
private async detectExternalConnection(callerIdentity: GetCallerIdentityResponse): Promise<void> {
139+
// @ts-ignore
140+
const headers = callerIdentity.$response?.httpResponse?.headers
141+
if (headers !== undefined && localStackConnectionHeader in headers) {
142+
await globals.globalState.update('aws.toolkit.externalConnection', localStackConnectionString)
143+
telemetry.auth_localstackEndpoint.emit({ source: 'validateCredentials', result: 'Succeeded' })
144+
} else {
145+
await globals.globalState.update('aws.toolkit.externalConnection', undefined)
146+
}
147+
}
148+
124149
/**
125150
* Removes Credentials from the Toolkit. Essentially the Toolkit becomes "logged out".
126151
*

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ export interface CredentialsProvider {
112112
*/
113113
getTelemetryType(): CredentialType
114114
getDefaultRegion(): string | undefined
115+
/**
116+
* Gets the endpoint URL configured for this profile, if any.
117+
*/
118+
getEndpointUrl?(): string | undefined
115119
getHashCode(): string
116120
getCredentials(): Promise<AWS.Credentials>
117121
/**

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,9 @@ export class EnvVarsCredentialsProvider implements CredentialsProvider {
6161
}
6262
return this.credentials
6363
}
64+
65+
public getEndpointUrl(): string | undefined {
66+
const env = process.env as EnvironmentVariables
67+
return env.AWS_ENDPOINT_URL?.toString()
68+
}
6469
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ export class SharedCredentialsProvider implements CredentialsProvider {
105105
return this.profile[SharedCredentialsKeys.REGION]
106106
}
107107

108+
public getEndpointUrl(): string | undefined {
109+
return this.profile[SharedCredentialsKeys.ENDPOINT_URL]?.trim()
110+
}
111+
108112
public async canAutoConnect(): Promise<boolean> {
109113
if (isSsoProfile(this.profile)) {
110114
const tokenProvider = SsoAccessTokenProvider.create({

0 commit comments

Comments
 (0)