Skip to content

Commit 2a94a80

Browse files
committed
fix(credential-providers): allow env and configFile selection of region in fromTemporaryCredentials
1 parent 8bf0c03 commit 2a94a80

File tree

5 files changed

+190
-7
lines changed

5 files changed

+190
-7
lines changed

packages/credential-providers/package.json

Lines changed: 3 additions & 1 deletion
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

Lines changed: 15 additions & 3 deletions
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>
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

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
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";
@@ -25,6 +27,20 @@ vi.mock("@aws-sdk/nested-clients/sts", () => ({
2527
}),
2628
}));
2729

30+
let iniProfileData: ParsedIniData = null as any;
31+
vi.mock(import("@smithy/node-config-provider"), async (importOriginal) => {
32+
return {
33+
...(await importOriginal()),
34+
loadConfig: ((
35+
{ environmentVariableSelector, configFileSelector, default: defaultValue }: LoadedConfigSelectors<any>,
36+
{ profile = process.env.AWS_PROFILE ?? "default" }: { profile?: string }
37+
) => {
38+
return () =>
39+
environmentVariableSelector(process.env) ?? configFileSelector(iniProfileData[profile] ?? {}) ?? defaultValue();
40+
}) as any,
41+
};
42+
});
43+
2844
describe("fromTemporaryCredentials", () => {
2945
const RoleArn = "ROLE_ARN";
3046
const RoleSessionName = "ROLE_SESSION_NAME";
@@ -33,6 +49,7 @@ describe("fromTemporaryCredentials", () => {
3349
secretAccessKey: "SECRET_ACCESS_KEY",
3450
};
3551
const region = "US_BAR_1";
52+
let processSnapshot: Record<string, any>;
3653

3754
beforeEach(() => {
3855
vi.clearAllMocks();
@@ -43,6 +60,21 @@ describe("fromTemporaryCredentials", () => {
4360
SessionToken: "SESSION_TOKEN",
4461
},
4562
});
63+
64+
processSnapshot = {
65+
...process.env,
66+
};
67+
process.env = {};
68+
69+
iniProfileData = {
70+
default: {
71+
region: "us-west-2",
72+
},
73+
};
74+
});
75+
76+
afterEach(() => {
77+
process.env = processSnapshot;
4678
});
4779

4880
it("should call STS::AssumeRole API with master credentials", async () => {
@@ -91,7 +123,7 @@ describe("fromTemporaryCredentials", () => {
91123
credentials: masterCredentials,
92124
logger: void 0,
93125
profile: void 0,
94-
region: "us-east-1",
126+
region: "us-west-2", // profile default
95127
requestHandler: void 0,
96128
});
97129
expect(mockUsePlugin).toHaveBeenCalledTimes(1);
@@ -326,4 +358,121 @@ describe("fromTemporaryCredentials", () => {
326358
expect(e.tryNextLink).toBe(false);
327359
}
328360
});
361+
362+
describe("env configuration", () => {
363+
beforeEach(() => {
364+
iniProfileData = {
365+
default: {
366+
region: "us-west-2",
367+
},
368+
abc: {
369+
region: "eu-central-1",
370+
},
371+
xyz: {
372+
region: "us-west-1",
373+
},
374+
regionless: {},
375+
};
376+
});
377+
378+
it("should allow region configuration from config file", async () => {
379+
const provider = fromTemporaryCredentialsNode({
380+
params: {
381+
RoleArn,
382+
RoleSessionName,
383+
},
384+
masterCredentials,
385+
});
386+
await provider();
387+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toMatchObject({
388+
region: "us-west-2",
389+
});
390+
});
391+
392+
it("should allow region configuration from config file non-default profile", async () => {
393+
// SDK does not use AWS_DEFAULT_PROFILE.
394+
process.env.AWS_PROFILE = "xyz";
395+
const provider = fromTemporaryCredentialsNode({
396+
params: {
397+
RoleArn,
398+
RoleSessionName,
399+
},
400+
masterCredentials,
401+
});
402+
await provider();
403+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toMatchObject({
404+
region: "us-west-1",
405+
});
406+
});
407+
408+
it("should allow region configuration from env", async () => {
409+
// SDK does not use AWS_DEFAULT_REGION.
410+
process.env.AWS_REGION = "ap-southeast-7";
411+
const provider = fromTemporaryCredentialsNode({
412+
params: {
413+
RoleArn,
414+
RoleSessionName,
415+
},
416+
masterCredentials,
417+
});
418+
await provider();
419+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toMatchObject({
420+
region: "ap-southeast-7",
421+
});
422+
});
423+
424+
it("should allow region configuration from env overriding region in profile", async () => {
425+
process.env.AWS_PROFILE = "xyz";
426+
process.env.AWS_REGION = "eu-west-1";
427+
const provider = fromTemporaryCredentialsNode({
428+
params: {
429+
RoleArn,
430+
RoleSessionName,
431+
},
432+
masterCredentials,
433+
});
434+
await provider();
435+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toMatchObject({
436+
region: "eu-west-1",
437+
});
438+
});
439+
440+
it("should allow region configuration from env overriding region in profile where profile in code overrides env profile", async () => {
441+
process.env.AWS_PROFILE = "xyz";
442+
const provider = fromTemporaryCredentialsNode({
443+
params: {
444+
RoleArn,
445+
RoleSessionName,
446+
},
447+
masterCredentials,
448+
clientConfig: {
449+
profile: "abc",
450+
},
451+
});
452+
await provider();
453+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toMatchObject({
454+
region: "eu-central-1",
455+
});
456+
});
457+
458+
it("should use us-east-1 if no region is configured anywhere", async () => {
459+
// no configured region for the provider
460+
// no caller client with region
461+
// no region env
462+
// no region in profile
463+
process.env.AWS_PROFILE = "regionless";
464+
process.env.AWS_REGION = undefined;
465+
const provider = fromTemporaryCredentialsNode({
466+
params: {
467+
RoleArn,
468+
RoleSessionName,
469+
},
470+
masterCredentials,
471+
});
472+
await provider();
473+
expect(vi.mocked(STSClient as any).mock.calls[0][0]).toMatchObject({
474+
region: "us-east-1",
475+
});
476+
});
477+
});
329478
});

packages/credential-providers/src/fromTemporaryCredentials.ts

Lines changed: 19 additions & 1 deletion
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,21 @@ 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: () => {
70+
return "us-east-1";
71+
},
72+
},
73+
{ ...NODE_REGION_CONFIG_FILE_OPTIONS, profile }
74+
)()
75+
);
5876
};

yarn.lock

Lines changed: 2 additions & 0 deletions
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)