From dead1ea85addf23c28107de5d018588de72ff4b0 Mon Sep 17 00:00:00 2001 From: Jack Reinhardt Date: Mon, 20 Oct 2025 11:30:45 -0700 Subject: [PATCH 1/3] fix(client-sts): propagate parent configuration to nested sts client --- clients/client-sts/src/defaultStsRoleAssumers.ts | 4 ++-- .../codegen/sts-client-defaultRoleAssumers.spec.ts | 6 ++++++ .../typescript/codegen/sts-client-defaultStsRoleAssumers.ts | 4 ++-- .../src/submodules/sts/defaultStsRoleAssumers.ts | 4 ++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/clients/client-sts/src/defaultStsRoleAssumers.ts b/clients/client-sts/src/defaultStsRoleAssumers.ts index 9eaec4f7051c..1ed3a145db72 100644 --- a/clients/client-sts/src/defaultStsRoleAssumers.ts +++ b/clients/client-sts/src/defaultStsRoleAssumers.ts @@ -105,7 +105,7 @@ export const getDefaultRoleAssumer = ( const isCompatibleRequestHandler = !isH2(requestHandler); stsClient = new STSClient({ - profile: stsOptions?.parentClientConfig?.profile, + ...stsOptions?.parentClientConfig, // A hack to make sts client uses the credential in current closure. credentialDefaultProvider: () => async () => closureSourceCreds, region: resolvedRegion, @@ -166,7 +166,7 @@ export const getDefaultRoleAssumerWithWebIdentity = ( const isCompatibleRequestHandler = !isH2(requestHandler); stsClient = new STSClient({ - profile: stsOptions?.parentClientConfig?.profile, + ...stsOptions?.parentClientConfig, region: resolvedRegion, requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined, logger: logger as any, diff --git a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultRoleAssumers.spec.ts b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultRoleAssumers.spec.ts index c3a96fec9bb9..897e9c080fbf 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultRoleAssumers.spec.ts +++ b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultRoleAssumers.spec.ts @@ -165,11 +165,13 @@ describe("getDefaultRoleAssumer", () => { }; const region = "some-region"; const handler = new NodeHttpHandler(); + const customUserAgent = [["custom-agent", "1.0.0"]]; const roleAssumer = getDefaultRoleAssumer({ parentClientConfig: { region, logger, requestHandler: handler, + customUserAgent, }, }); const params: AssumeRoleCommandInput = { @@ -183,6 +185,7 @@ describe("getDefaultRoleAssumer", () => { logger, requestHandler: handler, region, + customUserAgent, }); }); @@ -275,10 +278,12 @@ describe("getDefaultRoleAssumerWithWebIdentity", () => { }; const region = "some-region"; const handler = new NodeHttpHandler(); + const customUserAgent = [["custom-agent", "1.0.0"]]; const roleAssumerWithWebIdentity = getDefaultRoleAssumerWithWebIdentity({ region, logger, requestHandler: handler, + customUserAgent, }); const params: AssumeRoleWithWebIdentityCommandInput = { RoleArn: "arn:aws:foo", @@ -291,6 +296,7 @@ describe("getDefaultRoleAssumerWithWebIdentity", () => { logger, requestHandler: handler, region, + customUserAgent, }); }); diff --git a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts index 9c613ad981bf..29337e096321 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts +++ b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts @@ -102,7 +102,7 @@ export const getDefaultRoleAssumer = ( const isCompatibleRequestHandler = !isH2(requestHandler); stsClient = new STSClient({ - profile: stsOptions?.parentClientConfig?.profile, + ...stsOptions?.parentClientConfig, // A hack to make sts client uses the credential in current closure. credentialDefaultProvider: () => async () => closureSourceCreds, region: resolvedRegion, @@ -163,7 +163,7 @@ export const getDefaultRoleAssumerWithWebIdentity = ( const isCompatibleRequestHandler = !isH2(requestHandler); stsClient = new STSClient({ - profile: stsOptions?.parentClientConfig?.profile, + ...stsOptions?.parentClientConfig, region: resolvedRegion, requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined, logger: logger as any, diff --git a/packages/nested-clients/src/submodules/sts/defaultStsRoleAssumers.ts b/packages/nested-clients/src/submodules/sts/defaultStsRoleAssumers.ts index 9eaec4f7051c..1ed3a145db72 100644 --- a/packages/nested-clients/src/submodules/sts/defaultStsRoleAssumers.ts +++ b/packages/nested-clients/src/submodules/sts/defaultStsRoleAssumers.ts @@ -105,7 +105,7 @@ export const getDefaultRoleAssumer = ( const isCompatibleRequestHandler = !isH2(requestHandler); stsClient = new STSClient({ - profile: stsOptions?.parentClientConfig?.profile, + ...stsOptions?.parentClientConfig, // A hack to make sts client uses the credential in current closure. credentialDefaultProvider: () => async () => closureSourceCreds, region: resolvedRegion, @@ -166,7 +166,7 @@ export const getDefaultRoleAssumerWithWebIdentity = ( const isCompatibleRequestHandler = !isH2(requestHandler); stsClient = new STSClient({ - profile: stsOptions?.parentClientConfig?.profile, + ...stsOptions?.parentClientConfig, region: resolvedRegion, requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined, logger: logger as any, From 330bbfc07385907a3053f82bed61c42030771b3a Mon Sep 17 00:00:00 2001 From: George Fu Date: Mon, 20 Oct 2025 15:21:28 -0400 Subject: [PATCH 2/3] fix(nested-clients): propagate clientConfig to nested STS client for role assumer --- clients/client-sts/src/defaultStsRoleAssumers.ts | 4 ++-- .../typescript/codegen/sts-client-defaultStsRoleAssumers.ts | 4 ++-- .../src/submodules/sts/defaultStsRoleAssumers.ts | 4 ++-- scripts/generate-clients/single-service.js | 5 +++++ 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/clients/client-sts/src/defaultStsRoleAssumers.ts b/clients/client-sts/src/defaultStsRoleAssumers.ts index 1ed3a145db72..0a7f518cfb6d 100644 --- a/clients/client-sts/src/defaultStsRoleAssumers.ts +++ b/clients/client-sts/src/defaultStsRoleAssumers.ts @@ -105,7 +105,7 @@ export const getDefaultRoleAssumer = ( const isCompatibleRequestHandler = !isH2(requestHandler); stsClient = new STSClient({ - ...stsOptions?.parentClientConfig, + ...stsOptions, // A hack to make sts client uses the credential in current closure. credentialDefaultProvider: () => async () => closureSourceCreds, region: resolvedRegion, @@ -166,7 +166,7 @@ export const getDefaultRoleAssumerWithWebIdentity = ( const isCompatibleRequestHandler = !isH2(requestHandler); stsClient = new STSClient({ - ...stsOptions?.parentClientConfig, + ...stsOptions, region: resolvedRegion, requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined, logger: logger as any, diff --git a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts index 29337e096321..bdf0ca1c8dc1 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts +++ b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts @@ -102,7 +102,7 @@ export const getDefaultRoleAssumer = ( const isCompatibleRequestHandler = !isH2(requestHandler); stsClient = new STSClient({ - ...stsOptions?.parentClientConfig, + ...stsOptions, // A hack to make sts client uses the credential in current closure. credentialDefaultProvider: () => async () => closureSourceCreds, region: resolvedRegion, @@ -163,7 +163,7 @@ export const getDefaultRoleAssumerWithWebIdentity = ( const isCompatibleRequestHandler = !isH2(requestHandler); stsClient = new STSClient({ - ...stsOptions?.parentClientConfig, + ...stsOptions, region: resolvedRegion, requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined, logger: logger as any, diff --git a/packages/nested-clients/src/submodules/sts/defaultStsRoleAssumers.ts b/packages/nested-clients/src/submodules/sts/defaultStsRoleAssumers.ts index 1ed3a145db72..0a7f518cfb6d 100644 --- a/packages/nested-clients/src/submodules/sts/defaultStsRoleAssumers.ts +++ b/packages/nested-clients/src/submodules/sts/defaultStsRoleAssumers.ts @@ -105,7 +105,7 @@ export const getDefaultRoleAssumer = ( const isCompatibleRequestHandler = !isH2(requestHandler); stsClient = new STSClient({ - ...stsOptions?.parentClientConfig, + ...stsOptions, // A hack to make sts client uses the credential in current closure. credentialDefaultProvider: () => async () => closureSourceCreds, region: resolvedRegion, @@ -166,7 +166,7 @@ export const getDefaultRoleAssumerWithWebIdentity = ( const isCompatibleRequestHandler = !isH2(requestHandler); stsClient = new STSClient({ - ...stsOptions?.parentClientConfig, + ...stsOptions, region: resolvedRegion, requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined, logger: logger as any, diff --git a/scripts/generate-clients/single-service.js b/scripts/generate-clients/single-service.js index c1c0d5ffac55..ba4ba6aa87c5 100644 --- a/scripts/generate-clients/single-service.js +++ b/scripts/generate-clients/single-service.js @@ -41,6 +41,11 @@ const { solo } = yargs(process.argv.slice(2)) } catch (ignored) {} } + if (solo === "sts" || solo === "sso-oidc") { + const generateNestedClients = require("./nested-clients/generate-nested-clients"); + await generateNestedClients(); + } + console.log("================ starting prettier ================", "\n", new Date().toString(), solo); await spawnProcess("npx", [ "prettier", From 377debb0264093ced1374e5272b174c60b5694a8 Mon Sep 17 00:00:00 2001 From: George Fu Date: Mon, 20 Oct 2025 16:04:56 -0400 Subject: [PATCH 3/3] test: integ test for inner STS clientConfig pass-through --- .../client-sts/src/defaultStsRoleAssumers.ts | 6 +- .../sts-client-defaultRoleAssumers.spec.ts | 6 -- .../sts-client-defaultStsRoleAssumers.ts | 6 +- .../credential-provider-node.integ.spec.ts | 73 ++++++++++++++++++- .../submodules/sts/defaultStsRoleAssumers.ts | 6 +- 5 files changed, 87 insertions(+), 10 deletions(-) diff --git a/clients/client-sts/src/defaultStsRoleAssumers.ts b/clients/client-sts/src/defaultStsRoleAssumers.ts index 0a7f518cfb6d..bb7e08de7d18 100644 --- a/clients/client-sts/src/defaultStsRoleAssumers.ts +++ b/clients/client-sts/src/defaultStsRoleAssumers.ts @@ -15,7 +15,7 @@ import type { STSClient, STSClientConfig, STSClientResolvedConfig } from "./STSC /** * @public */ -export type STSRoleAssumerOptions = Pick & { +export type STSRoleAssumerOptions = Pick & { credentialProviderLogger?: Logger; parentClientConfig?: CredentialProviderOptions["parentClientConfig"]; }; @@ -93,6 +93,7 @@ export const getDefaultRoleAssumer = ( if (!stsClient) { const { logger = stsOptions?.parentClientConfig?.logger, + profile = stsOptions?.parentClientConfig?.profile, region, requestHandler = stsOptions?.parentClientConfig?.requestHandler, credentialProviderLogger, @@ -106,6 +107,7 @@ export const getDefaultRoleAssumer = ( stsClient = new STSClient({ ...stsOptions, + profile, // A hack to make sts client uses the credential in current closure. credentialDefaultProvider: () => async () => closureSourceCreds, region: resolvedRegion, @@ -154,6 +156,7 @@ export const getDefaultRoleAssumerWithWebIdentity = ( if (!stsClient) { const { logger = stsOptions?.parentClientConfig?.logger, + profile = stsOptions?.parentClientConfig?.profile, region, requestHandler = stsOptions?.parentClientConfig?.requestHandler, credentialProviderLogger, @@ -167,6 +170,7 @@ export const getDefaultRoleAssumerWithWebIdentity = ( stsClient = new STSClient({ ...stsOptions, + profile, region: resolvedRegion, requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined, logger: logger as any, diff --git a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultRoleAssumers.spec.ts b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultRoleAssumers.spec.ts index 897e9c080fbf..c3a96fec9bb9 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultRoleAssumers.spec.ts +++ b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultRoleAssumers.spec.ts @@ -165,13 +165,11 @@ describe("getDefaultRoleAssumer", () => { }; const region = "some-region"; const handler = new NodeHttpHandler(); - const customUserAgent = [["custom-agent", "1.0.0"]]; const roleAssumer = getDefaultRoleAssumer({ parentClientConfig: { region, logger, requestHandler: handler, - customUserAgent, }, }); const params: AssumeRoleCommandInput = { @@ -185,7 +183,6 @@ describe("getDefaultRoleAssumer", () => { logger, requestHandler: handler, region, - customUserAgent, }); }); @@ -278,12 +275,10 @@ describe("getDefaultRoleAssumerWithWebIdentity", () => { }; const region = "some-region"; const handler = new NodeHttpHandler(); - const customUserAgent = [["custom-agent", "1.0.0"]]; const roleAssumerWithWebIdentity = getDefaultRoleAssumerWithWebIdentity({ region, logger, requestHandler: handler, - customUserAgent, }); const params: AssumeRoleWithWebIdentityCommandInput = { RoleArn: "arn:aws:foo", @@ -296,7 +291,6 @@ describe("getDefaultRoleAssumerWithWebIdentity", () => { logger, requestHandler: handler, region, - customUserAgent, }); }); diff --git a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts index bdf0ca1c8dc1..69a2d0019161 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts +++ b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts @@ -12,7 +12,7 @@ import type { STSClient, STSClientConfig, STSClientResolvedConfig } from "./STSC /** * @public */ -export type STSRoleAssumerOptions = Pick & { +export type STSRoleAssumerOptions = Pick & { credentialProviderLogger?: Logger; parentClientConfig?: CredentialProviderOptions["parentClientConfig"]; }; @@ -90,6 +90,7 @@ export const getDefaultRoleAssumer = ( if (!stsClient) { const { logger = stsOptions?.parentClientConfig?.logger, + profile = stsOptions?.parentClientConfig?.profile, region, requestHandler = stsOptions?.parentClientConfig?.requestHandler, credentialProviderLogger, @@ -103,6 +104,7 @@ export const getDefaultRoleAssumer = ( stsClient = new STSClient({ ...stsOptions, + profile, // A hack to make sts client uses the credential in current closure. credentialDefaultProvider: () => async () => closureSourceCreds, region: resolvedRegion, @@ -151,6 +153,7 @@ export const getDefaultRoleAssumerWithWebIdentity = ( if (!stsClient) { const { logger = stsOptions?.parentClientConfig?.logger, + profile = stsOptions?.parentClientConfig?.profile, region, requestHandler = stsOptions?.parentClientConfig?.requestHandler, credentialProviderLogger, @@ -164,6 +167,7 @@ export const getDefaultRoleAssumerWithWebIdentity = ( stsClient = new STSClient({ ...stsOptions, + profile, region: resolvedRegion, requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined, logger: logger as any, diff --git a/packages/credential-provider-node/tests/credential-provider-node.integ.spec.ts b/packages/credential-provider-node/tests/credential-provider-node.integ.spec.ts index 76ffe992f178..e2bc97d60978 100644 --- a/packages/credential-provider-node/tests/credential-provider-node.integ.spec.ts +++ b/packages/credential-provider-node/tests/credential-provider-node.integ.spec.ts @@ -5,7 +5,7 @@ import { STS, STSExtensionConfiguration } 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 { HttpResponse } from "@smithy/protocol-http"; -import type { HttpRequest, NodeHttpHandlerOptions, ParsedIniData } from "@smithy/types"; +import type { HttpRequest, MiddlewareStack, NodeHttpHandlerOptions, ParsedIniData } from "@smithy/types"; import { AdaptiveRetryStrategy, StandardRetryStrategy } from "@smithy/util-retry"; import { PassThrough } from "node:stream"; import { homedir } from "node:os"; @@ -1371,4 +1371,75 @@ describe("credential-provider-node integration test", () => { ); }); }); + + describe("nested STS client", () => { + it("the clientConfig is propagated to the inner STS client used for AssumeRole ", async () => { + setIniProfileData({ + assume: { + region: "us-stsar-1", + aws_access_key_id: "ASSUME_STATIC_ACCESS_KEY", + aws_secret_access_key: "ASSUME_STATIC_SECRET_KEY", + }, + default: { + region: "us-stsar-1", + role_arn: "ROLE_ARN", + role_session_name: "ROLE_SESSION_NAME", + external_id: "EXTERNAL_ID", + source_profile: "assume", + }, + }); + + let request: HttpRequest | undefined = undefined; + + const logRequest = (next: any) => async (args: any) => { + const r = await next(args); + request = args.request; + return r; + }; + const logger = { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; + + const client = new STS({ + credentials: defaultProvider({ + clientPlugins: [ + { + applyToStack(stack: MiddlewareStack) { + stack.add(logRequest, { + step: "finalizeRequest", + }); + }, + }, + ], + clientConfig: { + customUserAgent: "my-custom-useragent", + endpoint: "https://localhost/endpoint", + logger, + }, + }), + }); + + const callerId = await client.getCallerIdentity(); + expect(callerId).toEqual({ + $metadata: { + attempts: 1, + cfId: undefined, + extendedRequestId: undefined, + httpStatusCode: 200, + requestId: undefined, + totalRetryDelay: 0, + }, + Account: "123456789012", + Arn: "arn:aws:iam::123456789012:user/Alice", + UserId: "AIDACKCEVSQ6C2EXAMPLE", + }); + expect(request!.headers?.["x-amz-user-agent"]).toMatch(/my-custom-useragent$/); + expect(request!.headers?.host).toMatch(/localhost$/); + expect(logger.debug).toHaveBeenCalled(); + expect(logger.info).toHaveBeenCalled(); + }); + }); }); diff --git a/packages/nested-clients/src/submodules/sts/defaultStsRoleAssumers.ts b/packages/nested-clients/src/submodules/sts/defaultStsRoleAssumers.ts index 0a7f518cfb6d..bb7e08de7d18 100644 --- a/packages/nested-clients/src/submodules/sts/defaultStsRoleAssumers.ts +++ b/packages/nested-clients/src/submodules/sts/defaultStsRoleAssumers.ts @@ -15,7 +15,7 @@ import type { STSClient, STSClientConfig, STSClientResolvedConfig } from "./STSC /** * @public */ -export type STSRoleAssumerOptions = Pick & { +export type STSRoleAssumerOptions = Pick & { credentialProviderLogger?: Logger; parentClientConfig?: CredentialProviderOptions["parentClientConfig"]; }; @@ -93,6 +93,7 @@ export const getDefaultRoleAssumer = ( if (!stsClient) { const { logger = stsOptions?.parentClientConfig?.logger, + profile = stsOptions?.parentClientConfig?.profile, region, requestHandler = stsOptions?.parentClientConfig?.requestHandler, credentialProviderLogger, @@ -106,6 +107,7 @@ export const getDefaultRoleAssumer = ( stsClient = new STSClient({ ...stsOptions, + profile, // A hack to make sts client uses the credential in current closure. credentialDefaultProvider: () => async () => closureSourceCreds, region: resolvedRegion, @@ -154,6 +156,7 @@ export const getDefaultRoleAssumerWithWebIdentity = ( if (!stsClient) { const { logger = stsOptions?.parentClientConfig?.logger, + profile = stsOptions?.parentClientConfig?.profile, region, requestHandler = stsOptions?.parentClientConfig?.requestHandler, credentialProviderLogger, @@ -167,6 +170,7 @@ export const getDefaultRoleAssumerWithWebIdentity = ( stsClient = new STSClient({ ...stsOptions, + profile, region: resolvedRegion, requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined, logger: logger as any,