From 8c0b29eabfe0ce8dbd2cbdcfb8b0a31b003bc3f2 Mon Sep 17 00:00:00 2001 From: George Fu Date: Fri, 9 Feb 2024 11:41:54 -0500 Subject: [PATCH] fix(credential-provider-node): pass client region to inner credential client region (#5758) test: sts does not resolve aws auth config --- clients/client-sts/src/defaultRoleAssumers.ts | 5 +- .../client-sts/src/defaultStsRoleAssumers.ts | 74 +++++++--- .../codegen/sts-client-defaultRoleAssumers.ts | 5 +- .../sts-client-defaultStsRoleAssumers.ts | 74 +++++++--- .../aws_sdk/resolveAwsSdkSigV4Config.ts | 12 +- .../src/fromCognitoIdentity.ts | 9 +- .../src/fromCognitoIdentityPool.ts | 7 +- .../src/resolveAssumeRoleCredentials.ts | 9 +- .../src/resolveWebIdentityCredentials.ts | 1 + .../credential-provider-node.integ.spec.ts | 136 +++++++++++------- .../src/fromWebToken.ts | 9 +- .../src/awsAuthConfiguration.ts | 12 +- packages/types/src/credentials.ts | 17 +++ 13 files changed, 268 insertions(+), 102 deletions(-) diff --git a/clients/client-sts/src/defaultRoleAssumers.ts b/clients/client-sts/src/defaultRoleAssumers.ts index 9e795410e386..280c553a559b 100644 --- a/clients/client-sts/src/defaultRoleAssumers.ts +++ b/clients/client-sts/src/defaultRoleAssumers.ts @@ -9,6 +9,7 @@ import { getDefaultRoleAssumerWithWebIdentity as StsGetDefaultRoleAssumerWithWebIdentity, RoleAssumer, RoleAssumerWithWebIdentity, + STSRoleAssumerOptions, } from "./defaultStsRoleAssumers"; import { ServiceInputTypes, ServiceOutputTypes, STSClient, STSClientConfig } from "./STSClient"; @@ -32,7 +33,7 @@ const getCustomizableStsClientCtor = ( * The default role assumer that used by credential providers when sts:AssumeRole API is needed. */ export const getDefaultRoleAssumer = ( - stsOptions: Pick = {}, + stsOptions: STSRoleAssumerOptions = {}, stsPlugins?: Pluggable[] ): RoleAssumer => StsGetDefaultRoleAssumer(stsOptions, getCustomizableStsClientCtor(STSClient, stsPlugins)); @@ -40,7 +41,7 @@ export const getDefaultRoleAssumer = ( * The default role assumer that used by credential providers when sts:AssumeRoleWithWebIdentity API is needed. */ export const getDefaultRoleAssumerWithWebIdentity = ( - stsOptions: Pick = {}, + stsOptions: STSRoleAssumerOptions = {}, stsPlugins?: Pluggable[] ): RoleAssumerWithWebIdentity => StsGetDefaultRoleAssumerWithWebIdentity(stsOptions, getCustomizableStsClientCtor(STSClient, stsPlugins)); diff --git a/clients/client-sts/src/defaultStsRoleAssumers.ts b/clients/client-sts/src/defaultStsRoleAssumers.ts index cdee4e2df752..aeee276e00f4 100644 --- a/clients/client-sts/src/defaultStsRoleAssumers.ts +++ b/clients/client-sts/src/defaultStsRoleAssumers.ts @@ -1,7 +1,9 @@ // smithy-typescript generated code // Please do not touch this file. It's generated from template in: // https://github.com/aws/aws-sdk-js-v3/blob/main/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts -import { AwsCredentialIdentity, Provider } from "@smithy/types"; +import type { CredentialProviderOptions } from "@aws-sdk/types"; +import { partition } from "@aws-sdk/util-endpoints"; +import { AwsCredentialIdentity, Logger, Provider } from "@smithy/types"; import { AssumeRoleCommand, AssumeRoleCommandInput } from "./commands/AssumeRoleCommand"; import { @@ -10,6 +12,14 @@ import { } from "./commands/AssumeRoleWithWebIdentityCommand"; import type { STSClient, STSClientConfig, STSClientResolvedConfig } from "./STSClient"; +/** + * @public + */ +export type STSRoleAssumerOptions = Pick & { + credentialProviderLogger?: Logger; + parentClientConfig?: CredentialProviderOptions["parentClientConfig"]; +}; + /** * @internal */ @@ -21,19 +31,37 @@ export type RoleAssumer = ( const ASSUME_ROLE_DEFAULT_REGION = "us-east-1"; /** - * Inject the fallback STS region of us-east-1. + * @internal + * + * Default to the us-east-1 region for aws partition, + * or default to the parent client region otherwise. */ -const decorateDefaultRegion = (region: string | Provider | undefined): string | Provider => { - if (typeof region !== "function") { - return region === undefined ? ASSUME_ROLE_DEFAULT_REGION : region; +const resolveRegion = async ( + _region: string | Provider | undefined, + _parentRegion: string | Provider | undefined, + credentialProviderLogger?: Logger +): Promise => { + const region: string | undefined = typeof _region === "function" ? await _region() : _region; + const parentRegion: string | undefined = typeof _parentRegion === "function" ? await _parentRegion() : _parentRegion; + + if (!parentRegion || partition(parentRegion).name === "aws") { + credentialProviderLogger?.debug?.( + "@aws-sdk/client-sts::resolveRegion", + "accepting first of:", + `${region} (provider)`, + `${ASSUME_ROLE_DEFAULT_REGION} (STS default)` + ); + return region ?? ASSUME_ROLE_DEFAULT_REGION; + } else { + credentialProviderLogger?.debug?.( + "@aws-sdk/client-sts::resolveRegion", + "accepting first of:", + `${region} (provider)`, + `${parentRegion} (parent client)`, + `${ASSUME_ROLE_DEFAULT_REGION} (STS default)` + ); + return region ?? parentRegion ?? ASSUME_ROLE_DEFAULT_REGION; } - return async () => { - try { - return await region(); - } catch (e) { - return ASSUME_ROLE_DEFAULT_REGION; - } - }; }; /** @@ -41,7 +69,7 @@ const decorateDefaultRegion = (region: string | Provider | undefined): s * @internal */ export const getDefaultRoleAssumer = ( - stsOptions: Pick, + stsOptions: STSRoleAssumerOptions, stsClientCtor: new (options: STSClientConfig) => STSClient ): RoleAssumer => { let stsClient: STSClient; @@ -49,12 +77,17 @@ export const getDefaultRoleAssumer = ( return async (sourceCreds, params) => { closureSourceCreds = sourceCreds; if (!stsClient) { - const { logger, region, requestHandler } = stsOptions; + const { logger, region, requestHandler, credentialProviderLogger } = stsOptions; + const resolvedRegion = await resolveRegion( + region, + stsOptions?.parentClientConfig?.region, + credentialProviderLogger + ); stsClient = new stsClientCtor({ logger, // A hack to make sts client uses the credential in current closure. credentialDefaultProvider: () => async () => closureSourceCreds, - region: decorateDefaultRegion(region || stsOptions.region), + region: resolvedRegion, ...(requestHandler ? { requestHandler } : {}), }); } @@ -85,16 +118,21 @@ export type RoleAssumerWithWebIdentity = ( * @internal */ export const getDefaultRoleAssumerWithWebIdentity = ( - stsOptions: Pick, + stsOptions: STSRoleAssumerOptions, stsClientCtor: new (options: STSClientConfig) => STSClient ): RoleAssumerWithWebIdentity => { let stsClient: STSClient; return async (params) => { if (!stsClient) { - const { logger, region, requestHandler } = stsOptions; + const { logger, region, requestHandler, credentialProviderLogger } = stsOptions; + const resolvedRegion = await resolveRegion( + region, + stsOptions?.parentClientConfig?.region, + credentialProviderLogger + ); stsClient = new stsClientCtor({ logger, - region: decorateDefaultRegion(region || stsOptions.region), + region: resolvedRegion, ...(requestHandler ? { requestHandler } : {}), }); } diff --git a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultRoleAssumers.ts b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultRoleAssumers.ts index 4409fb38ab5b..b6e9dc76c6be 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultRoleAssumers.ts +++ b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultRoleAssumers.ts @@ -6,6 +6,7 @@ import { getDefaultRoleAssumerWithWebIdentity as StsGetDefaultRoleAssumerWithWebIdentity, RoleAssumer, RoleAssumerWithWebIdentity, + STSRoleAssumerOptions, } from "./defaultStsRoleAssumers"; import { ServiceInputTypes, ServiceOutputTypes, STSClient, STSClientConfig } from "./STSClient"; @@ -29,7 +30,7 @@ const getCustomizableStsClientCtor = ( * The default role assumer that used by credential providers when sts:AssumeRole API is needed. */ export const getDefaultRoleAssumer = ( - stsOptions: Pick = {}, + stsOptions: STSRoleAssumerOptions = {}, stsPlugins?: Pluggable[] ): RoleAssumer => StsGetDefaultRoleAssumer(stsOptions, getCustomizableStsClientCtor(STSClient, stsPlugins)); @@ -37,7 +38,7 @@ export const getDefaultRoleAssumer = ( * The default role assumer that used by credential providers when sts:AssumeRoleWithWebIdentity API is needed. */ export const getDefaultRoleAssumerWithWebIdentity = ( - stsOptions: Pick = {}, + stsOptions: STSRoleAssumerOptions = {}, stsPlugins?: Pluggable[] ): RoleAssumerWithWebIdentity => StsGetDefaultRoleAssumerWithWebIdentity(stsOptions, getCustomizableStsClientCtor(STSClient, stsPlugins)); diff --git a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts index fb22ebccb7bc..adc8a2ba58aa 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts +++ b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts @@ -1,4 +1,6 @@ -import { AwsCredentialIdentity, Provider } from "@smithy/types"; +import type { CredentialProviderOptions } from "@aws-sdk/types"; +import { partition } from "@aws-sdk/util-endpoints"; +import { AwsCredentialIdentity, Logger, Provider } from "@smithy/types"; import { AssumeRoleCommand, AssumeRoleCommandInput } from "./commands/AssumeRoleCommand"; import { @@ -7,6 +9,14 @@ import { } from "./commands/AssumeRoleWithWebIdentityCommand"; import type { STSClient, STSClientConfig, STSClientResolvedConfig } from "./STSClient"; +/** + * @public + */ +export type STSRoleAssumerOptions = Pick & { + credentialProviderLogger?: Logger; + parentClientConfig?: CredentialProviderOptions["parentClientConfig"]; +}; + /** * @internal */ @@ -18,19 +28,37 @@ export type RoleAssumer = ( const ASSUME_ROLE_DEFAULT_REGION = "us-east-1"; /** - * Inject the fallback STS region of us-east-1. + * @internal + * + * Default to the us-east-1 region for aws partition, + * or default to the parent client region otherwise. */ -const decorateDefaultRegion = (region: string | Provider | undefined): string | Provider => { - if (typeof region !== "function") { - return region === undefined ? ASSUME_ROLE_DEFAULT_REGION : region; +const resolveRegion = async ( + _region: string | Provider | undefined, + _parentRegion: string | Provider | undefined, + credentialProviderLogger?: Logger +): Promise => { + const region: string | undefined = typeof _region === "function" ? await _region() : _region; + const parentRegion: string | undefined = typeof _parentRegion === "function" ? await _parentRegion() : _parentRegion; + + if (!parentRegion || partition(parentRegion).name === "aws") { + credentialProviderLogger?.debug?.( + "@aws-sdk/client-sts::resolveRegion", + "accepting first of:", + `${region} (provider)`, + `${ASSUME_ROLE_DEFAULT_REGION} (STS default)` + ); + return region ?? ASSUME_ROLE_DEFAULT_REGION; + } else { + credentialProviderLogger?.debug?.( + "@aws-sdk/client-sts::resolveRegion", + "accepting first of:", + `${region} (provider)`, + `${parentRegion} (parent client)`, + `${ASSUME_ROLE_DEFAULT_REGION} (STS default)` + ); + return region ?? parentRegion ?? ASSUME_ROLE_DEFAULT_REGION; } - return async () => { - try { - return await region(); - } catch (e) { - return ASSUME_ROLE_DEFAULT_REGION; - } - }; }; /** @@ -38,7 +66,7 @@ const decorateDefaultRegion = (region: string | Provider | undefined): s * @internal */ export const getDefaultRoleAssumer = ( - stsOptions: Pick, + stsOptions: STSRoleAssumerOptions, stsClientCtor: new (options: STSClientConfig) => STSClient ): RoleAssumer => { let stsClient: STSClient; @@ -46,12 +74,17 @@ export const getDefaultRoleAssumer = ( return async (sourceCreds, params) => { closureSourceCreds = sourceCreds; if (!stsClient) { - const { logger, region, requestHandler } = stsOptions; + const { logger, region, requestHandler, credentialProviderLogger } = stsOptions; + const resolvedRegion = await resolveRegion( + region, + stsOptions?.parentClientConfig?.region, + credentialProviderLogger + ); stsClient = new stsClientCtor({ logger, // A hack to make sts client uses the credential in current closure. credentialDefaultProvider: () => async () => closureSourceCreds, - region: decorateDefaultRegion(region || stsOptions.region), + region: resolvedRegion, ...(requestHandler ? { requestHandler } : {}), }); } @@ -82,16 +115,21 @@ export type RoleAssumerWithWebIdentity = ( * @internal */ export const getDefaultRoleAssumerWithWebIdentity = ( - stsOptions: Pick, + stsOptions: STSRoleAssumerOptions, stsClientCtor: new (options: STSClientConfig) => STSClient ): RoleAssumerWithWebIdentity => { let stsClient: STSClient; return async (params) => { if (!stsClient) { - const { logger, region, requestHandler } = stsOptions; + const { logger, region, requestHandler, credentialProviderLogger } = stsOptions; + const resolvedRegion = await resolveRegion( + region, + stsOptions?.parentClientConfig?.region, + credentialProviderLogger + ); stsClient = new stsClientCtor({ logger, - region: decorateDefaultRegion(region || stsOptions.region), + region: resolvedRegion, ...(requestHandler ? { requestHandler } : {}), }); } diff --git a/packages/core/src/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts b/packages/core/src/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts index 7b01c9980e9c..c70d7a9e37d5 100644 --- a/packages/core/src/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts +++ b/packages/core/src/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts @@ -111,9 +111,17 @@ export const resolveAwsSdkSigV4Config = ( // credentialDefaultProvider should always be populated, but in case // it isn't, set a default identity provider that throws an error if (config.credentialDefaultProvider) { - normalizedCreds = normalizeProvider(config.credentialDefaultProvider(config as any)); + normalizedCreds = normalizeProvider( + config.credentialDefaultProvider( + Object.assign({}, config as any, { + parentClientConfig: config, + }) + ) + ); } else { - normalizedCreds = async () => { throw new Error("`credentials` is missing") }; + normalizedCreds = async () => { + throw new Error("`credentials` is missing"); + }; } } diff --git a/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts b/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts index d61e238f5dfd..00651eacbf4d 100644 --- a/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts +++ b/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts @@ -40,7 +40,14 @@ export function fromCognitoIdentity(parameters: FromCognitoIdentityParameters): SecretKey = throwOnMissingSecretKey(), SessionToken, } = throwOnMissingCredentials(), - } = await (parameters.client ?? new CognitoIdentityClient(parameters.clientConfig ?? {})).send( + } = await ( + parameters.client ?? + new CognitoIdentityClient( + Object.assign({}, parameters.clientConfig ?? {}, { + region: parameters.clientConfig?.region ?? parameters.parentClientConfig?.region, + }) + ) + ).send( new GetCredentialsForIdentityCommand({ CustomRoleArn: parameters.customRoleArn, IdentityId: parameters.identityId, diff --git a/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts b/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts index d38755510db4..fc1e1db9e925 100644 --- a/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts +++ b/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts @@ -27,6 +27,7 @@ export function fromCognitoIdentityPool({ logins, userIdentifier = !logins || Object.keys(logins).length === 0 ? "ANONYMOUS" : undefined, logger, + parentClientConfig, }: FromCognitoIdentityPoolParameters): CognitoIdentityCredentialProvider { logger?.debug("@aws-sdk/credential-provider-cognito-identity", "fromCognitoIdentity"); const cacheKey: string | undefined = userIdentifier @@ -35,7 +36,11 @@ export function fromCognitoIdentityPool({ let provider: CognitoIdentityCredentialProvider = async () => { const { GetIdCommand, CognitoIdentityClient } = await import("./loadCognitoIdentity"); - const _client = client ?? new CognitoIdentityClient(clientConfig ?? {}); + const _client = + client ?? + new CognitoIdentityClient( + Object.assign({}, clientConfig ?? {}, { region: clientConfig?.region ?? parentClientConfig?.region }) + ); let identityId: string | undefined = (cacheKey && (await cache.getItem(cacheKey))) as string | undefined; if (!identityId) { diff --git a/packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts b/packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts index b107c043aab2..11157f851144 100644 --- a/packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts +++ b/packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts @@ -88,7 +88,14 @@ export const resolveAssumeRoleCredentials = async ( if (!options.roleAssumer) { const { getDefaultRoleAssumer } = await import("./loadSts"); - options.roleAssumer = getDefaultRoleAssumer(options.clientConfig, options.clientPlugins); + options.roleAssumer = getDefaultRoleAssumer( + { + ...options.clientConfig, + credentialProviderLogger: options.logger, + parentClientConfig: options?.parentClientConfig, + }, + options.clientPlugins + ); } const { source_profile } = data; diff --git a/packages/credential-provider-ini/src/resolveWebIdentityCredentials.ts b/packages/credential-provider-ini/src/resolveWebIdentityCredentials.ts index 938db28e7fe1..8eef85d465c4 100644 --- a/packages/credential-provider-ini/src/resolveWebIdentityCredentials.ts +++ b/packages/credential-provider-ini/src/resolveWebIdentityCredentials.ts @@ -35,5 +35,6 @@ export const resolveWebIdentityCredentials = async ( roleSessionName: profile.role_session_name, roleAssumerWithWebIdentity: options.roleAssumerWithWebIdentity, logger: options.logger, + parentClientConfig: options.parentClientConfig, })() ); diff --git a/packages/credential-provider-node/src/credential-provider-node.integ.spec.ts b/packages/credential-provider-node/src/credential-provider-node.integ.spec.ts index cf322b536128..f7beb68cd4dd 100644 --- a/packages/credential-provider-node/src/credential-provider-node.integ.spec.ts +++ b/packages/credential-provider-node/src/credential-provider-node.integ.spec.ts @@ -67,58 +67,41 @@ jest.mock("@aws-sdk/client-sso", () => { }; }); +let stsSpy: jest.Spied | any | undefined = undefined; + jest.mock("@aws-sdk/client-sts", () => { const actual = jest.requireActual("@aws-sdk/client-sts"); - return { - ...actual, - getDefaultRoleAssumer() { - return async () => { - return { - accessKeyId: "STS_AR_ACCESS_KEY_ID", - secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", - sessionToken: "STS_AR_SESSION_TOKEN", - expiration: new Date("3000/1/1"), - credentialScope: "us-stsar-1", - }; + + const originalSend = actual.STSClient.prototype.send; + + stsSpy = jest.spyOn(actual.STSClient.prototype, "send").mockImplementation(async function (command: any) { + if (command.constructor.name === "AssumeRoleCommand") { + return { + Credentials: { + AccessKeyId: "STS_AR_ACCESS_KEY_ID", + SecretAccessKey: "STS_AR_SECRET_ACCESS_KEY", + SessionToken: "STS_AR_SESSION_TOKEN", + Expiration: new Date("3000/1/1"), + CredentialScope: "us-stsar-1__" + (await this.config.region()), + }, }; - }, - getDefaultRoleAssumerWithWebIdentity() { - return async () => { - return { - accessKeyId: "STS_ARWI_ACCESS_KEY_ID", - secretAccessKey: "STS_ARWI_SECRET_ACCESS_KEY", - sessionToken: "STS_ARWI_SESSION_TOKEN", - expiration: new Date("3000/1/1"), - credentialScope: "us-stsarwi-1", - }; + } + if (command.constructor.name === "AssumeRoleWithWebIdentityCommand") { + return { + Credentials: { + AccessKeyId: "STS_ARWI_ACCESS_KEY_ID", + SecretAccessKey: "STS_ARWI_SECRET_ACCESS_KEY", + SessionToken: "STS_ARWI_SESSION_TOKEN", + Expiration: new Date("3000/1/1"), + CredentialScope: "us-stsarwi-1__" + (await this.config.region()), + }, }; - }, - STSClient: class { - async send(command: any) { - if (command.constructor.name === "AssumeRoleCommand") { - return { - Credentials: { - AccessKeyId: "STS_AR_ACCESS_KEY_ID", - SecretAccessKey: "STS_AR_SECRET_ACCESS_KEY", - SessionToken: "STS_AR_SESSION_TOKEN", - Expiration: new Date("3000/1/1"), - CredentialScope: "us-stsar-1", - }, - }; - } - if (command.constructor.name === "AssumeRoleWithWebIdentityCommand") { - return { - Credentials: { - AccessKeyId: "STS_ARWI_ACCESS_KEY_ID", - SecretAccessKey: "STS_ARWI_SECRET_ACCESS_KEY", - SessionToken: "STS_ARWI_SESSION_TOKEN", - Expiration: new Date("3000/1/1"), - CredentialScope: "us-stsarwi-1", - }, - }; - } - } - }, + } + return originalSend.bind(this)(command); + }); + + return { + ...actual, }; }); @@ -266,6 +249,7 @@ describe("credential-provider-node integration test", () => { afterAll(async () => { jest.clearAllMocks(); jest.clearAllTimers(); + stsSpy?.mockRestore(); }); describe("fromEnv", () => { @@ -356,7 +340,35 @@ describe("credential-provider-node integration test", () => { secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", sessionToken: "STS_AR_SESSION_TOKEN", expiration: new Date("3000/1/1"), - credentialScope: "us-stsar-1", + credentialScope: "us-stsar-1__us-east-1", + }); + }); + + it("should use the outer client's region for STS when the partition is not AWS", async () => { + sts = new STS({ + region: "us-gov-stsar-1", + requestHandler: mockRequestHandler, + }); + iniProfileData.assume = { + region: "us-gov-stsar-1", + aws_access_key_id: "ASSUME_STATIC_ACCESS_KEY", + aws_secret_access_key: "ASSUME_STATIC_SECRET_KEY", + }; + Object.assign(iniProfileData.default, { + region: "us-gov-stsar-1", + role_arn: "ROLE_ARN", + role_session_name: "ROLE_SESSION_NAME", + external_id: "EXTERNAL_ID", + source_profile: "assume", + }); + await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); + expect(credentials).toEqual({ + accessKeyId: "STS_AR_ACCESS_KEY_ID", + secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", + sessionToken: "STS_AR_SESSION_TOKEN", + expiration: new Date("3000/1/1"), + credentialScope: "us-stsar-1__us-gov-stsar-1", }); }); @@ -376,13 +388,29 @@ describe("credential-provider-node integration test", () => { secretAccessKey: "STS_ARWI_SECRET_ACCESS_KEY", sessionToken: "STS_ARWI_SESSION_TOKEN", expiration: new Date("3000/1/1"), - credentialScope: "us-stsarwi-1", + credentialScope: "us-stsarwi-1__us-east-1", }); }); - xit("should resolve credentials from STS assumeRoleWithWebIdentity if the ini profile is configured for web identity and the client region is not the default AWS partition", async () => { - // TODO - // this is difficult to do when getDefaultRoleAssumerWithWebIdentity is mocked. + it("should resolve credentials from STS assumeRoleWithWebIdentity if the ini profile is configured for web identity and the client region is not the default AWS partition", async () => { + sts = new STS({ + region: "us-gov-sts-1", + requestHandler: mockRequestHandler, + }); + Object.assign(iniProfileData.default, { + region: "us-gov-sts-1", + web_identity_token_file: "token-filepath", + role_arn: "ROLE_ARN", + }); + await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); + expect(credentials).toEqual({ + accessKeyId: "STS_ARWI_ACCESS_KEY_ID", + secretAccessKey: "STS_ARWI_SECRET_ACCESS_KEY", + sessionToken: "STS_ARWI_SESSION_TOKEN", + expiration: new Date("3000/1/1"), + credentialScope: "us-stsarwi-1__us-gov-sts-1", + }); }); it("should resolve process credentials if the profile is a process profile", async () => { @@ -454,7 +482,7 @@ describe("credential-provider-node integration test", () => { secretAccessKey: "STS_ARWI_SECRET_ACCESS_KEY", sessionToken: "STS_ARWI_SESSION_TOKEN", expiration: new Date("3000/1/1"), - credentialScope: "us-stsarwi-1", + credentialScope: "us-stsarwi-1__us-east-1", }); }); }); diff --git a/packages/credential-provider-web-identity/src/fromWebToken.ts b/packages/credential-provider-web-identity/src/fromWebToken.ts index 92d745c750e4..55e5e6ab87fd 100644 --- a/packages/credential-provider-web-identity/src/fromWebToken.ts +++ b/packages/credential-provider-web-identity/src/fromWebToken.ts @@ -161,7 +161,14 @@ export const fromWebToken = if (!roleAssumerWithWebIdentity) { const { getDefaultRoleAssumerWithWebIdentity } = await import("./loadSts"); - roleAssumerWithWebIdentity = getDefaultRoleAssumerWithWebIdentity(init.clientConfig, init.clientPlugins); + roleAssumerWithWebIdentity = getDefaultRoleAssumerWithWebIdentity( + { + ...init.clientConfig, + credentialProviderLogger: init.logger, + parentClientConfig: init.parentClientConfig, + }, + init.clientPlugins + ); } return roleAssumerWithWebIdentity!({ diff --git a/packages/middleware-signing/src/awsAuthConfiguration.ts b/packages/middleware-signing/src/awsAuthConfiguration.ts index d0be2b382fe4..f9d0ab4b977c 100644 --- a/packages/middleware-signing/src/awsAuthConfiguration.ts +++ b/packages/middleware-signing/src/awsAuthConfiguration.ts @@ -133,7 +133,11 @@ export const resolveAwsAuthConfig = ( ): T & AwsAuthResolvedConfig => { const normalizedCreds = input.credentials ? normalizeCredentialProvider(input.credentials) - : input.credentialDefaultProvider(input as any); + : input.credentialDefaultProvider( + Object.assign({}, input, { + parentClientConfig: input, + }) + ); const { signingEscapePath = true, systemClockOffset = input.systemClockOffset || 0, sha256 } = input; let signer: (authScheme?: AuthScheme) => Promise; if (input.signer) { @@ -228,7 +232,11 @@ export const resolveSigV4AuthConfig = ( ): T & SigV4AuthResolvedConfig => { const normalizedCreds = input.credentials ? normalizeCredentialProvider(input.credentials) - : input.credentialDefaultProvider(input as any); + : input.credentialDefaultProvider( + Object.assign({}, input, { + parentClientConfig: input, + }) + ); const { signingEscapePath = true, systemClockOffset = input.systemClockOffset || 0, sha256 } = input; let signer: Provider; if (input.signer) { diff --git a/packages/types/src/credentials.ts b/packages/types/src/credentials.ts index fdd6d1a0c805..4ea990d81505 100644 --- a/packages/types/src/credentials.ts +++ b/packages/types/src/credentials.ts @@ -32,4 +32,21 @@ export type CredentialProviderOptions = { * It does not log credentials. */ logger?: Logger; + + /** + * Present if the credential provider was created by calling + * the defaultCredentialProvider in a client's middleware, having + * access to the client's config. + * + * The region of that parent or outer client is important because + * an inner client used by the credential provider may need + * to match its default partition or region with that of + * the outer client. + * + * @internal + * @deprecated - not truly deprecated, marked as a warning to not use this. + */ + parentClientConfig?: { + region?: string | Provider; + }; };