From a65995fd23de4a96b737b10398f9855795283a89 Mon Sep 17 00:00:00 2001 From: George Fu Date: Fri, 13 Dec 2024 15:15:31 -0500 Subject: [PATCH] feat(credential-providers): make credential providers aware of caller client region (#6726) * feat(credential-providers): make credential providers aware of contextual client region * chore: update lockfile * test(credential-provider-node): additional integ tests for cognito * feat(credential-providers): fix tests, add chaining support * feat(credential-providers): rename contextClientConfig to callerClientConfig * Update packages/core/src/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts Co-authored-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> * test: unit test fixes * feat(credential-providers): rename types --------- Co-authored-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> --- package.json | 1 + .../aws_sdk/resolveAwsSdkSigV4Config.ts | 20 +- .../src/fromCognitoIdentity.ts | 13 +- .../src/fromCognitoIdentityPool.ts | 15 +- packages/credential-provider-ini/package.json | 4 +- .../src/fromIni.integ.spec.ts | 242 ++++++++++++++++++ .../credential-provider-ini/src/fromIni.ts | 16 +- .../src/resolveAssumeRoleCredentials.ts | 23 +- .../vitest.config.integ.ts | 8 + .../credential-provider-node/package.json | 2 +- .../credential-provider-node.integ.spec.ts | 149 ++++++++++- .../src/fromWebToken.ts | 22 +- .../src/createCredentialChain.spec.ts | 30 +++ .../src/createCredentialChain.ts | 41 ++- 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 | 22 +- .../src/identity/AwsCredentialIdentity.ts | 40 ++- yarn.lock | 1 - 22 files changed, 666 insertions(+), 82 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/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..8962aba484214 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,14 @@ export const resolveAwsSdkSigV4Config = ( ) ); } else { - normalizedCreds = async () => { + credentialsProvider = async () => { throw new Error("`credentials` is missing"); }; } } + const boundCredentialsProvider = async () => credentialsProvider!({ callerClientConfig: config }); + // Populate sigv4 arguments const { // Default for signingEscapePath @@ -170,7 +172,7 @@ export const resolveAwsSdkSigV4Config = ( const params: SignatureV4Init & SignatureV4CryptoInit = { ...config, - credentials: normalizedCreds!, + credentials: boundCredentialsProvider, region: config.signingRegion, service: config.signingName, sha256, @@ -206,7 +208,7 @@ export const resolveAwsSdkSigV4Config = ( const params: SignatureV4Init & SignatureV4CryptoInit = { ...config, - credentials: normalizedCreds!, + credentials: boundCredentialsProvider, region: config.signingRegion, service: config.signingName, sha256, @@ -224,10 +226,10 @@ export const resolveAwsSdkSigV4Config = ( signingEscapePath, credentials: isUserSupplied ? async () => - normalizedCreds!().then((creds: AttributedAwsCredentialIdentity) => + boundCredentialsProvider!().then((creds: AttributedAwsCredentialIdentity) => setCredentialFeature(creds, "CREDENTIALS_CODE", "e") ) - : normalizedCreds!, + : boundCredentialsProvider!, signer, }; }; diff --git a/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts b/packages/credential-provider-cognito-identity/src/fromCognitoIdentity.ts index 7b90018f43289..13b75764b7d93 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, RuntimeConfigIdentityProvider } 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 = RuntimeConfigIdentityProvider; /** * @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?.callerClientConfig?.region, }) ) ).send( diff --git a/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts b/packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts index 559b281897274..2cf4d5f122934 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?.callerClientConfig?.region, + }) ); let identityId: string | undefined = (cacheKey && (await cache.getItem(cacheKey))) as string | undefined; @@ -65,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 6526add499df9..b13e3af9e5882 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..db15da42fa6c0 --- /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 profile region", 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_us-stsar-1", + }); + }); + + 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: 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 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 160e4fbe08c5e..d505746de1b6f 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 { RuntimeConfigAwsCredentialIdentityProvider } 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,17 @@ export interface FromIniInit extends SourceProfileInit, CredentialProviderOption * role assumption and multi-factor authentication. */ export const fromIni = - (init: FromIniInit = {}): AwsCredentialIdentityProvider => - async () => { + (_init: FromIniInit = {}): RuntimeConfigAwsCredentialIdentityProvider => + async ({ callerClientConfig } = {}) => { + const init: FromIniInit = { + ..._init, + }; + if (callerClientConfig?.region) { + init.parentClientConfig = { + region: callerClientConfig.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..29d5648cee04d 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,15 @@ export const resolveAssumeRoleCredentials = async ( { ...options.clientConfig, credentialProviderLogger: options.logger, - parentClientConfig: options?.parentClientConfig, + parentClientConfig: { + ...options?.parentClientConfig, + region: region ?? options?.parentClientConfig?.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 +152,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 +166,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/package.json b/packages/credential-provider-node/package.json index a71c4699f97b0..2c5a5aec92efe 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..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 @@ -1,5 +1,7 @@ import { STS } from "@aws-sdk/client-sts"; import * as credentialProviderHttp from "@aws-sdk/credential-provider-http"; +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"; @@ -67,6 +69,8 @@ jest.mock("@smithy/node-http-handler", () => { assumeRoleArns.push(request.body.match(/RoleArn=(.*?)&/)?.[1]); } + const region = (request.hostname.match(/(sts|cognito-identity)\.(.*?)\./) || [, , "unknown"])[2]; + if (request.headers.Authorization === "container-authorization") { body.write( JSON.stringify({ @@ -94,7 +98,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 +113,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 @@ -129,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(); @@ -379,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", + sessionToken: "STS_AR_SESSION_TOKEN_us-stsar-1", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { CREDENTIALS_PROFILE_SOURCE_PROFILE: "o", @@ -409,7 +428,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,7 +458,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-gov-stsar-1", expiration: new Date("3000-01-01T00:00:00.000Z"), $source: { CREDENTIALS_PROFILE_SOURCE_PROFILE: "o", @@ -458,7 +477,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 +503,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 +581,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 +624,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 +669,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 +721,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 +772,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", @@ -785,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 caller 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 caller 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 caller 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 caller 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 caller 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 166af9a70ce01..41489ce7467c0 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, + RuntimeConfigAwsCredentialIdentityProvider, +} 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): RuntimeConfigAwsCredentialIdentityProvider => + 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,14 @@ export const fromWebToken = { ...init.clientConfig, credentialProviderLogger: init.logger, - parentClientConfig: init.parentClientConfig, + ...(awsIdentityProperties?.callerClientConfig?.region || init.parentClientConfig + ? { + parentClientConfig: { + region: awsIdentityProperties?.callerClientConfig?.region, + ...init.parentClientConfig, + }, + } + : {}), }, init.clientPlugins ); diff --git a/packages/credential-providers/src/createCredentialChain.spec.ts b/packages/credential-providers/src/createCredentialChain.spec.ts index 08679a87b3efe..e188c2e8d8968 100644 --- a/packages/credential-providers/src/createCredentialChain.spec.ts +++ b/packages/credential-providers/src/createCredentialChain.spec.ts @@ -1,3 +1,4 @@ +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"; @@ -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: RuntimeConfigAwsCredentialIdentityProvider = async ({ callerClientConfig } = {}) => { + return { + accessKeyId: "", + secretAccessKey: "", + sessionToken: (await callerClientConfig?.region()) ?? "wrong_region", + }; + }; + const errorProvider = async () => { + throw new ProviderError("", { tryNextLink: true }); + }; + + const chain = createCredentialChain(errorProvider, provider); + + expect( + await chain({ + callerClientConfig: { + 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..6bc80f26cde5e 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, + RuntimeConfigAwsCredentialIdentityProvider, + RuntimeConfigIdentityProvider, +} 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: RuntimeConfigAwsCredentialIdentityProvider[] +): RuntimeConfigAwsCredentialIdentityProvider & 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>): RuntimeConfigIdentityProvider => + 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; + }; diff --git a/packages/credential-providers/src/fromIni.ts b/packages/credential-providers/src/fromIni.ts index e98825009c454..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 { AwsCredentialIdentityProvider } from "@smithy/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 { AwsCredentialIdentityProvider } from "@smithy/types"; * }); * ``` */ -export const fromIni = (init: FromIniInit = {}): AwsCredentialIdentityProvider => +export const fromIni = (init: FromIniInit = {}): RuntimeConfigAwsCredentialIdentityProvider => _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..539f317441ee3 100644 --- a/packages/middleware-signing/src/awsAuthConfiguration.ts +++ b/packages/middleware-signing/src/awsAuthConfiguration.ts @@ -1,3 +1,4 @@ +import type { AwsCredentialIdentityProvider, RuntimeConfigAwsCredentialIdentityProvider } 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 | RuntimeConfigAwsCredentialIdentityProvider ): 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 | RuntimeConfigAwsCredentialIdentityProvider; + 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 RuntimeConfigAwsCredentialIdentityProvider)({ + callerClientConfig: { + 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..0266209a3b6c9 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, + RuntimeConfigIdentityProvider, + TokenIdentity, +} from "@aws-sdk/types"; import { TokenProviderError } from "@smithy/property-provider"; import { getProfileName, @@ -31,8 +36,19 @@ 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 = {}): RuntimeConfigIdentityProvider => + async (awsIdentityProperties?: AwsIdentityProperties) => { + const init: FromSsoInit = { + ..._init, + ...(awsIdentityProperties?.callerClientConfig?.region + ? { + parentClientConfig: { + region: awsIdentityProperties?.callerClientConfig?.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..ad1091af88acd 100644 --- a/packages/types/src/identity/AwsCredentialIdentity.ts +++ b/packages/types/src/identity/AwsCredentialIdentity.ts @@ -2,8 +2,46 @@ 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 { + callerClientConfig?: { + region(): Promise; + }; +} + +/** + * @public + * + * Variation of {@link IdentityProvider} which accepts a contextual + * client configuration that includes an AWS region and potentially other + * configurable fields. + * + * Used to link a credential provider to a client if it is being called + * in the context of a client. + */ +export type RuntimeConfigIdentityProvider = (awsIdentityProperties?: AwsIdentityProperties) => Promise; + +/** + * @public + * + * Variation of {@link AwsCredentialIdentityProvider} which accepts a contextual + * client configuration that includes an AWS region and potentially other + * configurable fields. + * + * Used to link a credential provider to a client if it is being called + * in the context of a client. + */ +export type RuntimeConfigAwsCredentialIdentityProvider = RuntimeConfigIdentityProvider; + +/** + * @public + * + * AwsCredentialIdentity with source attribution metadata. + */ export type AttributedAwsCredentialIdentity = AwsCredentialIdentity & { $source?: AwsSdkCredentialsFeatures; }; 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"