Skip to content

Commit 6b99b0b

Browse files
authoredJan 30, 2024
fix(credential-provider-node): add fromHttp credential provider to default chain (#5739)
* fix(credential-provider-node): add fromHttp credential provider to default chain * chore(credential-provider-ini): pass through logger * fix(credential-provider-sso): prefer sso region for inner client if configured
1 parent 5cb98d6 commit 6b99b0b

File tree

11 files changed

+69
-28
lines changed

11 files changed

+69
-28
lines changed
 

‎packages/credential-provider-http/src/fromHttp/fromHttp.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ export const fromHttp = (options: FromHttpOptions): AwsCredentialIdentityProvide
4747
} else if (relative) {
4848
host = `${DEFAULT_LINK_LOCAL_HOST}${relative}`;
4949
} else {
50-
throw new CredentialsProviderError("No HTTP credential provider host provided.");
50+
throw new CredentialsProviderError(
51+
`No HTTP credential provider host provided.
52+
Set AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.`
53+
);
5154
}
5255

5356
// throws if invalid format.
@@ -56,7 +59,10 @@ export const fromHttp = (options: FromHttpOptions): AwsCredentialIdentityProvide
5659
// throws if not to spec for provider.
5760
checkUrl(url);
5861

59-
const requestHandler = new NodeHttpHandler();
62+
const requestHandler = new NodeHttpHandler({
63+
requestTimeout: options.timeout ?? 1000,
64+
connectionTimeout: options.timeout ?? 1000,
65+
});
6066

6167
return retryWrapper(
6268
async (): Promise<AwsCredentialIdentity> => {
@@ -69,8 +75,12 @@ export const fromHttp = (options: FromHttpOptions): AwsCredentialIdentityProvide
6975
// to allow for updates to the file contents.
7076
request.headers.Authorization = (await fs.readFile(tokenFile)).toString();
7177
}
72-
const result = await requestHandler.handle(request);
73-
return getCredentials(result.response);
78+
try {
79+
const result = await requestHandler.handle(request);
80+
return getCredentials(result.response);
81+
} catch (e: unknown) {
82+
throw new CredentialsProviderError(String(e));
83+
}
7484
},
7585
options.maxRetries ?? 3,
7686
options.timeout ?? 1000

‎packages/credential-provider-ini/src/resolveProfileData.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,6 @@ describe(resolveProfileData.name, () => {
117117
(resolveSsoCredentials as jest.Mock).mockImplementation(() => Promise.resolve(mockCreds));
118118
const receivedCreds = await resolveProfileData(mockProfileName, mockProfiles, mockOptions);
119119
expect(receivedCreds).toStrictEqual(mockCreds);
120-
expect(resolveSsoCredentials).toHaveBeenCalledWith(mockProfileName);
120+
expect(resolveSsoCredentials).toHaveBeenCalledWith(mockProfileName, mockOptions);
121121
});
122122
});

‎packages/credential-provider-ini/src/resolveProfileData.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const resolveProfileData = async (
5151
}
5252

5353
if (isSsoProfile(data)) {
54-
return await resolveSsoCredentials(profileName);
54+
return await resolveSsoCredentials(profileName, options);
5555
}
5656

5757
// If the profile cannot be parsed or contains neither static credentials

‎packages/credential-provider-ini/src/resolveSsoCredentials.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import type { SsoProfile } from "@aws-sdk/credential-provider-sso";
2+
import type { CredentialProviderOptions } from "@aws-sdk/types";
23
import type { Profile } from "@smithy/types";
34

45
/**
56
* @internal
67
*/
7-
export const resolveSsoCredentials = async (profile: string) => {
8+
export const resolveSsoCredentials = async (profile: string, options: CredentialProviderOptions = {}) => {
89
const { fromSSO } = await import("@aws-sdk/credential-provider-sso");
910
return fromSSO({
1011
profile,
12+
logger: options.logger,
1113
})();
1214
};
1315

‎packages/credential-provider-ini/src/resolveWebIdentityCredentials.ts

+1
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,6 @@ export const resolveWebIdentityCredentials = async (
3434
roleArn: profile.role_arn,
3535
roleSessionName: profile.role_session_name,
3636
roleAssumerWithWebIdentity: options.roleAssumerWithWebIdentity,
37+
logger: options.logger,
3738
})()
3839
);

‎packages/credential-provider-node/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"license": "Apache-2.0",
2929
"dependencies": {
3030
"@aws-sdk/credential-provider-env": "*",
31+
"@aws-sdk/credential-provider-http": "*",
3132
"@aws-sdk/credential-provider-ini": "*",
3233
"@aws-sdk/credential-provider-process": "*",
3334
"@aws-sdk/credential-provider-sso": "*",

‎packages/credential-provider-node/src/remoteProvider.spec.ts

+27-10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ describe(remoteProvider.name, () => {
2121
accessKeyId: "mockInstanceMetadataAccessKeyId",
2222
secretAccessKey: "mockInstanceMetadataSecretAccessKey",
2323
};
24+
const mockFromHttp = jest.fn().mockReturnValue(async () => mockCredsFromContainer);
25+
26+
const sampleFullUri = "http://localhost";
27+
const sampleRelativeUri = "/";
2428

2529
beforeEach(() => {
2630
process.env = {
@@ -29,6 +33,10 @@ describe(remoteProvider.name, () => {
2933
[ENV_CMDS_FULL_URI]: undefined,
3034
[ENV_IMDS_DISABLED]: undefined,
3135
};
36+
37+
jest.mock("@aws-sdk/credential-provider-http", () => ({
38+
fromHttp: mockFromHttp,
39+
}));
3240
(fromContainerMetadata as jest.Mock).mockReturnValue(async () => mockCredsFromContainer);
3341
(fromInstanceMetadata as jest.Mock).mockReturnValue(async () => mockSourceCredsFromInstanceMetadata);
3442
});
@@ -38,16 +46,23 @@ describe(remoteProvider.name, () => {
3846
jest.clearAllMocks();
3947
});
4048

41-
it.each([ENV_CMDS_RELATIVE_URI, ENV_CMDS_FULL_URI])(
42-
"returns fromContainerMetadata if env['%s'] is set",
43-
async (key) => {
44-
process.env[key] = "defined";
45-
const receivedCreds = await (await remoteProvider(mockInit))();
46-
expect(receivedCreds).toStrictEqual(mockCredsFromContainer);
47-
expect(fromContainerMetadata).toHaveBeenCalledWith(mockInit);
48-
expect(fromInstanceMetadata).not.toHaveBeenCalled();
49-
}
50-
);
49+
it(`returns fromContainerMetadata if env[${ENV_CMDS_RELATIVE_URI}] is set`, async () => {
50+
process.env[ENV_CMDS_RELATIVE_URI] = sampleRelativeUri;
51+
const receivedCreds = await (await remoteProvider(mockInit))();
52+
expect(receivedCreds).toStrictEqual(mockCredsFromContainer);
53+
expect(mockFromHttp).toHaveBeenCalledWith(mockInit);
54+
expect(fromContainerMetadata).toHaveBeenCalledWith(mockInit);
55+
expect(fromInstanceMetadata).not.toHaveBeenCalled();
56+
});
57+
58+
it(`returns fromContainerMetadata if env[${ENV_CMDS_FULL_URI}] is set`, async () => {
59+
process.env[ENV_CMDS_FULL_URI] = sampleFullUri;
60+
const receivedCreds = await (await remoteProvider(mockInit))();
61+
expect(receivedCreds).toStrictEqual(mockCredsFromContainer);
62+
expect(mockFromHttp).toHaveBeenCalledWith(mockInit);
63+
expect(fromContainerMetadata).toHaveBeenCalledWith(mockInit);
64+
expect(fromInstanceMetadata).not.toHaveBeenCalled();
65+
});
5166

5267
it(`throws if env['${ENV_IMDS_DISABLED}'] is set`, async () => {
5368
process.env[ENV_IMDS_DISABLED] = "1";
@@ -60,6 +75,7 @@ describe(remoteProvider.name, () => {
6075
} catch (error) {
6176
expect(error).toStrictEqual(expectedError);
6277
}
78+
expect(mockFromHttp).not.toHaveBeenCalled();
6379
expect(fromContainerMetadata).not.toHaveBeenCalled();
6480
expect(fromInstanceMetadata).not.toHaveBeenCalled();
6581
});
@@ -69,5 +85,6 @@ describe(remoteProvider.name, () => {
6985
expect(receivedCreds).toStrictEqual(mockSourceCredsFromInstanceMetadata);
7086
expect(fromInstanceMetadata).toHaveBeenCalledWith(mockInit);
7187
expect(fromContainerMetadata).not.toHaveBeenCalled();
88+
expect(mockFromHttp).not.toHaveBeenCalled();
7289
});
7390
});

‎packages/credential-provider-node/src/remoteProvider.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import type { RemoteProviderInit } from "@smithy/credential-provider-imds";
2-
import { CredentialsProviderError } from "@smithy/property-provider";
2+
import { chain, CredentialsProviderError } from "@smithy/property-provider";
33
import type { AwsCredentialIdentityProvider } from "@smithy/types";
44

5+
/**
6+
* @internal
7+
*/
58
export const ENV_IMDS_DISABLED = "AWS_EC2_METADATA_DISABLED";
69

710
/**
@@ -13,8 +16,9 @@ export const remoteProvider = async (init: RemoteProviderInit): Promise<AwsCrede
1316
);
1417

1518
if (process.env[ENV_CMDS_RELATIVE_URI] || process.env[ENV_CMDS_FULL_URI]) {
16-
init.logger?.debug("@aws-sdk/credential-provider-node", "remoteProvider::fromContainerMetadata");
17-
return fromContainerMetadata(init);
19+
init.logger?.debug("@aws-sdk/credential-provider-node", "remoteProvider::fromHttp/fromContainerMetadata");
20+
const { fromHttp } = await import("@aws-sdk/credential-provider-http");
21+
return chain(fromHttp(init), fromContainerMetadata(init));
1822
}
1923

2024
if (process.env[ENV_IMDS_DISABLED]) {

‎packages/credential-provider-sso/src/fromSSO.spec.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ describe(fromSSO.name, () => {
101101
expect(validateSsoProfile).toHaveBeenCalledWith(mockSsoProfile);
102102
});
103103

104-
it("calls resolveSSOCredentials with values from validated Sso profile", async () => {
104+
it("calls resolveSSOCredentials with values from validated SSO profile", async () => {
105105
const mockValidatedSsoProfile = {
106106
sso_start_url: "mock_sso_start_url",
107107
sso_account_id: "mock_sso_account_id",
@@ -119,7 +119,8 @@ describe(fromSSO.name, () => {
119119
ssoRoleName: mockValidatedSsoProfile.sso_role_name,
120120
profile: mockProfileName,
121121
ssoSession: undefined,
122-
ssoClient: expect.any(SSOClient),
122+
ssoClient: undefined,
123+
clientConfig: undefined,
123124
});
124125
});
125126
});

‎packages/credential-provider-sso/src/fromSSO.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,7 @@ export const fromSSO =
8383
async () => {
8484
init.logger?.debug("@aws-sdk/credential-provider-sso", "fromSSO");
8585
const { ssoStartUrl, ssoAccountId, ssoRegion, ssoRoleName, ssoSession } = init;
86-
let { ssoClient } = init;
87-
if (!ssoClient) {
88-
const { SSOClient } = await import("./loadSso");
89-
ssoClient = new SSOClient(init.clientConfig ?? {});
90-
}
86+
const { ssoClient } = init;
9187
const profileName = getProfileName(init);
9288

9389
if (!ssoStartUrl && !ssoAccountId && !ssoRegion && !ssoRoleName && !ssoSession) {
@@ -125,6 +121,7 @@ export const fromSSO =
125121
ssoRegion: sso_region,
126122
ssoRoleName: sso_role_name,
127123
ssoClient: ssoClient,
124+
clientConfig: init.clientConfig,
128125
profile: profileName,
129126
});
130127
} else if (!ssoStartUrl || !ssoAccountId || !ssoRegion || !ssoRoleName) {
@@ -140,6 +137,7 @@ export const fromSSO =
140137
ssoRegion,
141138
ssoRoleName,
142139
ssoClient,
140+
clientConfig: init.clientConfig,
143141
profile: profileName,
144142
});
145143
}

‎packages/credential-provider-sso/src/resolveSSOCredentials.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const resolveSSOCredentials = async ({
1818
ssoRegion,
1919
ssoRoleName,
2020
ssoClient,
21+
clientConfig,
2122
profile,
2223
}: FromSSOInit & SsoCredentialsParameters): Promise<AwsCredentialIdentity> => {
2324
let token: SSOToken;
@@ -55,7 +56,13 @@ export const resolveSSOCredentials = async ({
5556

5657
const { SSOClient, GetRoleCredentialsCommand } = await import("./loadSso");
5758

58-
const sso = ssoClient || new SSOClient({ region: ssoRegion });
59+
const sso =
60+
ssoClient ||
61+
new SSOClient(
62+
Object.assign({}, clientConfig ?? {}, {
63+
region: clientConfig?.region ?? ssoRegion,
64+
})
65+
);
5966
let ssoResp: GetRoleCredentialsCommandOutput;
6067
try {
6168
ssoResp = await sso.send(

0 commit comments

Comments
 (0)
Please sign in to comment.