From 7c395474f0d23ad3d999e42e8fb4c439c51564dc Mon Sep 17 00:00:00 2001 From: George Fu Date: Wed, 11 Dec 2024 17:05:04 +0000 Subject: [PATCH 1/8] feat(credential-providers): make credential providers aware of contextual client region --- package.json | 1 + .../aws_sdk/resolveAwsSdkSigV4Config.ts | 22 +++--- .../src/fromCognitoIdentity.ts | 13 +-- .../src/fromCognitoIdentityPool.ts | 9 ++- .../credential-provider-ini/src/fromIni.ts | 14 +++- .../credential-provider-node/package.json | 2 +- .../credential-provider-node.integ.spec.ts | 59 +++++++++++--- .../src/fromWebToken.ts | 18 +++-- packages/credential-providers/src/fromIni.ts | 4 +- packages/middleware-signing/README.md | 3 + .../src/awsAuthConfiguration.ts | 79 +++++++++++++++---- .../src/awsAuthMiddleware.ts | 12 +++ packages/middleware-websocket/package.json | 1 - packages/token-providers/src/fromSso.ts | 18 ++++- .../src/identity/AwsCredentialIdentity.ts | 38 ++++++++- 15 files changed, 231 insertions(+), 62 deletions(-) diff --git a/package.json b/package.json index 4fdc5d8027ec0..43d84e1b47aa1 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "copy-models": "node ./scripts/copy-models", "extract:docs": "node ./scripts/extract-docs/index.js", "g:vitest": "cd $INIT_CWD && vitest", + "g:jest": "cd $INIT_CWD && jest", "generate-clients": "node ./scripts/generate-clients", "generate:clients:generic": "node ./scripts/generate-clients/generic", "generate:defaults-mode-provider": "./scripts/generate-defaults-mode-provider/index.js", diff --git a/packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts b/packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts index 5d19419bccb50..ba3fd5117e5fc 100644 --- a/packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts +++ b/packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts @@ -106,16 +106,16 @@ export const resolveAwsSdkSigV4Config = ( ): T & AwsSdkSigV4AuthResolvedConfig => { let isUserSupplied = false; // Normalize credentials - let normalizedCreds: AwsCredentialIdentityProvider | undefined; + let credentialsProvider: AwsCredentialIdentityProvider | undefined; if (config.credentials) { isUserSupplied = true; - normalizedCreds = memoizeIdentityProvider(config.credentials, isIdentityExpired, doesIdentityRequireRefresh); + credentialsProvider = memoizeIdentityProvider(config.credentials, isIdentityExpired, doesIdentityRequireRefresh); } - if (!normalizedCreds) { + if (!credentialsProvider) { // 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( + credentialsProvider = normalizeProvider( config.credentialDefaultProvider( Object.assign({}, config as any, { parentClientConfig: config, @@ -123,12 +123,16 @@ export const resolveAwsSdkSigV4Config = ( ) ); } else { - normalizedCreds = async () => { + credentialsProvider = async () => { throw new Error("`credentials` is missing"); }; } } + const contextBoundCredentialsProvider = async () => { + return credentialsProvider!({ contextClientConfig: config }); + }; + // Populate sigv4 arguments const { // Default for signingEscapePath @@ -170,7 +174,7 @@ export const resolveAwsSdkSigV4Config = ( const params: SignatureV4Init & SignatureV4CryptoInit = { ...config, - credentials: normalizedCreds!, + credentials: contextBoundCredentialsProvider, region: config.signingRegion, service: config.signingName, sha256, @@ -206,7 +210,7 @@ export const resolveAwsSdkSigV4Config = ( const params: SignatureV4Init & SignatureV4CryptoInit = { ...config, - credentials: normalizedCreds!, + credentials: contextBoundCredentialsProvider, region: config.signingRegion, service: config.signingName, sha256, @@ -224,10 +228,10 @@ export const resolveAwsSdkSigV4Config = ( signingEscapePath, credentials: isUserSupplied ? async () => - normalizedCreds!().then((creds: AttributedAwsCredentialIdentity) => + contextBoundCredentialsProvider!().then((creds: AttributedAwsCredentialIdentity) => setCredentialFeature(creds, "CREDENTIALS_CODE", "e") ) - : normalizedCreds!, + : contextBoundCredentialsProvider!, signer, }; }; diff --git a/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts b/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts index 7b90018f43289..389228053a719 100644 --- a/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts +++ b/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts @@ -1,6 +1,6 @@ -import type { CredentialProviderOptions } from "@aws-sdk/types"; +import type { AwsIdentityProperties, CredentialProviderOptions, RegionalIdentityProvider } from "@aws-sdk/types"; import { CredentialsProviderError } from "@smithy/property-provider"; -import { AwsCredentialIdentity, Logger, Provider } from "@smithy/types"; +import type { AwsCredentialIdentity, Logger } from "@smithy/types"; import { CognitoProviderParameters } from "./CognitoProviderParameters"; import { resolveLogins } from "./resolveLogins"; @@ -18,7 +18,7 @@ export interface CognitoIdentityCredentials extends AwsCredentialIdentity { /** * @internal */ -export type CognitoIdentityCredentialProvider = Provider; +export type CognitoIdentityCredentialProvider = RegionalIdentityProvider; /** * @internal @@ -29,7 +29,7 @@ export type CognitoIdentityCredentialProvider = Provider => { + return async (awsIdentityProperties?: AwsIdentityProperties): Promise => { parameters.logger?.debug("@aws-sdk/credential-provider-cognito-identity - fromCognitoIdentity"); const { GetCredentialsForIdentityCommand, CognitoIdentityClient } = await import("./loadCognitoIdentity"); @@ -44,7 +44,10 @@ export function fromCognitoIdentity(parameters: FromCognitoIdentityParameters): parameters.client ?? new CognitoIdentityClient( Object.assign({}, parameters.clientConfig ?? {}, { - region: parameters.clientConfig?.region ?? parameters.parentClientConfig?.region, + region: + parameters.clientConfig?.region ?? + parameters.parentClientConfig?.region ?? + awsIdentityProperties?.contextClientConfig?.region, }) ) ).send( diff --git a/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts b/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts index 559b281897274..319e1463fff86 100644 --- a/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts +++ b/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts @@ -1,4 +1,4 @@ -import type { CredentialProviderOptions } from "@aws-sdk/types"; +import type { AwsIdentityProperties, CredentialProviderOptions } from "@aws-sdk/types"; import { CredentialsProviderError } from "@smithy/property-provider"; import { Logger } from "@smithy/types"; @@ -35,12 +35,15 @@ export function fromCognitoIdentityPool({ ? `aws:cognito-identity-credentials:${identityPoolId}:${userIdentifier}` : undefined; - let provider: CognitoIdentityCredentialProvider = async () => { + let provider: CognitoIdentityCredentialProvider = async (awsIdentityProperties?: AwsIdentityProperties) => { const { GetIdCommand, CognitoIdentityClient } = await import("./loadCognitoIdentity"); const _client = client ?? new CognitoIdentityClient( - Object.assign({}, clientConfig ?? {}, { region: clientConfig?.region ?? parentClientConfig?.region }) + Object.assign({}, clientConfig ?? {}, { + region: + clientConfig?.region ?? parentClientConfig?.region ?? awsIdentityProperties?.contextClientConfig?.region, + }) ); let identityId: string | undefined = (cacheKey && (await cache.getItem(cacheKey))) as string | undefined; diff --git a/packages/credential-provider-ini/src/fromIni.ts b/packages/credential-provider-ini/src/fromIni.ts index 160e4fbe08c5e..58e001f243473 100644 --- a/packages/credential-provider-ini/src/fromIni.ts +++ b/packages/credential-provider-ini/src/fromIni.ts @@ -1,7 +1,8 @@ import type { AssumeRoleWithWebIdentityParams } from "@aws-sdk/credential-provider-web-identity"; import type { CredentialProviderOptions } from "@aws-sdk/types"; +import type { RegionalAwsCredentialIdentityProvider } from "@aws-sdk/types"; import { getProfileName, parseKnownFiles, SourceProfileInit } from "@smithy/shared-ini-file-loader"; -import type { AwsCredentialIdentity, AwsCredentialIdentityProvider, Pluggable } from "@smithy/types"; +import type { AwsCredentialIdentity, Pluggable } from "@smithy/types"; import { AssumeRoleParams } from "./resolveAssumeRoleCredentials"; import { resolveProfileData } from "./resolveProfileData"; @@ -55,8 +56,15 @@ export interface FromIniInit extends SourceProfileInit, CredentialProviderOption * role assumption and multi-factor authentication. */ export const fromIni = - (init: FromIniInit = {}): AwsCredentialIdentityProvider => - async () => { + (_init: FromIniInit = {}): RegionalAwsCredentialIdentityProvider => + async (props = {}) => { + const init: FromIniInit = { + ..._init, + parentClientConfig: { + region: props.contextClientConfig?.region, + ..._init.parentClientConfig, + }, + }; init.logger?.debug("@aws-sdk/credential-provider-ini - fromIni"); const profiles = await parseKnownFiles(init); return resolveProfileData(getProfileName(init), profiles, init); diff --git a/packages/credential-provider-node/package.json b/packages/credential-provider-node/package.json index 22d01779257d6..300bc406f81fb 100644 --- a/packages/credential-provider-node/package.json +++ b/packages/credential-provider-node/package.json @@ -16,7 +16,7 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "test": "yarn g:vitest run", - "test:integration": "jest -c jest.config.integ.js", + "test:integration": "yarn g:jest -c jest.config.integ.js", "test:watch": "yarn g:vitest watch" }, "keywords": [ 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 68e14f627a262..f377bafacb383 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 @@ -1,5 +1,6 @@ import { STS } from "@aws-sdk/client-sts"; import * as credentialProviderHttp from "@aws-sdk/credential-provider-http"; +import { fromIni } from "@aws-sdk/credential-providers"; import { HttpResponse } from "@smithy/protocol-http"; import type { SourceProfileInit } from "@smithy/shared-ini-file-loader"; import type { HttpRequest, NodeHttpHandlerOptions, ParsedIniData } from "@smithy/types"; @@ -67,6 +68,8 @@ jest.mock("@smithy/node-http-handler", () => { assumeRoleArns.push(request.body.match(/RoleArn=(.*?)&/)?.[1]); } + const region = (request.hostname.match(/sts\.(.*?)\./) || [, "unknown"])[1]; + if (request.headers.Authorization === "container-authorization") { body.write( JSON.stringify({ @@ -94,7 +97,7 @@ jest.mock("@smithy/node-http-handler", () => { STS_ARWI_ACCESS_KEY_ID STS_ARWI_SECRET_ACCESS_KEY - STS_ARWI_SESSION_TOKEN + STS_ARWI_SESSION_TOKEN_${region} 3000-01-01T00:00:00.000Z @@ -109,7 +112,7 @@ jest.mock("@smithy/node-http-handler", () => { STS_AR_ACCESS_KEY_ID STS_AR_SECRET_ACCESS_KEY - STS_AR_SESSION_TOKEN + STS_AR_SESSION_TOKEN_${region} 3000-01-01T00:00:00.000Z @@ -379,7 +382,7 @@ describe("credential-provider-node integration test", () => { expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", - sessionToken: "STS_AR_SESSION_TOKEN", + sessionToken: "STS_AR_SESSION_TOKEN_us-west-2", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { CREDENTIALS_PROFILE_SOURCE_PROFILE: "o", @@ -409,7 +412,7 @@ describe("credential-provider-node integration test", () => { expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", - sessionToken: "STS_AR_SESSION_TOKEN", + sessionToken: "STS_AR_SESSION_TOKEN_eu-west-1", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { CREDENTIALS_PROFILE_SOURCE_PROFILE: "o", @@ -439,9 +442,41 @@ describe("credential-provider-node integration test", () => { expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", - sessionToken: "STS_AR_SESSION_TOKEN", + sessionToken: "STS_AR_SESSION_TOKEN_us-gov-stsar-1", + expiration: new Date("3000-01-01T00:00:00.000Z"), + $source: { + CREDENTIALS_PROFILE_SOURCE_PROFILE: "o", + CREDENTIALS_STS_ASSUME_ROLE: "i", + }, + }); + }); + + it("should use the context client's region for STS even if initialized separately in a code-level provider", async () => { + sts = new STS({ + region: "eu-west-1", + credentials: fromIni(), + }); + iniProfileData.assume = { + region: "eu-west-1", + aws_access_key_id: "ASSUME_STATIC_ACCESS_KEY", + aws_secret_access_key: "ASSUME_STATIC_SECRET_KEY", + }; + Object.assign(iniProfileData.default, { + region: "eu-west-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_eu-west-1", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { + CREDENTIALS_CODE: "e", CREDENTIALS_PROFILE_SOURCE_PROFILE: "o", CREDENTIALS_STS_ASSUME_ROLE: "i", }, @@ -458,7 +493,7 @@ describe("credential-provider-node integration test", () => { expect(credentials).toEqual({ accessKeyId: "STS_ARWI_ACCESS_KEY_ID", secretAccessKey: "STS_ARWI_SECRET_ACCESS_KEY", - sessionToken: "STS_ARWI_SESSION_TOKEN", + sessionToken: "STS_ARWI_SESSION_TOKEN_us-west-2", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN: "q", @@ -484,7 +519,7 @@ describe("credential-provider-node integration test", () => { expect(credentials).toEqual({ accessKeyId: "STS_ARWI_ACCESS_KEY_ID", secretAccessKey: "STS_ARWI_SECRET_ACCESS_KEY", - sessionToken: "STS_ARWI_SESSION_TOKEN", + sessionToken: "STS_ARWI_SESSION_TOKEN_us-gov-sts-1", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN: "q", @@ -562,7 +597,7 @@ describe("credential-provider-node integration test", () => { expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", - sessionToken: "STS_AR_SESSION_TOKEN", + sessionToken: "STS_AR_SESSION_TOKEN_us-west-2", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { CREDENTIALS_CODE: "e", @@ -605,7 +640,7 @@ describe("credential-provider-node integration test", () => { expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", - sessionToken: "STS_AR_SESSION_TOKEN", + sessionToken: "STS_AR_SESSION_TOKEN_us-west-2", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { CREDENTIALS_CODE: "e", @@ -650,7 +685,7 @@ describe("credential-provider-node integration test", () => { expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", - sessionToken: "STS_AR_SESSION_TOKEN", + sessionToken: "STS_AR_SESSION_TOKEN_us-west-2", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { CREDENTIALS_CODE: "e", @@ -702,7 +737,7 @@ describe("credential-provider-node integration test", () => { expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", - sessionToken: "STS_AR_SESSION_TOKEN", + sessionToken: "STS_AR_SESSION_TOKEN_us-west-2", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { CREDENTIALS_CODE: "e", @@ -753,7 +788,7 @@ describe("credential-provider-node integration test", () => { expect(credentials).toEqual({ accessKeyId: "STS_ARWI_ACCESS_KEY_ID", secretAccessKey: "STS_ARWI_SECRET_ACCESS_KEY", - sessionToken: "STS_ARWI_SESSION_TOKEN", + sessionToken: "STS_ARWI_SESSION_TOKEN_us-west-2", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN: "h", diff --git a/packages/credential-provider-web-identity/src/fromWebToken.ts b/packages/credential-provider-web-identity/src/fromWebToken.ts index 166af9a70ce01..5e16e863fd964 100644 --- a/packages/credential-provider-web-identity/src/fromWebToken.ts +++ b/packages/credential-provider-web-identity/src/fromWebToken.ts @@ -1,6 +1,9 @@ -import { setCredentialFeature } from "@aws-sdk/core/client"; -import type { CredentialProviderOptions } from "@aws-sdk/types"; -import type { AwsCredentialIdentity, AwsCredentialIdentityProvider, Pluggable } from "@smithy/types"; +import type { + AwsIdentityProperties, + CredentialProviderOptions, + RegionalAwsCredentialIdentityProvider, +} from "@aws-sdk/types"; +import type { AwsCredentialIdentity, Pluggable } from "@smithy/types"; /** * @public @@ -152,8 +155,8 @@ export interface FromWebTokenInit * @internal */ export const fromWebToken = - (init: FromWebTokenInit): AwsCredentialIdentityProvider => - async () => { + (init: FromWebTokenInit): RegionalAwsCredentialIdentityProvider => + async (awsIdentityProperties?: AwsIdentityProperties) => { init.logger?.debug("@aws-sdk/credential-provider-web-identity - fromWebToken"); const { roleArn, roleSessionName, webIdentityToken, providerId, policyArns, policy, durationSeconds } = init; @@ -166,7 +169,10 @@ export const fromWebToken = { ...init.clientConfig, credentialProviderLogger: init.logger, - parentClientConfig: init.parentClientConfig, + parentClientConfig: { + region: awsIdentityProperties?.contextClientConfig?.region, + ...init.parentClientConfig, + }, }, init.clientPlugins ); diff --git a/packages/credential-providers/src/fromIni.ts b/packages/credential-providers/src/fromIni.ts index e98825009c454..6cc35a8c03ee3 100644 --- a/packages/credential-providers/src/fromIni.ts +++ b/packages/credential-providers/src/fromIni.ts @@ -1,5 +1,5 @@ import { fromIni as _fromIni, FromIniInit } from "@aws-sdk/credential-provider-ini"; -import { AwsCredentialIdentityProvider } from "@smithy/types"; +import type { RegionalAwsCredentialIdentityProvider } from "@aws-sdk/types"; /** * Creates a credential provider function that reads from a shared credentials file at `~/.aws/credentials` and a @@ -40,7 +40,7 @@ import { AwsCredentialIdentityProvider } from "@smithy/types"; * }); * ``` */ -export const fromIni = (init: FromIniInit = {}): AwsCredentialIdentityProvider => +export const fromIni = (init: FromIniInit = {}): RegionalAwsCredentialIdentityProvider => _fromIni({ ...init, }); diff --git a/packages/middleware-signing/README.md b/packages/middleware-signing/README.md index 22cb5d4091e2c..13efc0236a619 100644 --- a/packages/middleware-signing/README.md +++ b/packages/middleware-signing/README.md @@ -2,3 +2,6 @@ [![NPM version](https://img.shields.io/npm/v/@aws-sdk/middleware-signing/latest.svg)](https://www.npmjs.com/package/@aws-sdk/middleware-signing) [![NPM downloads](https://img.shields.io/npm/dm/@aws-sdk/middleware-signing.svg)](https://www.npmjs.com/package/@aws-sdk/middleware-signing) + +This package is deprecated. It is only used in "legacy auth", and no longer used in the +AWS SDK as of the Smithy Reference Architecture implementation of identity and auth. diff --git a/packages/middleware-signing/src/awsAuthConfiguration.ts b/packages/middleware-signing/src/awsAuthConfiguration.ts index 8fc40e1400906..1130754f80531 100644 --- a/packages/middleware-signing/src/awsAuthConfiguration.ts +++ b/packages/middleware-signing/src/awsAuthConfiguration.ts @@ -1,3 +1,4 @@ +import type { AwsCredentialIdentityProvider, RegionalAwsCredentialIdentityProvider } from "@aws-sdk/types"; import { memoize } from "@smithy/property-provider"; import { SignatureV4, SignatureV4CryptoInit, SignatureV4Init } from "@smithy/signature-v4"; import { @@ -23,6 +24,7 @@ const CREDENTIAL_EXPIRE_WINDOW = 300000; /** * @public + * @deprecated only used in legacy auth. */ export interface AwsAuthInputConfig { /** @@ -62,6 +64,7 @@ export interface AwsAuthInputConfig { /** * @public + * @deprecated only used in legacy auth. */ export interface SigV4AuthInputConfig { /** @@ -85,6 +88,10 @@ export interface SigV4AuthInputConfig { systemClockOffset?: number; } +/** + * @internal + * @deprecated only used in legacy auth. + */ interface PreviouslyResolved { credentialDefaultProvider: (input: any) => MemoizedProvider; region: string | Provider; @@ -97,6 +104,10 @@ interface PreviouslyResolved { useDualstackEndpoint: Provider; } +/** + * @internal + * @deprecated only used in legacy auth. + */ interface SigV4PreviouslyResolved { credentialDefaultProvider: (input: any) => MemoizedProvider; region: string | Provider; @@ -105,6 +116,10 @@ interface SigV4PreviouslyResolved { logger?: Logger; } +/** + * @internal + * @deprecated only used in legacy auth. + */ export interface AwsAuthResolvedConfig { /** * Resolved value for input config {@link AwsAuthInputConfig.credentials} @@ -126,18 +141,21 @@ export interface AwsAuthResolvedConfig { systemClockOffset: number; } +/** + * @internal + * @deprecated only used in legacy auth. + */ export interface SigV4AuthResolvedConfig extends AwsAuthResolvedConfig {} +/** + * @internal + * @deprecated only used in legacy auth. + */ export const resolveAwsAuthConfig = ( input: T & AwsAuthInputConfig & PreviouslyResolved ): T & AwsAuthResolvedConfig => { - const normalizedCreds = input.credentials - ? normalizeCredentialProvider(input.credentials) - : input.credentialDefaultProvider( - Object.assign({}, input, { - parentClientConfig: input, - }) - ); + const normalizedCreds = createConfigBoundCredentialProvider(input); + const { signingEscapePath = true, systemClockOffset = input.systemClockOffset || 0, sha256 } = input; let signer: (authScheme?: AuthScheme) => Promise; if (input.signer) { @@ -237,17 +255,14 @@ export const resolveAwsAuthConfig = ( }; }; -// TODO: reduce code duplication +/** + * @internal + * @deprecated only used in legacy auth. + */ export const resolveSigV4AuthConfig = ( input: T & SigV4AuthInputConfig & SigV4PreviouslyResolved ): T & SigV4AuthResolvedConfig => { - const normalizedCreds = input.credentials - ? normalizeCredentialProvider(input.credentials) - : input.credentialDefaultProvider( - Object.assign({}, input, { - parentClientConfig: input, - }) - ); + const normalizedCreds = createConfigBoundCredentialProvider(input); const { signingEscapePath = true, systemClockOffset = input.systemClockOffset || 0, sha256 } = input; let signer: Provider; if (input.signer) { @@ -273,8 +288,12 @@ export const resolveSigV4AuthConfig = ( }; }; +/** + * @internal + * @deprecated only used in legacy auth. + */ const normalizeCredentialProvider = ( - credentials: AwsCredentialIdentity | Provider + credentials: AwsCredentialIdentity | Provider | RegionalAwsCredentialIdentityProvider ): MemoizedProvider => { if (typeof credentials === "function") { return memoize( @@ -287,3 +306,31 @@ const normalizeCredentialProvider = ( } return normalizeProvider(credentials); }; + +/** + * @internal + * @deprecated only used in legacy auth. + * + * normalizes the credentials from the input config into a provider that has + * a binding to the config itself. + */ +const createConfigBoundCredentialProvider = (input: { + credentials?: AwsCredentialIdentity | AwsCredentialIdentityProvider | RegionalAwsCredentialIdentityProvider; + credentialDefaultProvider: PreviouslyResolved["credentialDefaultProvider"]; + region: PreviouslyResolved["region"]; +}): AwsCredentialIdentityProvider => { + const normalizedCredentialsProvider = input.credentials + ? normalizeCredentialProvider(input.credentials) + : input.credentialDefaultProvider( + Object.assign({}, input, { + parentClientConfig: input, + }) + ); + const normalizedCreds = async () => + (normalizedCredentialsProvider as RegionalAwsCredentialIdentityProvider)({ + contextClientConfig: { + region: normalizeProvider(input.region), + }, + }); + return normalizedCreds; +}; diff --git a/packages/middleware-signing/src/awsAuthMiddleware.ts b/packages/middleware-signing/src/awsAuthMiddleware.ts index b738c3610830e..706bff2cf1ee7 100644 --- a/packages/middleware-signing/src/awsAuthMiddleware.ts +++ b/packages/middleware-signing/src/awsAuthMiddleware.ts @@ -17,6 +17,9 @@ import { AwsAuthResolvedConfig } from "./awsAuthConfiguration"; import { getSkewCorrectedDate } from "./utils/getSkewCorrectedDate"; import { getUpdatedSystemClockOffset } from "./utils/getUpdatedSystemClockOffset"; +/** + * @deprecated only used in legacy auth. + */ export const awsAuthMiddleware = ( options: AwsAuthResolvedConfig @@ -111,6 +114,9 @@ export const awsAuthMiddleware = const getDateHeader = (response: unknown): string | undefined => HttpResponse.isInstance(response) ? response.headers?.date ?? response.headers?.Date : undefined; +/** + * @deprecated only used in legacy auth. + */ export const awsAuthMiddlewareOptions: RelativeMiddlewareOptions = { name: "awsAuthMiddleware", tags: ["SIGNATURE", "AWSAUTH"], @@ -119,10 +125,16 @@ export const awsAuthMiddlewareOptions: RelativeMiddlewareOptions = { override: true, }; +/** + * @deprecated only used in legacy auth. + */ export const getAwsAuthPlugin = (options: AwsAuthResolvedConfig): Pluggable => ({ applyToStack: (clientStack) => { clientStack.addRelativeTo(awsAuthMiddleware(options), awsAuthMiddlewareOptions); }, }); +/** + * @deprecated only used in legacy auth. + */ export const getSigV4AuthPlugin = getAwsAuthPlugin; diff --git a/packages/middleware-websocket/package.json b/packages/middleware-websocket/package.json index 5ef4d450daaed..1f8b80f144e76 100644 --- a/packages/middleware-websocket/package.json +++ b/packages/middleware-websocket/package.json @@ -23,7 +23,6 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-signing": "*", "@aws-sdk/types": "*", "@aws-sdk/util-format-url": "*", "@smithy/eventstream-codec": "^3.1.10", diff --git a/packages/token-providers/src/fromSso.ts b/packages/token-providers/src/fromSso.ts index 35957281f1cb3..dbee38ce5e44d 100644 --- a/packages/token-providers/src/fromSso.ts +++ b/packages/token-providers/src/fromSso.ts @@ -1,4 +1,9 @@ -import { CredentialProviderOptions, TokenIdentity, TokenIdentityProvider } from "@aws-sdk/types"; +import { + AwsIdentityProperties, + CredentialProviderOptions, + RegionalIdentityProvider, + TokenIdentity, +} from "@aws-sdk/types"; import { TokenProviderError } from "@smithy/property-provider"; import { getProfileName, @@ -31,8 +36,15 @@ export interface FromSsoInit extends SourceProfileInit, CredentialProviderOption * Creates a token provider that will read from SSO token cache or ssoOidc.createToken() call. */ export const fromSso = - (init: FromSsoInit = {}): TokenIdentityProvider => - async () => { + (_init: FromSsoInit = {}): RegionalIdentityProvider => + async (awsIdentityProperties?: AwsIdentityProperties) => { + const init: FromSsoInit = { + ..._init, + parentClientConfig: { + region: awsIdentityProperties?.contextClientConfig?.region, + ..._init.parentClientConfig, + }, + }; init.logger?.debug("@aws-sdk/token-providers - fromSso"); const profiles = await parseKnownFiles(init); diff --git a/packages/types/src/identity/AwsCredentialIdentity.ts b/packages/types/src/identity/AwsCredentialIdentity.ts index 813fd86f1128e..c720f09620de8 100644 --- a/packages/types/src/identity/AwsCredentialIdentity.ts +++ b/packages/types/src/identity/AwsCredentialIdentity.ts @@ -2,8 +2,44 @@ import type { AwsCredentialIdentity } from "@smithy/types"; import type { AwsSdkCredentialsFeatures } from "../feature-ids"; -export { AwsCredentialIdentity, AwsCredentialIdentityProvider } from "@smithy/types"; +export { AwsCredentialIdentity, AwsCredentialIdentityProvider, IdentityProvider } from "@smithy/types"; +/** + * @public + */ +export interface AwsIdentityProperties { + contextClientConfig?: { + region(): Promise; + }; +} + +/** + * @public + * + * Variation of {@link IdentityProvider} which accepts a contextual + * client configuration that includes an AWS region. + * + * Used to link a credential provider to a client region if it is being called + * in the context of a client. + */ +export type RegionalIdentityProvider = (awsIdentityProperties?: AwsIdentityProperties) => Promise; + +/** + * @public + * + * Variation of {@link AwsCredentialIdentityProvider} which accepts a contextual + * client configuration that includes an AWS region. + * + * Used to link a credential provider to a client region if it is being called + * in the context of a client. + */ +export type RegionalAwsCredentialIdentityProvider = RegionalIdentityProvider; + +/** + * @public + * + * AwsCredentialIdentity with source attribution metadata. + */ export type AttributedAwsCredentialIdentity = AwsCredentialIdentity & { $source?: AwsSdkCredentialsFeatures; }; From 179aabf3acb33e9ac74cf31132e72898fcd6ea8a Mon Sep 17 00:00:00 2001 From: George Fu Date: Wed, 11 Dec 2024 17:36:22 +0000 Subject: [PATCH 2/8] chore: update lockfile --- yarn.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 13afb9f5386df..b589a3762bbcc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24042,7 +24042,6 @@ __metadata: version: 0.0.0-use.local resolution: "@aws-sdk/middleware-websocket@workspace:packages/middleware-websocket" dependencies: - "@aws-sdk/middleware-signing": "npm:*" "@aws-sdk/types": "npm:*" "@aws-sdk/util-format-url": "npm:*" "@smithy/eventstream-codec": "npm:^3.1.10" From ae6b311d4d07fbdb87c1924d61da0e4d44db79f5 Mon Sep 17 00:00:00 2001 From: George Fu Date: Wed, 11 Dec 2024 18:51:56 +0000 Subject: [PATCH 3/8] test(credential-provider-node): additional integ tests for cognito --- .../src/fromCognitoIdentityPool.ts | 6 +- packages/credential-provider-ini/package.json | 4 +- .../src/fromIni.integ.spec.ts | 242 ++++++++++++++++++ .../credential-provider-ini/src/fromIni.ts | 10 +- .../src/resolveAssumeRoleCredentials.ts | 26 +- .../vitest.config.integ.ts | 8 + .../credential-provider-node.integ.spec.ts | 158 +++++++++--- .../src/fromWebToken.ts | 12 +- packages/token-providers/src/fromSso.ts | 12 +- 9 files changed, 418 insertions(+), 60 deletions(-) create mode 100644 packages/credential-provider-ini/src/fromIni.integ.spec.ts create mode 100644 packages/credential-provider-ini/vitest.config.integ.ts diff --git a/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts b/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts index 319e1463fff86..061b02dc45f31 100644 --- a/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts +++ b/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts @@ -68,11 +68,11 @@ export function fromCognitoIdentityPool({ identityId, }); - return provider(); + return provider(awsIdentityProperties); }; - return () => - provider().catch(async (err) => { + return (awsIdentityProperties?: AwsIdentityProperties) => + provider(awsIdentityProperties).catch(async (err) => { if (cacheKey) { Promise.resolve(cache.removeItem(cacheKey)).catch(() => {}); } diff --git a/packages/credential-provider-ini/package.json b/packages/credential-provider-ini/package.json index c31fc8b552285..823a288815655 100644 --- a/packages/credential-provider-ini/package.json +++ b/packages/credential-provider-ini/package.json @@ -13,7 +13,9 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "test": "yarn g:vitest run", - "test:watch": "yarn g:vitest watch" + "test:watch": "yarn g:vitest watch", + "test:integration": "yarn g:vitest run -c vitest.config.integ.ts", + "test:integration:watch": "yarn g:vitest watch -c vitest.config.integ.ts" }, "keywords": [ "aws", diff --git a/packages/credential-provider-ini/src/fromIni.integ.spec.ts b/packages/credential-provider-ini/src/fromIni.integ.spec.ts new file mode 100644 index 0000000000000..0cb0e93c00bd4 --- /dev/null +++ b/packages/credential-provider-ini/src/fromIni.integ.spec.ts @@ -0,0 +1,242 @@ +import { STS } from "@aws-sdk/client-sts"; +import { HttpRequest, HttpResponse } from "@smithy/protocol-http"; +import { SourceProfileInit } from "@smithy/shared-ini-file-loader"; +import type { NodeHttpHandlerOptions, ParsedIniData } from "@smithy/types"; +import { PassThrough } from "node:stream"; +import { beforeEach, describe, expect, test as it, vi } from "vitest"; + +import { fromIni } from "./fromIni"; + +let iniProfileData: ParsedIniData = null as any; +vi.mock("@smithy/shared-ini-file-loader", async () => { + const actual: any = await vi.importActual("@smithy/shared-ini-file-loader"); + const pkg = { + ...actual, + async loadSsoSessionData() { + return Object.entries(iniProfileData) + .filter(([key]) => key.startsWith("sso-session.")) + .reduce( + (acc, [key, value]) => ({ + ...acc, + [key.split("sso-session.")[1]]: value, + }), + {} + ); + }, + async parseKnownFiles(init: SourceProfileInit): Promise { + return iniProfileData; + }, + async getSSOTokenFromFile() { + return { + accessToken: "mock_sso_token", + expiresAt: "3000-01-01T00:00:00.000Z", + }; + }, + }; + return { + ...pkg, + default: pkg, + }; +}); + +class MockNodeHttpHandler { + static create(instanceOrOptions?: any) { + if (typeof instanceOrOptions?.handle === "function") { + return instanceOrOptions; + } + return new MockNodeHttpHandler(); + } + async handle(request: HttpRequest) { + const body = new PassThrough({}); + + const region = (request.hostname.match(/sts\.(.*?)\./) || [, "unknown"])[1]; + + if (request.headers.Authorization === "container-authorization") { + body.write( + JSON.stringify({ + AccessKeyId: "CONTAINER_ACCESS_KEY", + SecretAccessKey: "CONTAINER_SECRET_ACCESS_KEY", + Token: "CONTAINER_TOKEN", + Expiration: "3000-01-01T00:00:00.000Z", + }) + ); + } else if (request.path?.includes("/federation/credentials")) { + body.write( + JSON.stringify({ + roleCredentials: { + accessKeyId: "SSO_ACCESS_KEY_ID", + secretAccessKey: "SSO_SECRET_ACCESS_KEY", + sessionToken: "SSO_SESSION_TOKEN", + expiration: "3000-01-01T00:00:00.000Z", + }, + }) + ); + } else if (request.body?.includes("Action=AssumeRoleWithWebIdentity")) { + body.write(` + + + + STS_ARWI_ACCESS_KEY_ID + STS_ARWI_SECRET_ACCESS_KEY + STS_ARWI_SESSION_TOKEN_${region} + 3000-01-01T00:00:00.000Z + + + +01234567-89ab-cdef-0123-456789abcdef + +`); + } else if (request.body?.includes("Action=AssumeRole")) { + body.write(` + + + + STS_AR_ACCESS_KEY_ID + STS_AR_SECRET_ACCESS_KEY + STS_AR_SESSION_TOKEN_${region} + 3000-01-01T00:00:00.000Z + + + +01234567-89ab-cdef-0123-456789abcdef + +`); + } else if (request.body.includes("Action=GetCallerIdentity")) { + body.write(` + + +arn:aws:iam::123456789012:user/Alice +AIDACKCEVSQ6C2EXAMPLE +123456789012 + + +01234567-89ab-cdef-0123-456789abcdef + +`); + } else { + throw new Error("request not supported."); + } + body.end(); + return { + response: new HttpResponse({ + statusCode: 200, + body, + headers: {}, + }), + }; + } + updateHttpClientConfig(key: keyof NodeHttpHandlerOptions, value: NodeHttpHandlerOptions[typeof key]): void {} + httpHandlerConfigs(): NodeHttpHandlerOptions { + return null as any; + } +} + +describe("fromIni region search order", () => { + beforeEach(() => { + iniProfileData = { + default: { + region: "us-west-2", + output: "json", + }, + }; + iniProfileData.assume = { + region: "us-stsar-1", + aws_access_key_id: "ASSUME_STATIC_ACCESS_KEY", + aws_secret_access_key: "ASSUME_STATIC_SECRET_KEY", + }; + Object.assign(iniProfileData.default, { + region: "us-stsar-1", + role_arn: "ROLE_ARN", + role_session_name: "ROLE_SESSION_NAME", + external_id: "EXTERNAL_ID", + source_profile: "assume", + }); + }); + + it("should use 1st priority for the clientConfig given to the provider factory", async () => { + const sts = new STS({ + requestHandler: new MockNodeHttpHandler(), + region: "ap-northeast-2", + credentials: fromIni({ + clientConfig: { + requestHandler: new MockNodeHttpHandler(), + region: "ap-northeast-1", + }, + }), + }); + + await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); + expect(credentials).toContain({ + accessKeyId: "STS_AR_ACCESS_KEY_ID", + secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", + sessionToken: "STS_AR_SESSION_TOKEN_ap-northeast-1", + }); + }); + + it("should use 2nd priority for the context client", async () => { + const sts = new STS({ + requestHandler: new MockNodeHttpHandler(), + region: "ap-northeast-2", + credentials: fromIni({ + clientConfig: { + requestHandler: new MockNodeHttpHandler(), + }, + }), + }); + + await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); + expect(credentials).toContain({ + accessKeyId: "STS_AR_ACCESS_KEY_ID", + secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", + sessionToken: "STS_AR_SESSION_TOKEN_ap-northeast-2", + }); + }); + + it("should use 3rd priority for the profile region if not used in the context of a client with a region", async () => { + const credentialsData = await fromIni({ + clientConfig: { + requestHandler: new MockNodeHttpHandler(), + }, + })(); + + const sts = new STS({ + requestHandler: new MockNodeHttpHandler(), + region: "ap-northeast-2", + credentials: credentialsData, + }); + + await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); + expect(credentials).toContain({ + accessKeyId: "STS_AR_ACCESS_KEY_ID", + secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", + sessionToken: "STS_AR_SESSION_TOKEN_us-stsar-1", + }); + }); + + it("should use 4th priority for the default partition's default region", async () => { + delete iniProfileData.default.region; + + const credentialsData = await fromIni({ + clientConfig: { + requestHandler: new MockNodeHttpHandler(), + }, + })(); + + const sts = new STS({ + requestHandler: new MockNodeHttpHandler(), + region: "ap-northeast-2", + credentials: credentialsData, + }); + + await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); + expect(credentials).toContain({ + accessKeyId: "STS_AR_ACCESS_KEY_ID", + secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", + sessionToken: "STS_AR_SESSION_TOKEN_us-east-1", + }); + }); +}); diff --git a/packages/credential-provider-ini/src/fromIni.ts b/packages/credential-provider-ini/src/fromIni.ts index 58e001f243473..08a8c2ae18cf2 100644 --- a/packages/credential-provider-ini/src/fromIni.ts +++ b/packages/credential-provider-ini/src/fromIni.ts @@ -60,11 +60,13 @@ export const fromIni = async (props = {}) => { const init: FromIniInit = { ..._init, - parentClientConfig: { - region: props.contextClientConfig?.region, - ..._init.parentClientConfig, - }, }; + if (props.contextClientConfig?.region) { + init.parentClientConfig = { + region: props.contextClientConfig.region, + ..._init.parentClientConfig, + }; + } init.logger?.debug("@aws-sdk/credential-provider-ini - fromIni"); const profiles = await parseKnownFiles(init); return resolveProfileData(getProfileName(init), profiles, init); diff --git a/packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts b/packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts index a7bed9952747f..1e9b7942555ad 100644 --- a/packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts +++ b/packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts @@ -107,7 +107,8 @@ export const resolveAssumeRoleCredentials = async ( visitedProfiles: Record = {} ) => { options.logger?.debug("@aws-sdk/credential-provider-ini - resolveAssumeRoleCredentials (STS)"); - const data = profiles[profileName]; + const profileData = profiles[profileName]; + const { source_profile, region } = profileData; if (!options.roleAssumer) { // @ts-ignore Cannot find module '@aws-sdk/client-sts' @@ -116,13 +117,18 @@ export const resolveAssumeRoleCredentials = async ( { ...options.clientConfig, credentialProviderLogger: options.logger, - parentClientConfig: options?.parentClientConfig, + parentClientConfig: { + ...options?.parentClientConfig, + // The profile region is the last fallback, and only applies + // if the clientConfig.region is not defined by the user + // and no contextual outer client configuration region can be found. + region: options?.parentClientConfig?.region ?? region, + }, }, options.clientPlugins ); } - const { source_profile } = data; if (source_profile && source_profile in visitedProfiles) { throw new CredentialsProviderError( `Detected a cycle attempting to resolve credentials for profile` + @@ -149,9 +155,9 @@ export const resolveAssumeRoleCredentials = async ( }, isCredentialSourceWithoutRoleArn(profiles[source_profile!] ?? {}) ) - : (await resolveCredentialSource(data.credential_source!, profileName, options.logger)(options))(); + : (await resolveCredentialSource(profileData.credential_source!, profileName, options.logger)(options))(); - if (isCredentialSourceWithoutRoleArn(data)) { + if (isCredentialSourceWithoutRoleArn(profileData)) { /** * This control-flow branch is accessed when in a chained source_profile * scenario, and the last step of the chain is a credential_source @@ -163,13 +169,13 @@ export const resolveAssumeRoleCredentials = async ( return sourceCredsProvider.then((creds) => setCredentialFeature(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o")); } else { const params: AssumeRoleParams = { - RoleArn: data.role_arn!, - RoleSessionName: data.role_session_name || `aws-sdk-js-${Date.now()}`, - ExternalId: data.external_id, - DurationSeconds: parseInt(data.duration_seconds || "3600", 10), + RoleArn: profileData.role_arn!, + RoleSessionName: profileData.role_session_name || `aws-sdk-js-${Date.now()}`, + ExternalId: profileData.external_id, + DurationSeconds: parseInt(profileData.duration_seconds || "3600", 10), }; - const { mfa_serial } = data; + const { mfa_serial } = profileData; if (mfa_serial) { if (!options.mfaCodeProvider) { throw new CredentialsProviderError( diff --git a/packages/credential-provider-ini/vitest.config.integ.ts b/packages/credential-provider-ini/vitest.config.integ.ts new file mode 100644 index 0000000000000..5802db1ac64a8 --- /dev/null +++ b/packages/credential-provider-ini/vitest.config.integ.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["**/*.integ.spec.ts"], + environment: "node", + }, +}); 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 f377bafacb383..b57651770bb91 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 @@ -1,6 +1,7 @@ import { STS } from "@aws-sdk/client-sts"; import * as credentialProviderHttp from "@aws-sdk/credential-provider-http"; -import { fromIni } from "@aws-sdk/credential-providers"; +import { fromCognitoIdentity, fromCognitoIdentityPool, fromIni, fromWebToken } from "@aws-sdk/credential-providers"; +import { fromSso } from "@aws-sdk/token-providers"; import { HttpResponse } from "@smithy/protocol-http"; import type { SourceProfileInit } from "@smithy/shared-ini-file-loader"; import type { HttpRequest, NodeHttpHandlerOptions, ParsedIniData } from "@smithy/types"; @@ -68,7 +69,7 @@ jest.mock("@smithy/node-http-handler", () => { assumeRoleArns.push(request.body.match(/RoleArn=(.*?)&/)?.[1]); } - const region = (request.hostname.match(/sts\.(.*?)\./) || [, "unknown"])[1]; + const region = (request.hostname.match(/(sts|cognito-identity)\.(.*?)\./) || [, , "unknown"])[2]; if (request.headers.Authorization === "container-authorization") { body.write( @@ -132,7 +133,22 @@ jest.mock("@smithy/node-http-handler", () => { 01234567-89ab-cdef-0123-456789abcdef `); + } else if (request.headers["x-amz-target"] === "AWSCognitoIdentityService.GetCredentialsForIdentity") { + body.write(`{ + "Credentials":{ + "SecretKey":"COGNITO_SECRET_KEY", + "SessionToken":"COGNITO_SESSION_TOKEN_${region}", + "Expiration":${new Date("3000-01-01T00:00:00.000Z").getTime() / 1000}, + "AccessKeyId":"COGNITO_ACCESS_KEY_ID" + }, + "IdentityId":"${region}:COGNITO_IDENTITY_ID" + }`); + } else if (request.headers["x-amz-target"] === "AWSCognitoIdentityService.GetId") { + body.write(`{ + "IdentityId":"${region}:COGNITO_IDENTITY_ID" + }`); } else { + console.log(request); throw new Error("request not supported."); } body.end(); @@ -451,38 +467,6 @@ describe("credential-provider-node integration test", () => { }); }); - it("should use the context client's region for STS even if initialized separately in a code-level provider", async () => { - sts = new STS({ - region: "eu-west-1", - credentials: fromIni(), - }); - iniProfileData.assume = { - region: "eu-west-1", - aws_access_key_id: "ASSUME_STATIC_ACCESS_KEY", - aws_secret_access_key: "ASSUME_STATIC_SECRET_KEY", - }; - Object.assign(iniProfileData.default, { - region: "eu-west-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_eu-west-1", - expiration: new Date("3000-01-01T00:00:00.000Z"), - $source: { - CREDENTIALS_CODE: "e", - CREDENTIALS_PROFILE_SOURCE_PROFILE: "o", - CREDENTIALS_STS_ASSUME_ROLE: "i", - }, - }); - }); - it("should resolve credentials from STS assumeRoleWithWebIdentity if the ini profile is configured for web identity", async () => { Object.assign(iniProfileData.default, { web_identity_token_file: "token-filepath", @@ -820,6 +804,112 @@ describe("credential-provider-node integration test", () => { }); }); + describe("Region resolution for code-level providers given to a client", () => { + it("fromCognitoIdentity provider should use context client region", async () => { + sts = new STS({ + region: "ap-northeast-1", + credentials: fromCognitoIdentity({ + identityId: "", + }), + }); + await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); + expect(credentials).toEqual({ + accessKeyId: "COGNITO_ACCESS_KEY_ID", + secretAccessKey: "COGNITO_SECRET_KEY", + sessionToken: "COGNITO_SESSION_TOKEN_ap-northeast-1", + identityId: "", + expiration: new Date("3000-01-01T00:00:00.000Z"), + $source: { CREDENTIALS_CODE: "e" }, + }); + }); + + it("fromCognitoIdentityPool provider should use context client region", async () => { + sts = new STS({ + region: "ap-northeast-1", + credentials: fromCognitoIdentityPool({ + identityPoolId: "", + }), + }); + await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); + expect(credentials).toEqual({ + accessKeyId: "COGNITO_ACCESS_KEY_ID", + secretAccessKey: "COGNITO_SECRET_KEY", + sessionToken: "COGNITO_SESSION_TOKEN_ap-northeast-1", + identityId: "ap-northeast-1:COGNITO_IDENTITY_ID", + expiration: new Date("3000-01-01T00:00:00.000Z"), + $source: { CREDENTIALS_CODE: "e" }, + }); + }); + + it("fromIni assumeRole provider should use the context client's region for STS", async () => { + sts = new STS({ + region: "eu-west-1", + credentials: fromIni(), + }); + iniProfileData.assume = { + region: "eu-west-1", + aws_access_key_id: "ASSUME_STATIC_ACCESS_KEY", + aws_secret_access_key: "ASSUME_STATIC_SECRET_KEY", + }; + Object.assign(iniProfileData.default, { + region: "eu-west-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_eu-west-1", + expiration: new Date("3000-01-01T00:00:00.000Z"), + $source: { + CREDENTIALS_CODE: "e", + CREDENTIALS_PROFILE_SOURCE_PROFILE: "o", + CREDENTIALS_STS_ASSUME_ROLE: "i", + }, + }); + }); + + it("fromWebToken provider should use context client region", async () => { + sts = new STS({ + region: "ap-northeast-1", + credentials: fromWebToken({ + roleArn: "", + webIdentityToken: "", + }), + }); + 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_ap-northeast-1", + expiration: new Date("3000-01-01T00:00:00.000Z"), + $source: { + CREDENTIALS_CODE: "e", + CREDENTIALS_STS_ASSUME_ROLE_WEB_ID: "k", + }, + }); + }); + + it.skip( + "fromSSO (SSO) provider is excluded from testing because the SSO_REGION is a required parameter and is used " + + "instead of any fallback to the context client region", + async () => {} + ); + + it.skip( + "fromSso (SSO-OIDC) provider is excluded from testing because it is " + + "not used in a client initialization context", + async () => {} + ); + }); + describe("No credentials available", () => { it("should throw CredentialsProviderError", async () => { process.env.AWS_EC2_METADATA_DISABLED = "true"; diff --git a/packages/credential-provider-web-identity/src/fromWebToken.ts b/packages/credential-provider-web-identity/src/fromWebToken.ts index 5e16e863fd964..880a27772b3c9 100644 --- a/packages/credential-provider-web-identity/src/fromWebToken.ts +++ b/packages/credential-provider-web-identity/src/fromWebToken.ts @@ -169,10 +169,14 @@ export const fromWebToken = { ...init.clientConfig, credentialProviderLogger: init.logger, - parentClientConfig: { - region: awsIdentityProperties?.contextClientConfig?.region, - ...init.parentClientConfig, - }, + ...(awsIdentityProperties?.contextClientConfig?.region + ? { + parentClientConfig: { + region: awsIdentityProperties?.contextClientConfig?.region, + ...init.parentClientConfig, + }, + } + : {}), }, init.clientPlugins ); diff --git a/packages/token-providers/src/fromSso.ts b/packages/token-providers/src/fromSso.ts index dbee38ce5e44d..516b4efea1ca8 100644 --- a/packages/token-providers/src/fromSso.ts +++ b/packages/token-providers/src/fromSso.ts @@ -40,10 +40,14 @@ export const fromSso = async (awsIdentityProperties?: AwsIdentityProperties) => { const init: FromSsoInit = { ..._init, - parentClientConfig: { - region: awsIdentityProperties?.contextClientConfig?.region, - ..._init.parentClientConfig, - }, + ...(awsIdentityProperties?.contextClientConfig?.region + ? { + parentClientConfig: { + region: awsIdentityProperties?.contextClientConfig?.region, + ..._init.parentClientConfig, + }, + } + : {}), }; init.logger?.debug("@aws-sdk/token-providers - fromSso"); From 7ffce965b6b44bb77c9a9725bcbb1fa73bfa510a Mon Sep 17 00:00:00 2001 From: George Fu Date: Wed, 11 Dec 2024 19:51:02 +0000 Subject: [PATCH 4/8] feat(credential-providers): fix tests, add chaining support --- .../credential-provider-ini/src/fromIni.ts | 6 +-- .../src/fromWebToken.ts | 2 +- .../src/createCredentialChain.spec.ts | 30 ++++++++++++++ .../src/createCredentialChain.ts | 41 ++++++++++++++++--- 4 files changed, 70 insertions(+), 9 deletions(-) diff --git a/packages/credential-provider-ini/src/fromIni.ts b/packages/credential-provider-ini/src/fromIni.ts index 08a8c2ae18cf2..795a4aa7ed551 100644 --- a/packages/credential-provider-ini/src/fromIni.ts +++ b/packages/credential-provider-ini/src/fromIni.ts @@ -57,13 +57,13 @@ export interface FromIniInit extends SourceProfileInit, CredentialProviderOption */ export const fromIni = (_init: FromIniInit = {}): RegionalAwsCredentialIdentityProvider => - async (props = {}) => { + async ({ contextClientConfig } = {}) => { const init: FromIniInit = { ..._init, }; - if (props.contextClientConfig?.region) { + if (contextClientConfig?.region) { init.parentClientConfig = { - region: props.contextClientConfig.region, + region: contextClientConfig.region, ..._init.parentClientConfig, }; } diff --git a/packages/credential-provider-web-identity/src/fromWebToken.ts b/packages/credential-provider-web-identity/src/fromWebToken.ts index 880a27772b3c9..1650113733ff5 100644 --- a/packages/credential-provider-web-identity/src/fromWebToken.ts +++ b/packages/credential-provider-web-identity/src/fromWebToken.ts @@ -169,7 +169,7 @@ export const fromWebToken = { ...init.clientConfig, credentialProviderLogger: init.logger, - ...(awsIdentityProperties?.contextClientConfig?.region + ...(awsIdentityProperties?.contextClientConfig?.region || init.parentClientConfig ? { parentClientConfig: { region: awsIdentityProperties?.contextClientConfig?.region, diff --git a/packages/credential-providers/src/createCredentialChain.spec.ts b/packages/credential-providers/src/createCredentialChain.spec.ts index 08679a87b3efe..04f5fbb679621 100644 --- a/packages/credential-providers/src/createCredentialChain.spec.ts +++ b/packages/credential-providers/src/createCredentialChain.spec.ts @@ -1,3 +1,4 @@ +import { RegionalAwsCredentialIdentityProvider } from "@aws-sdk/types"; import { ProviderError } from "@smithy/property-provider"; import { AwsCredentialIdentity, AwsCredentialIdentityProvider } from "@smithy/types"; import { describe, expect, test as it } from "vitest"; @@ -79,4 +80,33 @@ describe(createCredentialChain.name, () => { "@aws-sdk/credential-providers - createCredentialChain(...).expireAfter(ms) may not be called with a duration lower than five minutes." ); }); + + it("is compatible with contextual-region-aware credential providers", async () => { + const provider: RegionalAwsCredentialIdentityProvider = async ({ contextClientConfig } = {}) => { + return { + accessKeyId: "", + secretAccessKey: "", + sessionToken: (await contextClientConfig?.region()) ?? "wrong_region", + }; + }; + const errorProvider = async () => { + throw new ProviderError("", { tryNextLink: true }); + }; + + const chain = createCredentialChain(errorProvider, provider); + + expect( + await chain({ + contextClientConfig: { + async region() { + return "ap-northeast-1"; + }, + }, + }) + ).toEqual({ + accessKeyId: "", + secretAccessKey: "", + sessionToken: "ap-northeast-1", + }); + }); }); diff --git a/packages/credential-providers/src/createCredentialChain.ts b/packages/credential-providers/src/createCredentialChain.ts index 857ce1b5345a4..0c536436f1a86 100644 --- a/packages/credential-providers/src/createCredentialChain.ts +++ b/packages/credential-providers/src/createCredentialChain.ts @@ -1,4 +1,9 @@ -import { chain as propertyProviderChain } from "@smithy/property-provider"; +import type { + AwsIdentityProperties, + RegionalAwsCredentialIdentityProvider, + RegionalIdentityProvider, +} from "@aws-sdk/types"; +import { ProviderError } from "@smithy/property-provider"; import type { AwsCredentialIdentityProvider } from "@smithy/types"; export interface CustomCredentialChainOptions { @@ -52,11 +57,11 @@ type Mutable = { * providers in sequence until one succeeds or all fail. */ export const createCredentialChain = ( - ...credentialProviders: AwsCredentialIdentityProvider[] -): AwsCredentialIdentityProvider & CustomCredentialChainOptions => { + ...credentialProviders: RegionalAwsCredentialIdentityProvider[] +): RegionalAwsCredentialIdentityProvider & CustomCredentialChainOptions => { let expireAfter = -1; - const baseFunction = async () => { - const credentials = await propertyProviderChain(...credentialProviders)(); + const baseFunction = async (awsIdentityProperties?: AwsIdentityProperties) => { + const credentials = await propertyProviderChain(...credentialProviders)(awsIdentityProperties); if (!credentials.expiration && expireAfter !== -1) { (credentials as Mutable).expiration = new Date(Date.now() + expireAfter); } @@ -75,3 +80,29 @@ export const createCredentialChain = ( }); return withOptions; }; + +/** + * @internal + */ +export const propertyProviderChain = + (...providers: Array>): RegionalIdentityProvider => + async (awsIdentityProperties?: AwsIdentityProperties) => { + if (providers.length === 0) { + throw new ProviderError("No providers in chain"); + } + + let lastProviderError: Error | undefined; + for (const provider of providers) { + try { + const credentials = await provider(awsIdentityProperties); + return credentials; + } catch (err) { + lastProviderError = err; + if (err?.tryNextLink) { + continue; + } + throw err; + } + } + throw lastProviderError; + }; From 61251d6f0bc8ae35e8513f59cf2a85782f6d50cc Mon Sep 17 00:00:00 2001 From: George Fu Date: Thu, 12 Dec 2024 20:08:02 +0000 Subject: [PATCH 5/8] feat(credential-providers): rename contextClientConfig to callerClientConfig --- .../aws_sdk/resolveAwsSdkSigV4Config.ts | 12 ++++++------ .../src/fromCognitoIdentity.ts | 2 +- .../src/fromCognitoIdentityPool.ts | 2 +- packages/credential-provider-ini/src/fromIni.ts | 6 +++--- .../src/fromWebToken.ts | 4 ++-- .../middleware-signing/src/awsAuthConfiguration.ts | 2 +- packages/token-providers/src/fromSso.ts | 4 ++-- packages/types/src/identity/AwsCredentialIdentity.ts | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts b/packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts index ba3fd5117e5fc..61351e90a10cc 100644 --- a/packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts +++ b/packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts @@ -129,8 +129,8 @@ export const resolveAwsSdkSigV4Config = ( } } - const contextBoundCredentialsProvider = async () => { - return credentialsProvider!({ contextClientConfig: config }); + const boundCredentialsProvider = async () => { + return credentialsProvider!({ callerClientConfig: config }); }; // Populate sigv4 arguments @@ -174,7 +174,7 @@ export const resolveAwsSdkSigV4Config = ( const params: SignatureV4Init & SignatureV4CryptoInit = { ...config, - credentials: contextBoundCredentialsProvider, + credentials: boundCredentialsProvider, region: config.signingRegion, service: config.signingName, sha256, @@ -210,7 +210,7 @@ export const resolveAwsSdkSigV4Config = ( const params: SignatureV4Init & SignatureV4CryptoInit = { ...config, - credentials: contextBoundCredentialsProvider, + credentials: boundCredentialsProvider, region: config.signingRegion, service: config.signingName, sha256, @@ -228,10 +228,10 @@ export const resolveAwsSdkSigV4Config = ( signingEscapePath, credentials: isUserSupplied ? async () => - contextBoundCredentialsProvider!().then((creds: AttributedAwsCredentialIdentity) => + boundCredentialsProvider!().then((creds: AttributedAwsCredentialIdentity) => setCredentialFeature(creds, "CREDENTIALS_CODE", "e") ) - : contextBoundCredentialsProvider!, + : boundCredentialsProvider!, signer, }; }; diff --git a/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts b/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts index 389228053a719..db5e1b745ad43 100644 --- a/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts +++ b/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts @@ -47,7 +47,7 @@ export function fromCognitoIdentity(parameters: FromCognitoIdentityParameters): region: parameters.clientConfig?.region ?? parameters.parentClientConfig?.region ?? - awsIdentityProperties?.contextClientConfig?.region, + awsIdentityProperties?.callerClientConfig?.region, }) ) ).send( diff --git a/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts b/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts index 061b02dc45f31..2cf4d5f122934 100644 --- a/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts +++ b/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts @@ -42,7 +42,7 @@ export function fromCognitoIdentityPool({ new CognitoIdentityClient( Object.assign({}, clientConfig ?? {}, { region: - clientConfig?.region ?? parentClientConfig?.region ?? awsIdentityProperties?.contextClientConfig?.region, + clientConfig?.region ?? parentClientConfig?.region ?? awsIdentityProperties?.callerClientConfig?.region, }) ); diff --git a/packages/credential-provider-ini/src/fromIni.ts b/packages/credential-provider-ini/src/fromIni.ts index 795a4aa7ed551..9f6de87c5f851 100644 --- a/packages/credential-provider-ini/src/fromIni.ts +++ b/packages/credential-provider-ini/src/fromIni.ts @@ -57,13 +57,13 @@ export interface FromIniInit extends SourceProfileInit, CredentialProviderOption */ export const fromIni = (_init: FromIniInit = {}): RegionalAwsCredentialIdentityProvider => - async ({ contextClientConfig } = {}) => { + async ({ callerClientConfig } = {}) => { const init: FromIniInit = { ..._init, }; - if (contextClientConfig?.region) { + if (callerClientConfig?.region) { init.parentClientConfig = { - region: contextClientConfig.region, + region: callerClientConfig.region, ..._init.parentClientConfig, }; } diff --git a/packages/credential-provider-web-identity/src/fromWebToken.ts b/packages/credential-provider-web-identity/src/fromWebToken.ts index 1650113733ff5..d2edd9ca6cc16 100644 --- a/packages/credential-provider-web-identity/src/fromWebToken.ts +++ b/packages/credential-provider-web-identity/src/fromWebToken.ts @@ -169,10 +169,10 @@ export const fromWebToken = { ...init.clientConfig, credentialProviderLogger: init.logger, - ...(awsIdentityProperties?.contextClientConfig?.region || init.parentClientConfig + ...(awsIdentityProperties?.callerClientConfig?.region || init.parentClientConfig ? { parentClientConfig: { - region: awsIdentityProperties?.contextClientConfig?.region, + region: awsIdentityProperties?.callerClientConfig?.region, ...init.parentClientConfig, }, } diff --git a/packages/middleware-signing/src/awsAuthConfiguration.ts b/packages/middleware-signing/src/awsAuthConfiguration.ts index 1130754f80531..12313d69b0b55 100644 --- a/packages/middleware-signing/src/awsAuthConfiguration.ts +++ b/packages/middleware-signing/src/awsAuthConfiguration.ts @@ -328,7 +328,7 @@ const createConfigBoundCredentialProvider = (input: { ); const normalizedCreds = async () => (normalizedCredentialsProvider as RegionalAwsCredentialIdentityProvider)({ - contextClientConfig: { + callerClientConfig: { region: normalizeProvider(input.region), }, }); diff --git a/packages/token-providers/src/fromSso.ts b/packages/token-providers/src/fromSso.ts index 516b4efea1ca8..0f973dd8787cc 100644 --- a/packages/token-providers/src/fromSso.ts +++ b/packages/token-providers/src/fromSso.ts @@ -40,10 +40,10 @@ export const fromSso = async (awsIdentityProperties?: AwsIdentityProperties) => { const init: FromSsoInit = { ..._init, - ...(awsIdentityProperties?.contextClientConfig?.region + ...(awsIdentityProperties?.callerClientConfig?.region ? { parentClientConfig: { - region: awsIdentityProperties?.contextClientConfig?.region, + region: awsIdentityProperties?.callerClientConfig?.region, ..._init.parentClientConfig, }, } diff --git a/packages/types/src/identity/AwsCredentialIdentity.ts b/packages/types/src/identity/AwsCredentialIdentity.ts index c720f09620de8..0af1dcfb22d21 100644 --- a/packages/types/src/identity/AwsCredentialIdentity.ts +++ b/packages/types/src/identity/AwsCredentialIdentity.ts @@ -8,7 +8,7 @@ export { AwsCredentialIdentity, AwsCredentialIdentityProvider, IdentityProvider * @public */ export interface AwsIdentityProperties { - contextClientConfig?: { + callerClientConfig?: { region(): Promise; }; } From 5bd38890286cde89ac33df96cb2210ac724971df Mon Sep 17 00:00:00 2001 From: George Fu Date: Thu, 12 Dec 2024 15:57:54 -0500 Subject: [PATCH 6/8] Update packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts Co-authored-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> --- .../httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts b/packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts index 61351e90a10cc..8962aba484214 100644 --- a/packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts +++ b/packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts @@ -129,9 +129,7 @@ export const resolveAwsSdkSigV4Config = ( } } - const boundCredentialsProvider = async () => { - return credentialsProvider!({ callerClientConfig: config }); - }; + const boundCredentialsProvider = async () => credentialsProvider!({ callerClientConfig: config }); // Populate sigv4 arguments const { From 8c3671a3fad15da015446ea06de914e48da614c5 Mon Sep 17 00:00:00 2001 From: George Fu Date: Thu, 12 Dec 2024 20:58:25 +0000 Subject: [PATCH 7/8] test: unit test fixes --- .../src/fromIni.integ.spec.ts | 20 +++++++++---------- .../src/resolveAssumeRoleCredentials.ts | 5 +---- .../credential-provider-node.integ.spec.ts | 12 +++++------ .../src/createCredentialChain.spec.ts | 6 +++--- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/credential-provider-ini/src/fromIni.integ.spec.ts b/packages/credential-provider-ini/src/fromIni.integ.spec.ts index 0cb0e93c00bd4..db15da42fa6c0 100644 --- a/packages/credential-provider-ini/src/fromIni.integ.spec.ts +++ b/packages/credential-provider-ini/src/fromIni.integ.spec.ts @@ -174,7 +174,7 @@ describe("fromIni region search order", () => { }); }); - it("should use 2nd priority for the context client", async () => { + it("should use 2nd priority for the profile region", async () => { const sts = new STS({ requestHandler: new MockNodeHttpHandler(), region: "ap-northeast-2", @@ -190,21 +190,21 @@ describe("fromIni region search order", () => { expect(credentials).toContain({ accessKeyId: "STS_AR_ACCESS_KEY_ID", secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", - sessionToken: "STS_AR_SESSION_TOKEN_ap-northeast-2", + sessionToken: "STS_AR_SESSION_TOKEN_us-stsar-1", }); }); - it("should use 3rd priority for the profile region if not used in the context of a client with a region", async () => { - const credentialsData = await fromIni({ - clientConfig: { - requestHandler: new MockNodeHttpHandler(), - }, - })(); + it("should use 3rd priority for the caller client", async () => { + delete iniProfileData.default.region; const sts = new STS({ requestHandler: new MockNodeHttpHandler(), region: "ap-northeast-2", - credentials: credentialsData, + credentials: fromIni({ + clientConfig: { + requestHandler: new MockNodeHttpHandler(), + }, + }), }); await sts.getCallerIdentity({}); @@ -212,7 +212,7 @@ describe("fromIni region search order", () => { expect(credentials).toContain({ accessKeyId: "STS_AR_ACCESS_KEY_ID", secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", - sessionToken: "STS_AR_SESSION_TOKEN_us-stsar-1", + sessionToken: "STS_AR_SESSION_TOKEN_ap-northeast-2", }); }); diff --git a/packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts b/packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts index 1e9b7942555ad..29d5648cee04d 100644 --- a/packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts +++ b/packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts @@ -119,10 +119,7 @@ export const resolveAssumeRoleCredentials = async ( credentialProviderLogger: options.logger, parentClientConfig: { ...options?.parentClientConfig, - // The profile region is the last fallback, and only applies - // if the clientConfig.region is not defined by the user - // and no contextual outer client configuration region can be found. - region: options?.parentClientConfig?.region ?? region, + region: region ?? options?.parentClientConfig?.region, }, }, options.clientPlugins 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 b57651770bb91..b2ac2def6744a 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 @@ -398,7 +398,7 @@ describe("credential-provider-node integration test", () => { expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", secretAccessKey: "STS_AR_SECRET_ACCESS_KEY", - sessionToken: "STS_AR_SESSION_TOKEN_us-west-2", + sessionToken: "STS_AR_SESSION_TOKEN_us-stsar-1", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { CREDENTIALS_PROFILE_SOURCE_PROFILE: "o", @@ -805,7 +805,7 @@ describe("credential-provider-node integration test", () => { }); describe("Region resolution for code-level providers given to a client", () => { - it("fromCognitoIdentity provider should use context client region", async () => { + it("fromCognitoIdentity provider should use caller client region", async () => { sts = new STS({ region: "ap-northeast-1", credentials: fromCognitoIdentity({ @@ -824,7 +824,7 @@ describe("credential-provider-node integration test", () => { }); }); - it("fromCognitoIdentityPool provider should use context client region", async () => { + it("fromCognitoIdentityPool provider should use caller client region", async () => { sts = new STS({ region: "ap-northeast-1", credentials: fromCognitoIdentityPool({ @@ -843,7 +843,7 @@ describe("credential-provider-node integration test", () => { }); }); - it("fromIni assumeRole provider should use the context client's region for STS", async () => { + it("fromIni assumeRole provider should use the caller client's region for STS", async () => { sts = new STS({ region: "eu-west-1", credentials: fromIni(), @@ -875,7 +875,7 @@ describe("credential-provider-node integration test", () => { }); }); - it("fromWebToken provider should use context client region", async () => { + it("fromWebToken provider should use caller client region", async () => { sts = new STS({ region: "ap-northeast-1", credentials: fromWebToken({ @@ -899,7 +899,7 @@ describe("credential-provider-node integration test", () => { it.skip( "fromSSO (SSO) provider is excluded from testing because the SSO_REGION is a required parameter and is used " + - "instead of any fallback to the context client region", + "instead of any fallback to the caller client region", async () => {} ); diff --git a/packages/credential-providers/src/createCredentialChain.spec.ts b/packages/credential-providers/src/createCredentialChain.spec.ts index 04f5fbb679621..0cbee3d055539 100644 --- a/packages/credential-providers/src/createCredentialChain.spec.ts +++ b/packages/credential-providers/src/createCredentialChain.spec.ts @@ -82,11 +82,11 @@ describe(createCredentialChain.name, () => { }); it("is compatible with contextual-region-aware credential providers", async () => { - const provider: RegionalAwsCredentialIdentityProvider = async ({ contextClientConfig } = {}) => { + const provider: RegionalAwsCredentialIdentityProvider = async ({ callerClientConfig } = {}) => { return { accessKeyId: "", secretAccessKey: "", - sessionToken: (await contextClientConfig?.region()) ?? "wrong_region", + sessionToken: (await callerClientConfig?.region()) ?? "wrong_region", }; }; const errorProvider = async () => { @@ -97,7 +97,7 @@ describe(createCredentialChain.name, () => { expect( await chain({ - contextClientConfig: { + callerClientConfig: { async region() { return "ap-northeast-1"; }, From 90d71223bc57dab03851898e4287480cb3263c5d Mon Sep 17 00:00:00 2001 From: George Fu Date: Fri, 13 Dec 2024 19:47:16 +0000 Subject: [PATCH 8/8] feat(credential-providers): rename types --- .../src/fromCognitoIdentity.ts | 4 ++-- packages/credential-provider-ini/src/fromIni.ts | 4 ++-- .../src/fromWebToken.ts | 4 ++-- .../src/createCredentialChain.spec.ts | 4 ++-- .../src/createCredentialChain.ts | 10 +++++----- packages/credential-providers/src/fromIni.ts | 4 ++-- .../middleware-signing/src/awsAuthConfiguration.ts | 8 ++++---- packages/token-providers/src/fromSso.ts | 4 ++-- .../types/src/identity/AwsCredentialIdentity.ts | 14 ++++++++------ 9 files changed, 29 insertions(+), 27 deletions(-) diff --git a/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts b/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts index db5e1b745ad43..13b75764b7d93 100644 --- a/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts +++ b/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts @@ -1,4 +1,4 @@ -import type { AwsIdentityProperties, CredentialProviderOptions, RegionalIdentityProvider } from "@aws-sdk/types"; +import type { AwsIdentityProperties, CredentialProviderOptions, RuntimeConfigIdentityProvider } from "@aws-sdk/types"; import { CredentialsProviderError } from "@smithy/property-provider"; import type { AwsCredentialIdentity, Logger } from "@smithy/types"; @@ -18,7 +18,7 @@ export interface CognitoIdentityCredentials extends AwsCredentialIdentity { /** * @internal */ -export type CognitoIdentityCredentialProvider = RegionalIdentityProvider; +export type CognitoIdentityCredentialProvider = RuntimeConfigIdentityProvider; /** * @internal diff --git a/packages/credential-provider-ini/src/fromIni.ts b/packages/credential-provider-ini/src/fromIni.ts index 9f6de87c5f851..d505746de1b6f 100644 --- a/packages/credential-provider-ini/src/fromIni.ts +++ b/packages/credential-provider-ini/src/fromIni.ts @@ -1,6 +1,6 @@ import type { AssumeRoleWithWebIdentityParams } from "@aws-sdk/credential-provider-web-identity"; import type { CredentialProviderOptions } from "@aws-sdk/types"; -import type { RegionalAwsCredentialIdentityProvider } from "@aws-sdk/types"; +import type { RuntimeConfigAwsCredentialIdentityProvider } from "@aws-sdk/types"; import { getProfileName, parseKnownFiles, SourceProfileInit } from "@smithy/shared-ini-file-loader"; import type { AwsCredentialIdentity, Pluggable } from "@smithy/types"; @@ -56,7 +56,7 @@ export interface FromIniInit extends SourceProfileInit, CredentialProviderOption * role assumption and multi-factor authentication. */ export const fromIni = - (_init: FromIniInit = {}): RegionalAwsCredentialIdentityProvider => + (_init: FromIniInit = {}): RuntimeConfigAwsCredentialIdentityProvider => async ({ callerClientConfig } = {}) => { const init: FromIniInit = { ..._init, diff --git a/packages/credential-provider-web-identity/src/fromWebToken.ts b/packages/credential-provider-web-identity/src/fromWebToken.ts index d2edd9ca6cc16..41489ce7467c0 100644 --- a/packages/credential-provider-web-identity/src/fromWebToken.ts +++ b/packages/credential-provider-web-identity/src/fromWebToken.ts @@ -1,7 +1,7 @@ import type { AwsIdentityProperties, CredentialProviderOptions, - RegionalAwsCredentialIdentityProvider, + RuntimeConfigAwsCredentialIdentityProvider, } from "@aws-sdk/types"; import type { AwsCredentialIdentity, Pluggable } from "@smithy/types"; @@ -155,7 +155,7 @@ export interface FromWebTokenInit * @internal */ export const fromWebToken = - (init: FromWebTokenInit): RegionalAwsCredentialIdentityProvider => + (init: FromWebTokenInit): RuntimeConfigAwsCredentialIdentityProvider => async (awsIdentityProperties?: AwsIdentityProperties) => { init.logger?.debug("@aws-sdk/credential-provider-web-identity - fromWebToken"); const { roleArn, roleSessionName, webIdentityToken, providerId, policyArns, policy, durationSeconds } = init; diff --git a/packages/credential-providers/src/createCredentialChain.spec.ts b/packages/credential-providers/src/createCredentialChain.spec.ts index 0cbee3d055539..e188c2e8d8968 100644 --- a/packages/credential-providers/src/createCredentialChain.spec.ts +++ b/packages/credential-providers/src/createCredentialChain.spec.ts @@ -1,4 +1,4 @@ -import { RegionalAwsCredentialIdentityProvider } from "@aws-sdk/types"; +import { RuntimeConfigAwsCredentialIdentityProvider } from "@aws-sdk/types"; import { ProviderError } from "@smithy/property-provider"; import { AwsCredentialIdentity, AwsCredentialIdentityProvider } from "@smithy/types"; import { describe, expect, test as it } from "vitest"; @@ -82,7 +82,7 @@ describe(createCredentialChain.name, () => { }); it("is compatible with contextual-region-aware credential providers", async () => { - const provider: RegionalAwsCredentialIdentityProvider = async ({ callerClientConfig } = {}) => { + const provider: RuntimeConfigAwsCredentialIdentityProvider = async ({ callerClientConfig } = {}) => { return { accessKeyId: "", secretAccessKey: "", diff --git a/packages/credential-providers/src/createCredentialChain.ts b/packages/credential-providers/src/createCredentialChain.ts index 0c536436f1a86..6bc80f26cde5e 100644 --- a/packages/credential-providers/src/createCredentialChain.ts +++ b/packages/credential-providers/src/createCredentialChain.ts @@ -1,7 +1,7 @@ import type { AwsIdentityProperties, - RegionalAwsCredentialIdentityProvider, - RegionalIdentityProvider, + RuntimeConfigAwsCredentialIdentityProvider, + RuntimeConfigIdentityProvider, } from "@aws-sdk/types"; import { ProviderError } from "@smithy/property-provider"; import type { AwsCredentialIdentityProvider } from "@smithy/types"; @@ -57,8 +57,8 @@ type Mutable = { * providers in sequence until one succeeds or all fail. */ export const createCredentialChain = ( - ...credentialProviders: RegionalAwsCredentialIdentityProvider[] -): RegionalAwsCredentialIdentityProvider & CustomCredentialChainOptions => { + ...credentialProviders: RuntimeConfigAwsCredentialIdentityProvider[] +): RuntimeConfigAwsCredentialIdentityProvider & CustomCredentialChainOptions => { let expireAfter = -1; const baseFunction = async (awsIdentityProperties?: AwsIdentityProperties) => { const credentials = await propertyProviderChain(...credentialProviders)(awsIdentityProperties); @@ -85,7 +85,7 @@ export const createCredentialChain = ( * @internal */ export const propertyProviderChain = - (...providers: Array>): RegionalIdentityProvider => + (...providers: Array>): RuntimeConfigIdentityProvider => async (awsIdentityProperties?: AwsIdentityProperties) => { if (providers.length === 0) { throw new ProviderError("No providers in chain"); diff --git a/packages/credential-providers/src/fromIni.ts b/packages/credential-providers/src/fromIni.ts index 6cc35a8c03ee3..40b64511178c0 100644 --- a/packages/credential-providers/src/fromIni.ts +++ b/packages/credential-providers/src/fromIni.ts @@ -1,5 +1,5 @@ import { fromIni as _fromIni, FromIniInit } from "@aws-sdk/credential-provider-ini"; -import type { RegionalAwsCredentialIdentityProvider } from "@aws-sdk/types"; +import type { RuntimeConfigAwsCredentialIdentityProvider } from "@aws-sdk/types"; /** * Creates a credential provider function that reads from a shared credentials file at `~/.aws/credentials` and a @@ -40,7 +40,7 @@ import type { RegionalAwsCredentialIdentityProvider } from "@aws-sdk/types"; * }); * ``` */ -export const fromIni = (init: FromIniInit = {}): RegionalAwsCredentialIdentityProvider => +export const fromIni = (init: FromIniInit = {}): RuntimeConfigAwsCredentialIdentityProvider => _fromIni({ ...init, }); diff --git a/packages/middleware-signing/src/awsAuthConfiguration.ts b/packages/middleware-signing/src/awsAuthConfiguration.ts index 12313d69b0b55..539f317441ee3 100644 --- a/packages/middleware-signing/src/awsAuthConfiguration.ts +++ b/packages/middleware-signing/src/awsAuthConfiguration.ts @@ -1,4 +1,4 @@ -import type { AwsCredentialIdentityProvider, RegionalAwsCredentialIdentityProvider } from "@aws-sdk/types"; +import type { AwsCredentialIdentityProvider, RuntimeConfigAwsCredentialIdentityProvider } from "@aws-sdk/types"; import { memoize } from "@smithy/property-provider"; import { SignatureV4, SignatureV4CryptoInit, SignatureV4Init } from "@smithy/signature-v4"; import { @@ -293,7 +293,7 @@ export const resolveSigV4AuthConfig = ( * @deprecated only used in legacy auth. */ const normalizeCredentialProvider = ( - credentials: AwsCredentialIdentity | Provider | RegionalAwsCredentialIdentityProvider + credentials: AwsCredentialIdentity | Provider | RuntimeConfigAwsCredentialIdentityProvider ): MemoizedProvider => { if (typeof credentials === "function") { return memoize( @@ -315,7 +315,7 @@ const normalizeCredentialProvider = ( * a binding to the config itself. */ const createConfigBoundCredentialProvider = (input: { - credentials?: AwsCredentialIdentity | AwsCredentialIdentityProvider | RegionalAwsCredentialIdentityProvider; + credentials?: AwsCredentialIdentity | AwsCredentialIdentityProvider | RuntimeConfigAwsCredentialIdentityProvider; credentialDefaultProvider: PreviouslyResolved["credentialDefaultProvider"]; region: PreviouslyResolved["region"]; }): AwsCredentialIdentityProvider => { @@ -327,7 +327,7 @@ const createConfigBoundCredentialProvider = (input: { }) ); const normalizedCreds = async () => - (normalizedCredentialsProvider as RegionalAwsCredentialIdentityProvider)({ + (normalizedCredentialsProvider as RuntimeConfigAwsCredentialIdentityProvider)({ callerClientConfig: { region: normalizeProvider(input.region), }, diff --git a/packages/token-providers/src/fromSso.ts b/packages/token-providers/src/fromSso.ts index 0f973dd8787cc..0266209a3b6c9 100644 --- a/packages/token-providers/src/fromSso.ts +++ b/packages/token-providers/src/fromSso.ts @@ -1,7 +1,7 @@ import { AwsIdentityProperties, CredentialProviderOptions, - RegionalIdentityProvider, + RuntimeConfigIdentityProvider, TokenIdentity, } from "@aws-sdk/types"; import { TokenProviderError } from "@smithy/property-provider"; @@ -36,7 +36,7 @@ export interface FromSsoInit extends SourceProfileInit, CredentialProviderOption * Creates a token provider that will read from SSO token cache or ssoOidc.createToken() call. */ export const fromSso = - (_init: FromSsoInit = {}): RegionalIdentityProvider => + (_init: FromSsoInit = {}): RuntimeConfigIdentityProvider => async (awsIdentityProperties?: AwsIdentityProperties) => { const init: FromSsoInit = { ..._init, diff --git a/packages/types/src/identity/AwsCredentialIdentity.ts b/packages/types/src/identity/AwsCredentialIdentity.ts index 0af1dcfb22d21..ad1091af88acd 100644 --- a/packages/types/src/identity/AwsCredentialIdentity.ts +++ b/packages/types/src/identity/AwsCredentialIdentity.ts @@ -17,23 +17,25 @@ export interface AwsIdentityProperties { * @public * * Variation of {@link IdentityProvider} which accepts a contextual - * client configuration that includes an AWS region. + * client configuration that includes an AWS region and potentially other + * configurable fields. * - * Used to link a credential provider to a client region if it is being called + * Used to link a credential provider to a client if it is being called * in the context of a client. */ -export type RegionalIdentityProvider = (awsIdentityProperties?: AwsIdentityProperties) => Promise; +export type RuntimeConfigIdentityProvider = (awsIdentityProperties?: AwsIdentityProperties) => Promise; /** * @public * * Variation of {@link AwsCredentialIdentityProvider} which accepts a contextual - * client configuration that includes an AWS region. + * client configuration that includes an AWS region and potentially other + * configurable fields. * - * Used to link a credential provider to a client region if it is being called + * Used to link a credential provider to a client if it is being called * in the context of a client. */ -export type RegionalAwsCredentialIdentityProvider = RegionalIdentityProvider; +export type RuntimeConfigAwsCredentialIdentityProvider = RuntimeConfigIdentityProvider; /** * @public