Skip to content

Commit 0d0b14e

Browse files
authoredJan 22, 2025··
feat(credential-providers): pass caller client options to fromTemporaryCredentials inner STSClient (#6838)
* feat(credential-providers): pass caller client options to fromTemporaryCredentials inner STSClient * chore: update lockfile * chore(client-sts): rename variable * test(credential-providers): add unit test fromTempCreds
1 parent 9199f2f commit 0d0b14e

File tree

11 files changed

+231
-326
lines changed

11 files changed

+231
-326
lines changed
 

‎.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ CHANGELOG.md
44
**/*.hbs
55
**/*/report.md
66
clients/*/src/endpoint/ruleset.ts
7+
packages/nested-clients/src/submodules/*/endpoint/ruleset.ts
78
**/*.java

‎clients/client-sts/src/defaultStsRoleAssumers.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const resolveRegion = async (
8484
*/
8585
export const getDefaultRoleAssumer = (
8686
stsOptions: STSRoleAssumerOptions,
87-
stsClientCtor: new (options: STSClientConfig) => STSClient
87+
STSClient: new (options: STSClientConfig) => STSClient
8888
): RoleAssumer => {
8989
let stsClient: STSClient;
9090
let closureSourceCreds: AwsCredentialIdentity;
@@ -104,7 +104,8 @@ export const getDefaultRoleAssumer = (
104104
);
105105
const isCompatibleRequestHandler = !isH2(requestHandler);
106106

107-
stsClient = new stsClientCtor({
107+
stsClient = new STSClient({
108+
profile: stsOptions?.parentClientConfig?.profile,
108109
// A hack to make sts client uses the credential in current closure.
109110
credentialDefaultProvider: () => async () => closureSourceCreds,
110111
region: resolvedRegion,
@@ -146,7 +147,7 @@ export type RoleAssumerWithWebIdentity = (
146147
*/
147148
export const getDefaultRoleAssumerWithWebIdentity = (
148149
stsOptions: STSRoleAssumerOptions,
149-
stsClientCtor: new (options: STSClientConfig) => STSClient
150+
STSClient: new (options: STSClientConfig) => STSClient
150151
): RoleAssumerWithWebIdentity => {
151152
let stsClient: STSClient;
152153
return async (params) => {
@@ -164,7 +165,8 @@ export const getDefaultRoleAssumerWithWebIdentity = (
164165
);
165166
const isCompatibleRequestHandler = !isH2(requestHandler);
166167

167-
stsClient = new stsClientCtor({
168+
stsClient = new STSClient({
169+
profile: stsOptions?.parentClientConfig?.profile,
168170
region: resolvedRegion,
169171
requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined,
170172
logger: logger as any,

‎codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ const resolveRegion = async (
8181
*/
8282
export const getDefaultRoleAssumer = (
8383
stsOptions: STSRoleAssumerOptions,
84-
stsClientCtor: new (options: STSClientConfig) => STSClient
84+
STSClient: new (options: STSClientConfig) => STSClient
8585
): RoleAssumer => {
8686
let stsClient: STSClient;
8787
let closureSourceCreds: AwsCredentialIdentity;
@@ -101,7 +101,8 @@ export const getDefaultRoleAssumer = (
101101
);
102102
const isCompatibleRequestHandler = !isH2(requestHandler);
103103

104-
stsClient = new stsClientCtor({
104+
stsClient = new STSClient({
105+
profile: stsOptions?.parentClientConfig?.profile,
105106
// A hack to make sts client uses the credential in current closure.
106107
credentialDefaultProvider: () => async () => closureSourceCreds,
107108
region: resolvedRegion,
@@ -143,7 +144,7 @@ export type RoleAssumerWithWebIdentity = (
143144
*/
144145
export const getDefaultRoleAssumerWithWebIdentity = (
145146
stsOptions: STSRoleAssumerOptions,
146-
stsClientCtor: new (options: STSClientConfig) => STSClient
147+
STSClient: new (options: STSClientConfig) => STSClient
147148
): RoleAssumerWithWebIdentity => {
148149
let stsClient: STSClient;
149150
return async (params) => {
@@ -161,7 +162,8 @@ export const getDefaultRoleAssumerWithWebIdentity = (
161162
);
162163
const isCompatibleRequestHandler = !isH2(requestHandler);
163164

164-
stsClient = new stsClientCtor({
165+
stsClient = new STSClient({
166+
profile: stsOptions?.parentClientConfig?.profile,
165167
region: resolvedRegion,
166168
requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined,
167169
logger: logger as any,

‎packages/credential-providers/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"@aws-sdk/credential-provider-web-identity": "*",
4343
"@aws-sdk/nested-clients": "*",
4444
"@aws-sdk/types": "*",
45+
"@smithy/core": "^3.0.0",
4546
"@smithy/credential-provider-imds": "^4.0.0",
4647
"@smithy/property-provider": "^4.0.0",
4748
"@smithy/types": "^4.0.0",

‎packages/credential-providers/src/fromTemporaryCredentials.base.ts

+89-10
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,39 @@ import type {
44
CredentialProviderOptions,
55
RuntimeConfigAwsCredentialIdentityProvider,
66
} from "@aws-sdk/types";
7+
import { normalizeProvider } from "@smithy/core";
78
import { CredentialsProviderError } from "@smithy/property-provider";
8-
import { AwsCredentialIdentity, AwsCredentialIdentityProvider, Pluggable } from "@smithy/types";
9+
import { AwsCredentialIdentity, AwsCredentialIdentityProvider, Logger, Pluggable, RequestHandler } from "@smithy/types";
910

1011
export interface FromTemporaryCredentialsOptions extends CredentialProviderOptions {
1112
params: Omit<AssumeRoleCommandInput, "RoleSessionName"> & { RoleSessionName?: string };
1213
masterCredentials?: AwsCredentialIdentity | AwsCredentialIdentityProvider;
1314
clientConfig?: STSClientConfig;
15+
logger?: Logger;
1416
clientPlugins?: Pluggable<any, any>[];
1517
mfaCodeProvider?: (mfaSerial: string) => Promise<string>;
1618
}
1719

20+
const ASSUME_ROLE_DEFAULT_REGION = "us-east-1";
21+
1822
export const fromTemporaryCredentials = (
1923
options: FromTemporaryCredentialsOptions,
2024
credentialDefaultProvider?: () => AwsCredentialIdentityProvider
2125
): RuntimeConfigAwsCredentialIdentityProvider => {
2226
let stsClient: STSClient;
2327
return async (awsIdentityProperties: AwsIdentityProperties = {}): Promise<AwsCredentialIdentity> => {
24-
options.logger?.debug("@aws-sdk/credential-providers - fromTemporaryCredentials (STS)");
28+
const { callerClientConfig } = awsIdentityProperties;
29+
const logger = options.logger ?? callerClientConfig?.logger;
30+
logger?.debug("@aws-sdk/credential-providers - fromTemporaryCredentials (STS)");
31+
2532
const params = { ...options.params, RoleSessionName: options.params.RoleSessionName ?? "aws-sdk-js-" + Date.now() };
2633
if (params?.SerialNumber) {
2734
if (!options.mfaCodeProvider) {
2835
throw new CredentialsProviderError(
2936
`Temporary credential requires multi-factor authentication, but no MFA code callback was provided.`,
3037
{
3138
tryNextLink: false,
32-
logger: options.logger,
39+
logger,
3340
}
3441
);
3542
}
@@ -42,14 +49,68 @@ export const fromTemporaryCredentials = (
4249
const defaultCredentialsOrError =
4350
typeof credentialDefaultProvider === "function" ? credentialDefaultProvider() : undefined;
4451

45-
const { callerClientConfig } = awsIdentityProperties;
52+
const credentialSources = [
53+
options.masterCredentials,
54+
options.clientConfig?.credentials,
55+
/**
56+
* Important (!): callerClientConfig?.credentials is not a valid
57+
* credential source for this provider, because this function
58+
* is the caller client's credential provider function.
59+
*/
60+
void callerClientConfig?.credentials,
61+
callerClientConfig?.credentialDefaultProvider?.(),
62+
defaultCredentialsOrError,
63+
];
64+
let credentialSource = "STS client default credentials";
65+
if (credentialSources[0]) {
66+
credentialSource = "options.masterCredentials";
67+
} else if (credentialSources[1]) {
68+
credentialSource = "options.clientConfig.credentials";
69+
} else if (credentialSources[2]) {
70+
// This branch is not possible, see above void note.
71+
// This code is here to prevent accidental attempts to utilize
72+
// the invalid credential source.
73+
credentialSource = "caller client's credentials";
74+
throw new Error("fromTemporaryCredentials recursion in callerClientConfig.credentials");
75+
} else if (credentialSources[3]) {
76+
credentialSource = "caller client's credentialDefaultProvider";
77+
} else if (credentialSources[4]) {
78+
credentialSource = "AWS SDK default credentials";
79+
}
80+
81+
const regionSources = [options.clientConfig?.region, callerClientConfig?.region, ASSUME_ROLE_DEFAULT_REGION];
82+
let regionSource = "default partition's default region";
83+
if (regionSources[0]) {
84+
regionSource = "options.clientConfig.region";
85+
} else if (regionSources[1]) {
86+
regionSource = "caller client's region";
87+
}
88+
89+
const requestHandlerSources = [
90+
filterRequestHandler(options.clientConfig?.requestHandler),
91+
filterRequestHandler(callerClientConfig?.requestHandler),
92+
];
93+
let requestHandlerSource = "STS default requestHandler";
94+
if (requestHandlerSources[0]) {
95+
requestHandlerSource = "options.clientConfig.requestHandler";
96+
} else if (requestHandlerSources[1]) {
97+
requestHandlerSource = "caller client's requestHandler";
98+
}
99+
100+
logger?.debug?.(
101+
`@aws-sdk/credential-providers - fromTemporaryCredentials STS client init with ` +
102+
`${regionSource}=${await normalizeProvider(
103+
coalesce(regionSources)
104+
)()}, ${credentialSource}, ${requestHandlerSource}.`
105+
);
106+
46107
stsClient = new STSClient({
47108
...options.clientConfig,
48-
credentials:
49-
options.masterCredentials ??
50-
options.clientConfig?.credentials ??
51-
callerClientConfig?.credentialDefaultProvider?.() ??
52-
defaultCredentialsOrError,
109+
credentials: coalesce(credentialSources),
110+
logger,
111+
profile: options.clientConfig?.profile ?? callerClientConfig?.profile,
112+
region: coalesce(regionSources),
113+
requestHandler: coalesce(requestHandlerSources),
53114
});
54115
}
55116
if (options.clientPlugins) {
@@ -60,7 +121,7 @@ export const fromTemporaryCredentials = (
60121
const { Credentials } = await stsClient.send(new AssumeRoleCommand(params));
61122
if (!Credentials || !Credentials.AccessKeyId || !Credentials.SecretAccessKey) {
62123
throw new CredentialsProviderError(`Invalid response from STS.assumeRole call with role ${params.RoleArn}`, {
63-
logger: options.logger,
124+
logger,
64125
});
65126
}
66127
return {
@@ -73,3 +134,21 @@ export const fromTemporaryCredentials = (
73134
};
74135
};
75136
};
137+
138+
/**
139+
* @internal
140+
*/
141+
const filterRequestHandler = (requestHandler: STSClientConfig["requestHandler"]): undefined | typeof requestHandler => {
142+
return (requestHandler as RequestHandler<any, any>)?.metadata?.handlerProtocol === "h2" ? undefined : requestHandler;
143+
};
144+
145+
/**
146+
* @internal
147+
*/
148+
const coalesce = (args: any) => {
149+
for (const item of args) {
150+
if (item !== undefined) {
151+
return item;
152+
}
153+
}
154+
};

‎packages/credential-providers/src/fromTemporaryCredentials.spec.ts

+42
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ describe("fromTemporaryCredentials", () => {
8989
await provider();
9090
expect(vi.mocked(STSClient as any)).toHaveBeenCalledWith({
9191
credentials: masterCredentials,
92+
logger: void 0,
93+
profile: void 0,
94+
region: "us-east-1",
95+
requestHandler: void 0,
9296
});
9397
expect(mockUsePlugin).toHaveBeenCalledTimes(1);
9498
expect(mockUsePlugin).toHaveBeenNthCalledWith(1, plugin);
@@ -193,6 +197,44 @@ describe("fromTemporaryCredentials", () => {
193197
});
194198
});
195199

200+
it("uses caller client options if not overridden with provider client options", async () => {
201+
const provider = fromTemporaryCredentialsNode({
202+
params: {
203+
RoleArn,
204+
RoleSessionName,
205+
},
206+
});
207+
const logger = {
208+
debug() {},
209+
info() {},
210+
warn() {},
211+
error() {},
212+
};
213+
const credentials = {
214+
accessKeyId: "",
215+
secretAccessKey: "",
216+
};
217+
const credentialProvider = async () => credentials;
218+
const regionProvider = async () => "B";
219+
await provider({
220+
callerClientConfig: {
221+
profile: "A",
222+
region: regionProvider,
223+
logger,
224+
requestHandler: Symbol.for("requestHandler") as any,
225+
credentialDefaultProvider: () => credentialProvider,
226+
},
227+
});
228+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toEqual({
229+
profile: "A",
230+
region: regionProvider,
231+
logger,
232+
requestHandler: Symbol.for("requestHandler") as any,
233+
// mockImpl resolved the credentials.
234+
credentials,
235+
});
236+
});
237+
196238
it("should allow assume roles assuming roles assuming roles ad infinitum", async () => {
197239
const roleArnOf = (id: string) => `arn:aws:iam::123456789:role/${id}`;
198240
const idOf = (roleArn: string) => roleArn.split("/")?.[1] ?? "UNKNOWN";

0 commit comments

Comments
 (0)
Please sign in to comment.