Skip to content

Commit f46323d

Browse files
authored
fix(credential-providers): allow env and configFile selection of region in fromTemporaryCredentials (#6982)
1 parent 237cf3e commit f46323d

File tree

5 files changed

+196
-10
lines changed

5 files changed

+196
-10
lines changed

packages/credential-providers/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"clean": "rimraf ./dist-* && rimraf *.tsbuildinfo",
1818
"extract:docs": "api-extractor run --local",
1919
"test": "yarn g:vitest run",
20-
"test:integration": "jest -c jest.config.integ.js",
20+
"test:integration": "npx jest -c jest.config.integ.js",
2121
"test:watch": "yarn g:vitest watch"
2222
},
2323
"keywords": [
@@ -42,8 +42,10 @@
4242
"@aws-sdk/credential-provider-web-identity": "*",
4343
"@aws-sdk/nested-clients": "*",
4444
"@aws-sdk/types": "*",
45+
"@smithy/config-resolver": "^4.1.0",
4546
"@smithy/core": "^3.2.0",
4647
"@smithy/credential-provider-imds": "^4.0.2",
48+
"@smithy/node-config-provider": "^4.0.2",
4749
"@smithy/property-provider": "^4.0.2",
4850
"@smithy/types": "^4.2.0",
4951
"tslib": "^2.6.2"

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

+15-3
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@ const ASSUME_ROLE_DEFAULT_REGION = "us-east-1";
2121

2222
export const fromTemporaryCredentials = (
2323
options: FromTemporaryCredentialsOptions,
24-
credentialDefaultProvider?: () => AwsCredentialIdentityProvider
24+
credentialDefaultProvider?: () => AwsCredentialIdentityProvider,
25+
regionProvider?: ({ profile }: { profile?: string }) => Promise<string | undefined>
2526
): RuntimeConfigAwsCredentialIdentityProvider => {
2627
let stsClient: STSClient;
2728
return async (awsIdentityProperties: AwsIdentityProperties = {}): Promise<AwsCredentialIdentity> => {
2829
const { callerClientConfig } = awsIdentityProperties;
30+
const profile = options.clientConfig?.profile ?? callerClientConfig?.profile;
31+
2932
const logger = options.logger ?? callerClientConfig?.logger;
3033
logger?.debug("@aws-sdk/credential-providers - fromTemporaryCredentials (STS)");
3134

@@ -78,12 +81,21 @@ export const fromTemporaryCredentials = (
7881
credentialSource = "AWS SDK default credentials";
7982
}
8083

81-
const regionSources = [options.clientConfig?.region, callerClientConfig?.region, ASSUME_ROLE_DEFAULT_REGION];
84+
const regionSources = [
85+
options.clientConfig?.region,
86+
callerClientConfig?.region,
87+
await regionProvider?.({
88+
profile,
89+
}),
90+
ASSUME_ROLE_DEFAULT_REGION,
91+
];
8292
let regionSource = "default partition's default region";
8393
if (regionSources[0]) {
8494
regionSource = "options.clientConfig.region";
8595
} else if (regionSources[1]) {
8696
regionSource = "caller client's region";
97+
} else if (regionSources[2]) {
98+
regionSource = "file or env region";
8799
}
88100

89101
const requestHandlerSources = [
@@ -108,7 +120,7 @@ export const fromTemporaryCredentials = (
108120
...options.clientConfig,
109121
credentials: coalesce(credentialSources),
110122
logger,
111-
profile: options.clientConfig?.profile ?? callerClientConfig?.profile,
123+
profile,
112124
region: coalesce(regionSources),
113125
requestHandler: coalesce(requestHandlerSources),
114126
});

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

+159-5
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
import { AssumeRoleCommand, STSClient } from "@aws-sdk/nested-clients/sts";
2-
import { beforeEach, describe, expect, test as it, vi } from "vitest";
2+
import { LoadedConfigSelectors } from "@smithy/node-config-provider";
3+
import type { ParsedIniData } from "@smithy/types";
4+
import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest";
35

46
import { fromTemporaryCredentials as fromTemporaryCredentialsNode } from "./fromTemporaryCredentials";
57
import { fromTemporaryCredentials } from "./fromTemporaryCredentials.base";
68

79
const mockSend = vi.fn();
810
const mockUsePlugin = vi.fn();
11+
912
vi.mock("@aws-sdk/nested-clients/sts", () => ({
1013
STSClient: vi.fn().mockImplementation((config) => ({
1114
config,
1215
send: vi.fn().mockImplementation(async function (command) {
1316
// Mock resolving client credentials provider at send()
14-
if (typeof config.credentials === "function") config.credentials = await config.credentials();
15-
return await mockSend(command);
17+
if (typeof config.credentials === "function") {
18+
config.credentials = await config.credentials();
19+
}
20+
return mockSend(command);
1621
}),
1722
middlewareStack: { use: mockUsePlugin },
1823
})),
@@ -25,6 +30,20 @@ vi.mock("@aws-sdk/nested-clients/sts", () => ({
2530
}),
2631
}));
2732

33+
let iniProfileData: ParsedIniData = null as any;
34+
vi.mock(import("@smithy/node-config-provider"), async (importOriginal) => {
35+
return {
36+
...(await importOriginal()),
37+
loadConfig: ((
38+
{ environmentVariableSelector, configFileSelector, default: defaultValue }: LoadedConfigSelectors<any>,
39+
{ profile = process.env.AWS_PROFILE ?? "default" }: { profile?: string }
40+
) => {
41+
return () =>
42+
environmentVariableSelector(process.env) ?? configFileSelector(iniProfileData[profile] ?? {}) ?? defaultValue();
43+
}) as any,
44+
};
45+
});
46+
2847
describe("fromTemporaryCredentials", () => {
2948
const RoleArn = "ROLE_ARN";
3049
const RoleSessionName = "ROLE_SESSION_NAME";
@@ -33,16 +52,32 @@ describe("fromTemporaryCredentials", () => {
3352
secretAccessKey: "SECRET_ACCESS_KEY",
3453
};
3554
const region = "US_BAR_1";
55+
let processSnapshot: Record<string, any>;
3656

3757
beforeEach(() => {
3858
vi.clearAllMocks();
39-
mockSend.mockResolvedValueOnce({
59+
mockSend.mockResolvedValue({
4060
Credentials: {
4161
AccessKeyId: "ACCESS_KEY_ID",
4262
SecretAccessKey: "SECRET_ACCESS_KEY",
4363
SessionToken: "SESSION_TOKEN",
4464
},
4565
});
66+
67+
processSnapshot = {
68+
...process.env,
69+
};
70+
process.env = {};
71+
72+
iniProfileData = {
73+
default: {
74+
region: "us-west-2",
75+
},
76+
};
77+
});
78+
79+
afterEach(() => {
80+
process.env = processSnapshot;
4681
});
4782

4883
it("should call STS::AssumeRole API with master credentials", async () => {
@@ -91,7 +126,7 @@ describe("fromTemporaryCredentials", () => {
91126
credentials: masterCredentials,
92127
logger: void 0,
93128
profile: void 0,
94-
region: "us-east-1",
129+
region: "us-west-2", // profile default
95130
requestHandler: void 0,
96131
});
97132
expect(mockUsePlugin).toHaveBeenCalledTimes(1);
@@ -101,6 +136,7 @@ describe("fromTemporaryCredentials", () => {
101136
it("should create a role session name if none provided", async () => {
102137
const provider = fromTemporaryCredentialsNode({
103138
params: { RoleArn },
139+
masterCredentials,
104140
});
105141
await provider();
106142
expect(AssumeRoleCommand as unknown as any).toHaveBeenCalledWith({
@@ -299,6 +335,7 @@ describe("fromTemporaryCredentials", () => {
299335
const provider = fromTemporaryCredentialsNode({
300336
params: { RoleArn, SerialNumber, RoleSessionName },
301337
mfaCodeProvider,
338+
masterCredentials,
302339
});
303340
await provider();
304341
expect(mfaCodeProvider).toHaveBeenCalledWith(SerialNumber);
@@ -326,4 +363,121 @@ describe("fromTemporaryCredentials", () => {
326363
expect(e.tryNextLink).toBe(false);
327364
}
328365
});
366+
367+
describe("env configuration", () => {
368+
beforeEach(() => {
369+
iniProfileData = {
370+
default: {
371+
region: "us-west-2",
372+
},
373+
abc: {
374+
region: "eu-central-1",
375+
},
376+
xyz: {
377+
region: "us-west-1",
378+
},
379+
regionless: {},
380+
};
381+
});
382+
383+
it("should allow region configuration from config file", async () => {
384+
const provider = fromTemporaryCredentialsNode({
385+
params: {
386+
RoleArn,
387+
RoleSessionName,
388+
},
389+
masterCredentials,
390+
});
391+
await provider();
392+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toMatchObject({
393+
region: "us-west-2",
394+
});
395+
});
396+
397+
it("should allow region configuration from config file non-default profile", async () => {
398+
// SDK does not use AWS_DEFAULT_PROFILE.
399+
process.env.AWS_PROFILE = "xyz";
400+
const provider = fromTemporaryCredentialsNode({
401+
params: {
402+
RoleArn,
403+
RoleSessionName,
404+
},
405+
masterCredentials,
406+
});
407+
await provider();
408+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toMatchObject({
409+
region: "us-west-1",
410+
});
411+
});
412+
413+
it("should allow region configuration from env", async () => {
414+
// SDK does not use AWS_DEFAULT_REGION.
415+
process.env.AWS_REGION = "ap-southeast-7";
416+
const provider = fromTemporaryCredentialsNode({
417+
params: {
418+
RoleArn,
419+
RoleSessionName,
420+
},
421+
masterCredentials,
422+
});
423+
await provider();
424+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toMatchObject({
425+
region: "ap-southeast-7",
426+
});
427+
});
428+
429+
it("should allow region configuration from env overriding region in profile", async () => {
430+
process.env.AWS_PROFILE = "xyz";
431+
process.env.AWS_REGION = "eu-west-1";
432+
const provider = fromTemporaryCredentialsNode({
433+
params: {
434+
RoleArn,
435+
RoleSessionName,
436+
},
437+
masterCredentials,
438+
});
439+
await provider();
440+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toMatchObject({
441+
region: "eu-west-1",
442+
});
443+
});
444+
445+
it("should allow region configuration from env overriding region in profile where profile in code overrides env profile", async () => {
446+
process.env.AWS_PROFILE = "xyz";
447+
const provider = fromTemporaryCredentialsNode({
448+
params: {
449+
RoleArn,
450+
RoleSessionName,
451+
},
452+
masterCredentials,
453+
clientConfig: {
454+
profile: "abc",
455+
},
456+
});
457+
await provider();
458+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toMatchObject({
459+
region: "eu-central-1",
460+
});
461+
});
462+
463+
it("should use us-east-1 if no region is configured anywhere", async () => {
464+
// no configured region for the provider
465+
// no caller client with region
466+
// no region env
467+
// no region in profile
468+
process.env.AWS_PROFILE = "regionless";
469+
process.env.AWS_REGION = undefined;
470+
const provider = fromTemporaryCredentialsNode({
471+
params: {
472+
RoleArn,
473+
RoleSessionName,
474+
},
475+
masterCredentials,
476+
});
477+
await provider();
478+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toMatchObject({
479+
region: "us-east-1",
480+
});
481+
});
482+
});
329483
});

packages/credential-providers/src/fromTemporaryCredentials.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import type { RuntimeConfigAwsCredentialIdentityProvider } from "@aws-sdk/types";
2+
import { NODE_REGION_CONFIG_FILE_OPTIONS } from "@smithy/config-resolver";
3+
import { loadConfig } from "@smithy/node-config-provider";
24

35
import { fromNodeProviderChain } from "./fromNodeProviderChain";
46
import type { FromTemporaryCredentialsOptions } from "./fromTemporaryCredentials.base";
@@ -54,5 +56,19 @@ export { FromTemporaryCredentialsOptions };
5456
export const fromTemporaryCredentials = (
5557
options: FromTemporaryCredentialsOptions
5658
): RuntimeConfigAwsCredentialIdentityProvider => {
57-
return fromTemporaryCredentialsBase(options, fromNodeProviderChain);
59+
return fromTemporaryCredentialsBase(
60+
options,
61+
fromNodeProviderChain,
62+
async ({ profile = process.env.AWS_PROFILE }: { profile?: string }) =>
63+
loadConfig(
64+
{
65+
environmentVariableSelector: (env) => env.AWS_REGION,
66+
configFileSelector: (profileData) => {
67+
return profileData.region;
68+
},
69+
default: () => undefined,
70+
},
71+
{ ...NODE_REGION_CONFIG_FILE_OPTIONS, profile }
72+
)()
73+
);
5874
};

yarn.lock

+2
Original file line numberDiff line numberDiff line change
@@ -22645,8 +22645,10 @@ __metadata:
2264522645
"@aws-sdk/credential-provider-web-identity": "npm:*"
2264622646
"@aws-sdk/nested-clients": "npm:*"
2264722647
"@aws-sdk/types": "npm:*"
22648+
"@smithy/config-resolver": "npm:^4.1.0"
2264822649
"@smithy/core": "npm:^3.2.0"
2264922650
"@smithy/credential-provider-imds": "npm:^4.0.2"
22651+
"@smithy/node-config-provider": "npm:^4.0.2"
2265022652
"@smithy/property-provider": "npm:^4.0.2"
2265122653
"@smithy/types": "npm:^4.2.0"
2265222654
"@tsconfig/recommended": "npm:1.0.1"

0 commit comments

Comments
 (0)