Skip to content

Commit d9cb1ac

Browse files
committed
test(credential-provider-node): additional integ tests for cognito
1 parent 59b84b9 commit d9cb1ac

File tree

9 files changed

+418
-60
lines changed

9 files changed

+418
-60
lines changed

packages/credential-provider-cognito-identity/src/fromCognitoIdentityPool.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ export function fromCognitoIdentityPool({
6868
identityId,
6969
});
7070

71-
return provider();
71+
return provider(awsIdentityProperties);
7272
};
7373

74-
return () =>
75-
provider().catch(async (err) => {
74+
return (awsIdentityProperties?: AwsIdentityProperties) =>
75+
provider(awsIdentityProperties).catch(async (err) => {
7676
if (cacheKey) {
7777
Promise.resolve(cache.removeItem(cacheKey)).catch(() => {});
7878
}

packages/credential-provider-ini/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
"build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4",
1414
"clean": "rimraf ./dist-* && rimraf *.tsbuildinfo",
1515
"test": "yarn g:vitest run",
16-
"test:watch": "yarn g:vitest watch"
16+
"test:watch": "yarn g:vitest watch",
17+
"test:integration": "yarn g:vitest run -c vitest.config.integ.ts",
18+
"test:integration:watch": "yarn g:vitest watch -c vitest.config.integ.ts"
1719
},
1820
"keywords": [
1921
"aws",
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import { STS } from "@aws-sdk/client-sts";
2+
import { HttpRequest, HttpResponse } from "@smithy/protocol-http";
3+
import { SourceProfileInit } from "@smithy/shared-ini-file-loader";
4+
import type { NodeHttpHandlerOptions, ParsedIniData } from "@smithy/types";
5+
import { PassThrough } from "node:stream";
6+
import { beforeEach, describe, expect, test as it, vi } from "vitest";
7+
8+
import { fromIni } from "./fromIni";
9+
10+
let iniProfileData: ParsedIniData = null as any;
11+
vi.mock("@smithy/shared-ini-file-loader", async () => {
12+
const actual: any = await vi.importActual("@smithy/shared-ini-file-loader");
13+
const pkg = {
14+
...actual,
15+
async loadSsoSessionData() {
16+
return Object.entries(iniProfileData)
17+
.filter(([key]) => key.startsWith("sso-session."))
18+
.reduce(
19+
(acc, [key, value]) => ({
20+
...acc,
21+
[key.split("sso-session.")[1]]: value,
22+
}),
23+
{}
24+
);
25+
},
26+
async parseKnownFiles(init: SourceProfileInit): Promise<ParsedIniData> {
27+
return iniProfileData;
28+
},
29+
async getSSOTokenFromFile() {
30+
return {
31+
accessToken: "mock_sso_token",
32+
expiresAt: "3000-01-01T00:00:00.000Z",
33+
};
34+
},
35+
};
36+
return {
37+
...pkg,
38+
default: pkg,
39+
};
40+
});
41+
42+
class MockNodeHttpHandler {
43+
static create(instanceOrOptions?: any) {
44+
if (typeof instanceOrOptions?.handle === "function") {
45+
return instanceOrOptions;
46+
}
47+
return new MockNodeHttpHandler();
48+
}
49+
async handle(request: HttpRequest) {
50+
const body = new PassThrough({});
51+
52+
const region = (request.hostname.match(/sts\.(.*?)\./) || [, "unknown"])[1];
53+
54+
if (request.headers.Authorization === "container-authorization") {
55+
body.write(
56+
JSON.stringify({
57+
AccessKeyId: "CONTAINER_ACCESS_KEY",
58+
SecretAccessKey: "CONTAINER_SECRET_ACCESS_KEY",
59+
Token: "CONTAINER_TOKEN",
60+
Expiration: "3000-01-01T00:00:00.000Z",
61+
})
62+
);
63+
} else if (request.path?.includes("/federation/credentials")) {
64+
body.write(
65+
JSON.stringify({
66+
roleCredentials: {
67+
accessKeyId: "SSO_ACCESS_KEY_ID",
68+
secretAccessKey: "SSO_SECRET_ACCESS_KEY",
69+
sessionToken: "SSO_SESSION_TOKEN",
70+
expiration: "3000-01-01T00:00:00.000Z",
71+
},
72+
})
73+
);
74+
} else if (request.body?.includes("Action=AssumeRoleWithWebIdentity")) {
75+
body.write(`
76+
<AssumeRoleWithWebIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
77+
<AssumeRoleWithWebIdentityResult>
78+
<Credentials>
79+
<AccessKeyId>STS_ARWI_ACCESS_KEY_ID</AccessKeyId>
80+
<SecretAccessKey>STS_ARWI_SECRET_ACCESS_KEY</SecretAccessKey>
81+
<SessionToken>STS_ARWI_SESSION_TOKEN_${region}</SessionToken>
82+
<Expiration>3000-01-01T00:00:00.000Z</Expiration>
83+
</Credentials>
84+
</AssumeRoleWithWebIdentityResult>
85+
<ResponseMetadata>
86+
<RequestId>01234567-89ab-cdef-0123-456789abcdef</RequestId>
87+
</ResponseMetadata>
88+
</AssumeRoleWithWebIdentityResponse>`);
89+
} else if (request.body?.includes("Action=AssumeRole")) {
90+
body.write(`
91+
<AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
92+
<AssumeRoleResult>
93+
<Credentials>
94+
<AccessKeyId>STS_AR_ACCESS_KEY_ID</AccessKeyId>
95+
<SecretAccessKey>STS_AR_SECRET_ACCESS_KEY</SecretAccessKey>
96+
<SessionToken>STS_AR_SESSION_TOKEN_${region}</SessionToken>
97+
<Expiration>3000-01-01T00:00:00.000Z</Expiration>
98+
</Credentials>
99+
</AssumeRoleResult>
100+
<ResponseMetadata>
101+
<RequestId>01234567-89ab-cdef-0123-456789abcdef</RequestId>
102+
</ResponseMetadata>
103+
</AssumeRoleResponse>`);
104+
} else if (request.body.includes("Action=GetCallerIdentity")) {
105+
body.write(`
106+
<GetCallerIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
107+
<GetCallerIdentityResult>
108+
<Arn>arn:aws:iam::123456789012:user/Alice</Arn>
109+
<UserId>AIDACKCEVSQ6C2EXAMPLE</UserId>
110+
<Account>123456789012</Account>
111+
</GetCallerIdentityResult>
112+
<ResponseMetadata>
113+
<RequestId>01234567-89ab-cdef-0123-456789abcdef</RequestId>
114+
</ResponseMetadata>
115+
</GetCallerIdentityResponse>`);
116+
} else {
117+
throw new Error("request not supported.");
118+
}
119+
body.end();
120+
return {
121+
response: new HttpResponse({
122+
statusCode: 200,
123+
body,
124+
headers: {},
125+
}),
126+
};
127+
}
128+
updateHttpClientConfig(key: keyof NodeHttpHandlerOptions, value: NodeHttpHandlerOptions[typeof key]): void {}
129+
httpHandlerConfigs(): NodeHttpHandlerOptions {
130+
return null as any;
131+
}
132+
}
133+
134+
describe("fromIni region search order", () => {
135+
beforeEach(() => {
136+
iniProfileData = {
137+
default: {
138+
region: "us-west-2",
139+
output: "json",
140+
},
141+
};
142+
iniProfileData.assume = {
143+
region: "us-stsar-1",
144+
aws_access_key_id: "ASSUME_STATIC_ACCESS_KEY",
145+
aws_secret_access_key: "ASSUME_STATIC_SECRET_KEY",
146+
};
147+
Object.assign(iniProfileData.default, {
148+
region: "us-stsar-1",
149+
role_arn: "ROLE_ARN",
150+
role_session_name: "ROLE_SESSION_NAME",
151+
external_id: "EXTERNAL_ID",
152+
source_profile: "assume",
153+
});
154+
});
155+
156+
it("should use 1st priority for the clientConfig given to the provider factory", async () => {
157+
const sts = new STS({
158+
requestHandler: new MockNodeHttpHandler(),
159+
region: "ap-northeast-2",
160+
credentials: fromIni({
161+
clientConfig: {
162+
requestHandler: new MockNodeHttpHandler(),
163+
region: "ap-northeast-1",
164+
},
165+
}),
166+
});
167+
168+
await sts.getCallerIdentity({});
169+
const credentials = await sts.config.credentials();
170+
expect(credentials).toContain({
171+
accessKeyId: "STS_AR_ACCESS_KEY_ID",
172+
secretAccessKey: "STS_AR_SECRET_ACCESS_KEY",
173+
sessionToken: "STS_AR_SESSION_TOKEN_ap-northeast-1",
174+
});
175+
});
176+
177+
it("should use 2nd priority for the context client", async () => {
178+
const sts = new STS({
179+
requestHandler: new MockNodeHttpHandler(),
180+
region: "ap-northeast-2",
181+
credentials: fromIni({
182+
clientConfig: {
183+
requestHandler: new MockNodeHttpHandler(),
184+
},
185+
}),
186+
});
187+
188+
await sts.getCallerIdentity({});
189+
const credentials = await sts.config.credentials();
190+
expect(credentials).toContain({
191+
accessKeyId: "STS_AR_ACCESS_KEY_ID",
192+
secretAccessKey: "STS_AR_SECRET_ACCESS_KEY",
193+
sessionToken: "STS_AR_SESSION_TOKEN_ap-northeast-2",
194+
});
195+
});
196+
197+
it("should use 3rd priority for the profile region if not used in the context of a client with a region", async () => {
198+
const credentialsData = await fromIni({
199+
clientConfig: {
200+
requestHandler: new MockNodeHttpHandler(),
201+
},
202+
})();
203+
204+
const sts = new STS({
205+
requestHandler: new MockNodeHttpHandler(),
206+
region: "ap-northeast-2",
207+
credentials: credentialsData,
208+
});
209+
210+
await sts.getCallerIdentity({});
211+
const credentials = await sts.config.credentials();
212+
expect(credentials).toContain({
213+
accessKeyId: "STS_AR_ACCESS_KEY_ID",
214+
secretAccessKey: "STS_AR_SECRET_ACCESS_KEY",
215+
sessionToken: "STS_AR_SESSION_TOKEN_us-stsar-1",
216+
});
217+
});
218+
219+
it("should use 4th priority for the default partition's default region", async () => {
220+
delete iniProfileData.default.region;
221+
222+
const credentialsData = await fromIni({
223+
clientConfig: {
224+
requestHandler: new MockNodeHttpHandler(),
225+
},
226+
})();
227+
228+
const sts = new STS({
229+
requestHandler: new MockNodeHttpHandler(),
230+
region: "ap-northeast-2",
231+
credentials: credentialsData,
232+
});
233+
234+
await sts.getCallerIdentity({});
235+
const credentials = await sts.config.credentials();
236+
expect(credentials).toContain({
237+
accessKeyId: "STS_AR_ACCESS_KEY_ID",
238+
secretAccessKey: "STS_AR_SECRET_ACCESS_KEY",
239+
sessionToken: "STS_AR_SESSION_TOKEN_us-east-1",
240+
});
241+
});
242+
});

packages/credential-provider-ini/src/fromIni.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,13 @@ export const fromIni =
6060
async (props = {}) => {
6161
const init: FromIniInit = {
6262
..._init,
63-
parentClientConfig: {
64-
region: props.contextClientConfig?.region,
65-
..._init.parentClientConfig,
66-
},
6763
};
64+
if (props.contextClientConfig?.region) {
65+
init.parentClientConfig = {
66+
region: props.contextClientConfig.region,
67+
..._init.parentClientConfig,
68+
};
69+
}
6870
init.logger?.debug("@aws-sdk/credential-provider-ini - fromIni");
6971
const profiles = await parseKnownFiles(init);
7072
return resolveProfileData(getProfileName(init), profiles, init);

packages/credential-provider-ini/src/resolveAssumeRoleCredentials.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ export const resolveAssumeRoleCredentials = async (
107107
visitedProfiles: Record<string, true> = {}
108108
) => {
109109
options.logger?.debug("@aws-sdk/credential-provider-ini - resolveAssumeRoleCredentials (STS)");
110-
const data = profiles[profileName];
110+
const profileData = profiles[profileName];
111+
const { source_profile, region } = profileData;
111112

112113
if (!options.roleAssumer) {
113114
// @ts-ignore Cannot find module '@aws-sdk/client-sts'
@@ -116,13 +117,18 @@ export const resolveAssumeRoleCredentials = async (
116117
{
117118
...options.clientConfig,
118119
credentialProviderLogger: options.logger,
119-
parentClientConfig: options?.parentClientConfig,
120+
parentClientConfig: {
121+
...options?.parentClientConfig,
122+
// The profile region is the last fallback, and only applies
123+
// if the clientConfig.region is not defined by the user
124+
// and no contextual outer client configuration region can be found.
125+
region: options?.parentClientConfig?.region ?? region,
126+
},
120127
},
121128
options.clientPlugins
122129
);
123130
}
124131

125-
const { source_profile } = data;
126132
if (source_profile && source_profile in visitedProfiles) {
127133
throw new CredentialsProviderError(
128134
`Detected a cycle attempting to resolve credentials for profile` +
@@ -149,9 +155,9 @@ export const resolveAssumeRoleCredentials = async (
149155
},
150156
isCredentialSourceWithoutRoleArn(profiles[source_profile!] ?? {})
151157
)
152-
: (await resolveCredentialSource(data.credential_source!, profileName, options.logger)(options))();
158+
: (await resolveCredentialSource(profileData.credential_source!, profileName, options.logger)(options))();
153159

154-
if (isCredentialSourceWithoutRoleArn(data)) {
160+
if (isCredentialSourceWithoutRoleArn(profileData)) {
155161
/**
156162
* This control-flow branch is accessed when in a chained source_profile
157163
* scenario, and the last step of the chain is a credential_source
@@ -163,13 +169,13 @@ export const resolveAssumeRoleCredentials = async (
163169
return sourceCredsProvider.then((creds) => setCredentialFeature(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
164170
} else {
165171
const params: AssumeRoleParams = {
166-
RoleArn: data.role_arn!,
167-
RoleSessionName: data.role_session_name || `aws-sdk-js-${Date.now()}`,
168-
ExternalId: data.external_id,
169-
DurationSeconds: parseInt(data.duration_seconds || "3600", 10),
172+
RoleArn: profileData.role_arn!,
173+
RoleSessionName: profileData.role_session_name || `aws-sdk-js-${Date.now()}`,
174+
ExternalId: profileData.external_id,
175+
DurationSeconds: parseInt(profileData.duration_seconds || "3600", 10),
170176
};
171177

172-
const { mfa_serial } = data;
178+
const { mfa_serial } = profileData;
173179
if (mfa_serial) {
174180
if (!options.mfaCodeProvider) {
175181
throw new CredentialsProviderError(
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { defineConfig } from "vitest/config";
2+
3+
export default defineConfig({
4+
test: {
5+
include: ["**/*.integ.spec.ts"],
6+
environment: "node",
7+
},
8+
});

0 commit comments

Comments
 (0)