Skip to content

Commit db2651c

Browse files
authored
feat(credential-provider-imds): update httpGet to accept options.method (#1353)
1 parent 089585d commit db2651c

9 files changed

+191
-161
lines changed

packages/credential-provider-imds/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@types/jest": "^26.0.4",
3030
"@types/node": "^10.0.0",
3131
"jest": "^26.1.0",
32+
"nock": "^13.0.2",
3233
"typescript": "~3.8.3"
3334
},
3435
"types": "./dist/cjs/index.d.ts"

packages/credential-provider-imds/src/fromContainerMetadata.spec.ts

+29-25
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@ import {
44
ENV_CMDS_RELATIVE_URI,
55
fromContainerMetadata
66
} from "./fromContainerMetadata";
7-
import { httpGet } from "./remoteProvider/httpGet";
7+
import { httpRequest } from "./remoteProvider/httpRequest";
88
import {
99
fromImdsCredentials,
1010
ImdsCredentials
1111
} from "./remoteProvider/ImdsCredentials";
1212

13-
const mockHttpGet = <any>httpGet;
14-
jest.mock("./remoteProvider/httpGet", () => ({ httpGet: jest.fn() }));
13+
const mockHttpRequest = <any>httpRequest;
14+
jest.mock("./remoteProvider/httpRequest", () => ({ httpRequest: jest.fn() }));
1515

1616
const relativeUri = process.env[ENV_CMDS_RELATIVE_URI];
1717
const fullUri = process.env[ENV_CMDS_FULL_URI];
1818
const authToken = process.env[ENV_CMDS_AUTH_TOKEN];
1919

2020
beforeEach(() => {
21-
mockHttpGet.mockReset();
21+
mockHttpRequest.mockReset();
2222
delete process.env[ENV_CMDS_RELATIVE_URI];
2323
delete process.env[ENV_CMDS_FULL_URI];
2424
delete process.env[ENV_CMDS_AUTH_TOKEN];
@@ -53,12 +53,12 @@ describe("fromContainerMetadata", () => {
5353
const token = "Basic abcd";
5454
process.env[ENV_CMDS_FULL_URI] = "http://localhost:8080/path";
5555
process.env[ENV_CMDS_AUTH_TOKEN] = token;
56-
mockHttpGet.mockReturnValue(Promise.resolve(JSON.stringify(creds)));
56+
mockHttpRequest.mockReturnValue(Promise.resolve(JSON.stringify(creds)));
5757

5858
await fromContainerMetadata()();
5959

60-
expect(mockHttpGet.mock.calls.length).toBe(1);
61-
const [options = {}] = mockHttpGet.mock.calls[0];
60+
expect(mockHttpRequest.mock.calls.length).toBe(1);
61+
const [options = {}] = mockHttpRequest.mock.calls[0];
6262
expect(options.headers).toMatchObject({
6363
Authorization: token
6464
});
@@ -70,7 +70,7 @@ describe("fromContainerMetadata", () => {
7070
});
7171

7272
it("should resolve credentials by fetching them from the container metadata service", async () => {
73-
mockHttpGet.mockReturnValue(Promise.resolve(JSON.stringify(creds)));
73+
mockHttpRequest.mockReturnValue(Promise.resolve(JSON.stringify(creds)));
7474

7575
expect(await fromContainerMetadata()()).toEqual(
7676
fromImdsCredentials(creds)
@@ -80,38 +80,42 @@ describe("fromContainerMetadata", () => {
8080
it("should retry the fetching operation up to maxRetries times", async () => {
8181
const maxRetries = 5;
8282
for (let i = 0; i < maxRetries - 1; i++) {
83-
mockHttpGet.mockReturnValueOnce(Promise.reject("No!"));
83+
mockHttpRequest.mockReturnValueOnce(Promise.reject("No!"));
8484
}
85-
mockHttpGet.mockReturnValueOnce(Promise.resolve(JSON.stringify(creds)));
85+
mockHttpRequest.mockReturnValueOnce(
86+
Promise.resolve(JSON.stringify(creds))
87+
);
8688

8789
expect(await fromContainerMetadata({ maxRetries })()).toEqual(
8890
fromImdsCredentials(creds)
8991
);
90-
expect(mockHttpGet.mock.calls.length).toEqual(maxRetries);
92+
expect(mockHttpRequest.mock.calls.length).toEqual(maxRetries);
9193
});
9294

9395
it("should retry responses that receive invalid response values", async () => {
9496
for (let key of Object.keys(creds)) {
9597
const invalidCreds: any = { ...creds };
9698
delete invalidCreds[key];
97-
mockHttpGet.mockReturnValueOnce(
99+
mockHttpRequest.mockReturnValueOnce(
98100
Promise.resolve(JSON.stringify(invalidCreds))
99101
);
100102
}
101-
mockHttpGet.mockReturnValueOnce(Promise.resolve(JSON.stringify(creds)));
103+
mockHttpRequest.mockReturnValueOnce(
104+
Promise.resolve(JSON.stringify(creds))
105+
);
102106

103107
await fromContainerMetadata({ maxRetries: 100 })();
104-
expect(mockHttpGet.mock.calls.length).toEqual(
108+
expect(mockHttpRequest.mock.calls.length).toEqual(
105109
Object.keys(creds).length + 1
106110
);
107111
});
108112

109-
it("should pass relevant configuration to httpGet", async () => {
113+
it("should pass relevant configuration to httpRequest", async () => {
110114
const timeout = Math.ceil(Math.random() * 1000);
111-
mockHttpGet.mockReturnValue(Promise.resolve(JSON.stringify(creds)));
115+
mockHttpRequest.mockReturnValue(Promise.resolve(JSON.stringify(creds)));
112116
await fromContainerMetadata({ timeout })();
113-
expect(mockHttpGet.mock.calls.length).toEqual(1);
114-
expect(mockHttpGet.mock.calls[0][0]).toEqual({
117+
expect(mockHttpRequest.mock.calls.length).toEqual(1);
118+
expect(mockHttpRequest.mock.calls[0][0]).toEqual({
115119
hostname: "169.254.170.2",
116120
path: process.env[ENV_CMDS_RELATIVE_URI],
117121
timeout
@@ -120,20 +124,20 @@ describe("fromContainerMetadata", () => {
120124
});
121125

122126
describe(ENV_CMDS_FULL_URI, () => {
123-
it("should pass relevant configuration to httpGet", async () => {
127+
it("should pass relevant configuration to httpRequest", async () => {
124128
process.env[ENV_CMDS_FULL_URI] = "http://localhost:8080/path";
125129

126130
const timeout = Math.ceil(Math.random() * 1000);
127-
mockHttpGet.mockReturnValue(Promise.resolve(JSON.stringify(creds)));
131+
mockHttpRequest.mockReturnValue(Promise.resolve(JSON.stringify(creds)));
128132
await fromContainerMetadata({ timeout })();
129-
expect(mockHttpGet.mock.calls.length).toEqual(1);
133+
expect(mockHttpRequest.mock.calls.length).toEqual(1);
130134
const {
131135
protocol,
132136
hostname,
133137
path,
134138
port,
135139
timeout: actualTimeout
136-
} = mockHttpGet.mock.calls[0][0];
140+
} = mockHttpRequest.mock.calls[0][0];
137141
expect(protocol).toBe("http:");
138142
expect(hostname).toBe("localhost");
139143
expect(path).toBe("/path");
@@ -146,10 +150,10 @@ describe("fromContainerMetadata", () => {
146150
process.env[ENV_CMDS_FULL_URI] = "http://localhost:8080/path";
147151

148152
const timeout = Math.ceil(Math.random() * 1000);
149-
mockHttpGet.mockReturnValue(Promise.resolve(JSON.stringify(creds)));
153+
mockHttpRequest.mockReturnValue(Promise.resolve(JSON.stringify(creds)));
150154
await fromContainerMetadata({ timeout })();
151-
expect(mockHttpGet.mock.calls.length).toEqual(1);
152-
expect(mockHttpGet.mock.calls[0][0]).toEqual({
155+
expect(mockHttpRequest.mock.calls.length).toEqual(1);
156+
expect(mockHttpRequest.mock.calls[0][0]).toEqual({
153157
hostname: "169.254.170.2",
154158
path: "foo",
155159
timeout

packages/credential-provider-imds/src/fromContainerMetadata.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
RemoteProviderInit,
44
providerConfigFromInit
55
} from "./remoteProvider/RemoteProviderInit";
6-
import { httpGet } from "./remoteProvider/httpGet";
6+
import { httpRequest } from "./remoteProvider/httpRequest";
77
import {
88
fromImdsCredentials,
99
isImdsCredentials
@@ -53,7 +53,7 @@ function requestFromEcsImds(
5353
options.headers = headers;
5454
}
5555

56-
return httpGet({
56+
return httpRequest({
5757
...options,
5858
timeout
5959
}).then(buffer => buffer.toString());

packages/credential-provider-imds/src/fromInstanceMetadata.spec.ts

+23-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { fromInstanceMetadata } from "./fromInstanceMetadata";
2-
import { httpGet } from "./remoteProvider/httpGet";
2+
import { httpRequest } from "./remoteProvider/httpRequest";
33
import {
44
fromImdsCredentials,
55
isImdsCredentials
@@ -8,7 +8,7 @@ import { providerConfigFromInit } from "./remoteProvider/RemoteProviderInit";
88
import { retry } from "./remoteProvider/retry";
99
import { ProviderError } from "@aws-sdk/property-provider";
1010

11-
jest.mock("./remoteProvider/httpGet");
11+
jest.mock("./remoteProvider/httpRequest");
1212
jest.mock("./remoteProvider/ImdsCredentials");
1313
jest.mock("./remoteProvider/retry");
1414
jest.mock("./remoteProvider/RemoteProviderInit");
@@ -18,7 +18,7 @@ describe("fromInstanceMetadata", () => {
1818
const mockMaxRetries = 3;
1919
const mockProfile = "foo";
2020

21-
const mockHttpGetOptions = {
21+
const mockHttpRequestOptions = {
2222
host: "169.254.169.254",
2323
path: "/latest/meta-data/iam/security-credentials/",
2424
timeout: mockTimeout
@@ -51,36 +51,36 @@ describe("fromInstanceMetadata", () => {
5151
});
5252

5353
it("gets profile name from IMDS, and passes profile name to fetch credentials", async () => {
54-
(httpGet as jest.Mock)
54+
(httpRequest as jest.Mock)
5555
.mockResolvedValueOnce(mockProfile)
5656
.mockResolvedValueOnce(JSON.stringify(mockImdsCreds));
5757

5858
(retry as jest.Mock).mockImplementation((fn: any) => fn());
5959
(fromImdsCredentials as jest.Mock).mockReturnValue(mockCreds);
6060

6161
await expect(fromInstanceMetadata()()).resolves.toEqual(mockCreds);
62-
expect(httpGet).toHaveBeenCalledTimes(2);
63-
expect(httpGet).toHaveBeenNthCalledWith(1, mockHttpGetOptions);
64-
expect(httpGet).toHaveBeenNthCalledWith(2, {
65-
...mockHttpGetOptions,
66-
path: `${mockHttpGetOptions.path}${mockProfile}`
62+
expect(httpRequest).toHaveBeenCalledTimes(2);
63+
expect(httpRequest).toHaveBeenNthCalledWith(1, mockHttpRequestOptions);
64+
expect(httpRequest).toHaveBeenNthCalledWith(2, {
65+
...mockHttpRequestOptions,
66+
path: `${mockHttpRequestOptions.path}${mockProfile}`
6767
});
6868
});
6969

7070
it("trims profile returned name from IMDS", async () => {
71-
(httpGet as jest.Mock)
71+
(httpRequest as jest.Mock)
7272
.mockResolvedValueOnce(" " + mockProfile + " ")
7373
.mockResolvedValueOnce(JSON.stringify(mockImdsCreds));
7474

7575
(retry as jest.Mock).mockImplementation((fn: any) => fn());
7676
(fromImdsCredentials as jest.Mock).mockReturnValue(mockCreds);
7777

7878
await expect(fromInstanceMetadata()()).resolves.toEqual(mockCreds);
79-
expect(httpGet).toHaveBeenCalledTimes(2);
80-
expect(httpGet).toHaveBeenNthCalledWith(1, mockHttpGetOptions);
81-
expect(httpGet).toHaveBeenNthCalledWith(2, {
82-
...mockHttpGetOptions,
83-
path: `${mockHttpGetOptions.path}${mockProfile}`
79+
expect(httpRequest).toHaveBeenCalledTimes(2);
80+
expect(httpRequest).toHaveBeenNthCalledWith(1, mockHttpRequestOptions);
81+
expect(httpRequest).toHaveBeenNthCalledWith(2, {
82+
...mockHttpRequestOptions,
83+
path: `${mockHttpRequestOptions.path}${mockProfile}`
8484
});
8585
});
8686

@@ -117,7 +117,7 @@ describe("fromInstanceMetadata", () => {
117117
});
118118

119119
it("throws ProviderError if credentials returned are incorrect", async () => {
120-
(httpGet as jest.Mock)
120+
(httpRequest as jest.Mock)
121121
.mockResolvedValueOnce(mockProfile)
122122
.mockResolvedValueOnce(JSON.stringify(mockImdsCreds));
123123

@@ -130,37 +130,37 @@ describe("fromInstanceMetadata", () => {
130130
)
131131
);
132132
expect(retry).toHaveBeenCalledTimes(2);
133-
expect(httpGet).toHaveBeenCalledTimes(2);
133+
expect(httpRequest).toHaveBeenCalledTimes(2);
134134
expect(isImdsCredentials).toHaveBeenCalledTimes(1);
135135
expect(isImdsCredentials).toHaveBeenCalledWith(mockImdsCreds);
136136
expect(fromImdsCredentials).not.toHaveBeenCalled();
137137
});
138138

139139
it("throws Error if requestFromEc2Imds for profile fails", async () => {
140140
const mockError = new Error("profile not found");
141-
(httpGet as jest.Mock).mockRejectedValueOnce(mockError);
141+
(httpRequest as jest.Mock).mockRejectedValueOnce(mockError);
142142
(retry as jest.Mock).mockImplementation((fn: any) => fn());
143143

144144
await expect(fromInstanceMetadata()()).rejects.toEqual(mockError);
145145
expect(retry).toHaveBeenCalledTimes(1);
146-
expect(httpGet).toHaveBeenCalledTimes(1);
146+
expect(httpRequest).toHaveBeenCalledTimes(1);
147147
});
148148

149149
it("throws Error if requestFromEc2Imds for credentials fails", async () => {
150150
const mockError = new Error("creds not found");
151-
(httpGet as jest.Mock)
151+
(httpRequest as jest.Mock)
152152
.mockResolvedValueOnce(mockProfile)
153153
.mockRejectedValueOnce(mockError);
154154
(retry as jest.Mock).mockImplementation((fn: any) => fn());
155155

156156
await expect(fromInstanceMetadata()()).rejects.toEqual(mockError);
157157
expect(retry).toHaveBeenCalledTimes(2);
158-
expect(httpGet).toHaveBeenCalledTimes(2);
158+
expect(httpRequest).toHaveBeenCalledTimes(2);
159159
expect(fromImdsCredentials).not.toHaveBeenCalled();
160160
});
161161

162162
it("throws SyntaxError if requestFromEc2Imds returns unparseable creds", async () => {
163-
(httpGet as jest.Mock)
163+
(httpRequest as jest.Mock)
164164
.mockResolvedValueOnce(mockProfile)
165165
.mockResolvedValueOnce(".");
166166
(retry as jest.Mock).mockImplementation((fn: any) => fn());
@@ -169,7 +169,7 @@ describe("fromInstanceMetadata", () => {
169169
new SyntaxError("Unexpected token . in JSON at position 0")
170170
);
171171
expect(retry).toHaveBeenCalledTimes(2);
172-
expect(httpGet).toHaveBeenCalledTimes(2);
172+
expect(httpRequest).toHaveBeenCalledTimes(2);
173173
expect(fromImdsCredentials).not.toHaveBeenCalled();
174174
});
175175
});

packages/credential-provider-imds/src/fromInstanceMetadata.ts

+15-18
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ import {
33
RemoteProviderInit,
44
providerConfigFromInit
55
} from "./remoteProvider/RemoteProviderInit";
6-
import { httpGet } from "./remoteProvider/httpGet";
6+
import { httpRequest } from "./remoteProvider/httpRequest";
77
import {
88
fromImdsCredentials,
99
isImdsCredentials
1010
} from "./remoteProvider/ImdsCredentials";
1111
import { retry } from "./remoteProvider/retry";
1212
import { ProviderError } from "@aws-sdk/property-provider";
1313

14+
const IMDS_IP = "169.254.169.254";
15+
const IMDS_PATH = "/latest/meta-data/iam/security-credentials/";
16+
1417
/**
1518
* Creates a credential provider that will source credentials from the EC2
1619
* Instance Metadata Service
@@ -22,14 +25,23 @@ export const fromInstanceMetadata = (
2225
return async () => {
2326
const profile = (
2427
await retry<string>(
25-
async () => await requestFromEc2Imds(timeout),
28+
async () =>
29+
(
30+
await httpRequest({ host: IMDS_IP, path: IMDS_PATH, timeout })
31+
).toString(),
2632
maxRetries
2733
)
2834
).trim();
2935

3036
return retry(async () => {
3137
const credsResponse = JSON.parse(
32-
await requestFromEc2Imds(timeout, profile)
38+
(
39+
await httpRequest({
40+
host: IMDS_IP,
41+
path: IMDS_PATH + profile,
42+
timeout
43+
})
44+
).toString()
3345
);
3446
if (!isImdsCredentials(credsResponse)) {
3547
throw new ProviderError(
@@ -41,18 +53,3 @@ export const fromInstanceMetadata = (
4153
}, maxRetries);
4254
};
4355
};
44-
45-
const IMDS_IP = "169.254.169.254";
46-
const IMDS_PATH = "latest/meta-data/iam/security-credentials";
47-
48-
const requestFromEc2Imds = async (
49-
timeout: number,
50-
path?: string
51-
): Promise<string> => {
52-
const buffer = await httpGet({
53-
host: IMDS_IP,
54-
path: `/${IMDS_PATH}/${path ? path : ""}`,
55-
timeout
56-
});
57-
return buffer.toString();
58-
};

0 commit comments

Comments
 (0)