@aws-sdk/credential-provider-sso 3.21.0 → 3.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/README.md +22 -5
- package/dist/cjs/index.js +53 -21
- package/dist/es/index.js +98 -61
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.es.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +38 -2
- package/dist/types/ts3.4/index.d.ts +38 -2
- package/package.json +9 -9
- package/src/index.spec.ts +193 -131
- package/src/index.ts +97 -29
package/src/index.spec.ts
CHANGED
|
@@ -2,10 +2,10 @@ jest.useFakeTimers("modern");
|
|
|
2
2
|
const now = 1613699814645;
|
|
3
3
|
jest.setSystemTime(now);
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const mockParseKnownFiles = jest.fn();
|
|
6
6
|
const mockGetMasterProfileName = jest.fn();
|
|
7
|
-
jest.mock("@aws-sdk/
|
|
8
|
-
parseKnownFiles:
|
|
7
|
+
jest.mock("@aws-sdk/util-credentials", () => ({
|
|
8
|
+
parseKnownFiles: mockParseKnownFiles,
|
|
9
9
|
getMasterProfileName: mockGetMasterProfileName,
|
|
10
10
|
}));
|
|
11
11
|
|
|
@@ -36,170 +36,232 @@ const toRFC3339String = (date: number): string => {
|
|
|
36
36
|
return timestamp.replace(/\.[\d]+Z$/, "Z");
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
describe("fromSSO", () => {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
sso_role_name: "some-role",
|
|
45
|
-
};
|
|
39
|
+
describe("fromSSO()", () => {
|
|
40
|
+
const ssoStartUrl = "https:some-url/start";
|
|
41
|
+
const ssoAccountId = "1234567890";
|
|
42
|
+
const ssoRegion = "us-foo-1";
|
|
43
|
+
const ssoRoleName = "some-role";
|
|
46
44
|
const token = {
|
|
47
|
-
startUrl:
|
|
48
|
-
region:
|
|
45
|
+
startUrl: ssoStartUrl,
|
|
46
|
+
region: ssoRegion,
|
|
49
47
|
accessToken: "base64 encoded string",
|
|
50
48
|
expiresAt: toRFC3339String(now + 60 * 60 * 1000),
|
|
51
49
|
};
|
|
50
|
+
|
|
52
51
|
beforeEach(() => {
|
|
53
|
-
|
|
52
|
+
mockParseKnownFiles.mockClear();
|
|
54
53
|
mockGetMasterProfileName.mockClear();
|
|
55
54
|
mockReadFileSync.mockClear();
|
|
56
55
|
mockSSOSend.mockClear();
|
|
57
56
|
});
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
58
|
+
describe("load from shared config file", () => {
|
|
59
|
+
const ssoConfig = {
|
|
60
|
+
sso_start_url: ssoStartUrl,
|
|
61
|
+
sso_account_id: ssoAccountId,
|
|
62
|
+
sso_region: ssoRegion,
|
|
63
|
+
sso_role_name: ssoRoleName,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
it("should fetch credentials from resolved token file", async () => {
|
|
67
|
+
mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
|
|
68
|
+
mockGetMasterProfileName.mockReturnValue("default");
|
|
69
|
+
mockReadFileSync.mockReturnValue(JSON.stringify(token));
|
|
70
|
+
const { roleCredentials } = mockRoleCredentials;
|
|
71
|
+
expect(await fromSSO()()).toEqual({ ...roleCredentials, expiration: new Date(roleCredentials.expiration) });
|
|
72
|
+
expect(mockReadFileSync.mock.calls[0][0]).toEqual(
|
|
73
|
+
expect.stringMatching(/fcab95d6966151d97d9ee7776a90d895b5e5fbe6.json$/)
|
|
74
|
+
);
|
|
75
|
+
expect(mockReadFileSync.mock.calls[0][1]).toMatchObject({ encoding: "utf-8" });
|
|
76
|
+
expect(mockGetRoleCredentialsCommand).toHaveBeenCalledWith({
|
|
77
|
+
accountId: ssoConfig.sso_account_id,
|
|
78
|
+
roleName: ssoConfig.sso_role_name,
|
|
79
|
+
accessToken: token.accessToken,
|
|
80
|
+
});
|
|
73
81
|
});
|
|
74
|
-
});
|
|
75
82
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
it("should allow supplying custom client", async () => {
|
|
84
|
+
mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
|
|
85
|
+
mockGetMasterProfileName.mockReturnValue("default");
|
|
86
|
+
mockReadFileSync.mockReturnValue(JSON.stringify(token));
|
|
87
|
+
const newSSOClient = { send: jest.fn().mockReturnValue(Promise.resolve(mockRoleCredentials)) };
|
|
88
|
+
//@ts-expect-error
|
|
89
|
+
await fromSSO({ ssoClient: newSSOClient })();
|
|
90
|
+
expect(newSSOClient.send).toHaveBeenCalled();
|
|
91
|
+
expect(mockSSOSend).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
86
93
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
it("should throw if profile doesn't exist in the config files", () => {
|
|
95
|
+
const profile = "exist";
|
|
96
|
+
mockParseKnownFiles.mockReturnValue(Promise.resolve({ non_exist: { foo: "bar" } }));
|
|
97
|
+
mockGetMasterProfileName.mockReturnValue(profile);
|
|
98
|
+
return expect(async () => {
|
|
99
|
+
await fromSSO()();
|
|
100
|
+
}).rejects.toMatchObject({
|
|
101
|
+
name: "CredentialsProviderError",
|
|
102
|
+
message: expect.stringContaining("Profile exist is not configured with SSO credentials"),
|
|
103
|
+
tryNextLink: true,
|
|
104
|
+
});
|
|
96
105
|
});
|
|
97
|
-
});
|
|
98
106
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
it("should throw if profile is not configured with SSO credential", () => {
|
|
108
|
+
const profile = "exist";
|
|
109
|
+
mockParseKnownFiles.mockReturnValue(Promise.resolve({ [profile]: { foo: "bar" } }));
|
|
110
|
+
mockGetMasterProfileName.mockReturnValue(profile);
|
|
111
|
+
return expect(async () => {
|
|
112
|
+
await fromSSO()();
|
|
113
|
+
}).rejects.toMatchObject({
|
|
114
|
+
message: `Profile ${profile} is not configured with SSO credentials.`,
|
|
115
|
+
tryNextLink: true,
|
|
116
|
+
});
|
|
108
117
|
});
|
|
109
|
-
});
|
|
110
118
|
|
|
111
|
-
|
|
112
|
-
const keyToRemove = Object.keys(ssoConfig)[i];
|
|
113
|
-
it(`should throw if sso configuration is missing ${keyToRemove}`, async () => {
|
|
119
|
+
it.each(Object.keys(ssoConfig))("should throw if sso configuration is missing %s", async (keyToRemove) => {
|
|
114
120
|
expect.assertions(2);
|
|
115
121
|
const config = { ...ssoConfig };
|
|
116
122
|
//@ts-ignore
|
|
117
123
|
delete config[keyToRemove];
|
|
118
|
-
|
|
124
|
+
mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: config }));
|
|
119
125
|
mockGetMasterProfileName.mockReturnValue("default");
|
|
120
126
|
try {
|
|
121
127
|
await fromSSO()();
|
|
122
128
|
} catch (e) {
|
|
123
|
-
expect(e.message).toContain("Profile
|
|
129
|
+
expect(e.message).toContain("Profile is configured with invalid SSO credentials.");
|
|
124
130
|
expect(e.tryNextLink).toBeFalsy();
|
|
125
131
|
}
|
|
126
132
|
});
|
|
127
|
-
}
|
|
128
133
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
it("should throw if token cache file is not found", async () => {
|
|
135
|
+
expect.assertions(2);
|
|
136
|
+
mockReadFileSync.mockImplementation(() => {
|
|
137
|
+
throw new Error("File not found.");
|
|
138
|
+
});
|
|
139
|
+
mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
|
|
140
|
+
mockGetMasterProfileName.mockReturnValue("default");
|
|
141
|
+
try {
|
|
142
|
+
await fromSSO()();
|
|
143
|
+
} catch (e) {
|
|
144
|
+
expect(e.message).toContain(
|
|
145
|
+
"The SSO session associated with this profile has expired or is otherwise invalid."
|
|
146
|
+
);
|
|
147
|
+
expect(e.tryNextLink).toBeFalsy();
|
|
148
|
+
}
|
|
133
149
|
});
|
|
134
|
-
mockParseKnowFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
|
|
135
|
-
mockGetMasterProfileName.mockReturnValue("default");
|
|
136
|
-
try {
|
|
137
|
-
await fromSSO()();
|
|
138
|
-
} catch (e) {
|
|
139
|
-
expect(e.message).toContain("The SSO session associated with this profile has expired or is otherwise invalid.");
|
|
140
|
-
expect(e.tryNextLink).toBeFalsy();
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
150
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
it("should throw if token cache file is invalid", async () => {
|
|
152
|
+
expect.assertions(2);
|
|
153
|
+
mockReadFileSync.mockReturnValue("invalid JSON content");
|
|
154
|
+
mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
|
|
155
|
+
mockGetMasterProfileName.mockReturnValue("default");
|
|
156
|
+
try {
|
|
157
|
+
await fromSSO()();
|
|
158
|
+
} catch (e) {
|
|
159
|
+
expect(e.message).toContain(
|
|
160
|
+
"The SSO session associated with this profile has expired or is otherwise invalid."
|
|
161
|
+
);
|
|
162
|
+
expect(e.tryNextLink).toBeFalsy();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
156
165
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
it("should throw if token cache is expired", async () => {
|
|
167
|
+
expect.assertions(2);
|
|
168
|
+
mockReadFileSync.mockReturnValue({ ...token, expiration: toRFC3339String(now + EXPIRE_WINDOW_MS - 2) });
|
|
169
|
+
mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
|
|
170
|
+
mockGetMasterProfileName.mockReturnValue("default");
|
|
171
|
+
try {
|
|
172
|
+
await fromSSO()();
|
|
173
|
+
} catch (e) {
|
|
174
|
+
expect(e.message).toContain(
|
|
175
|
+
"The SSO session associated with this profile has expired or is otherwise invalid."
|
|
176
|
+
);
|
|
177
|
+
expect(e.tryNextLink).toBeFalsy();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
169
180
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
181
|
+
it("should throw if SSO client throws", async () => {
|
|
182
|
+
expect.assertions(3);
|
|
183
|
+
mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
|
|
184
|
+
mockGetMasterProfileName.mockReturnValue("default");
|
|
185
|
+
mockReadFileSync.mockReturnValue(JSON.stringify(token));
|
|
186
|
+
const clientError = new Error("No account is found for the user");
|
|
187
|
+
//@ts-ignore
|
|
188
|
+
clientError.$fault = "client";
|
|
189
|
+
mockSSOSend.mockImplementation(async () => {
|
|
190
|
+
throw clientError;
|
|
191
|
+
});
|
|
192
|
+
try {
|
|
193
|
+
await fromSSO()();
|
|
194
|
+
} catch (e) {
|
|
195
|
+
expect(e.message).toContain(clientError.message);
|
|
196
|
+
expect(e.tryNextLink).toBeFalsy();
|
|
197
|
+
expect(e.$fault).toBe("client");
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should throw if credentials from SSO client is invalid", async () => {
|
|
202
|
+
expect.assertions(2);
|
|
203
|
+
mockReadFileSync.mockReturnValue(JSON.stringify(token));
|
|
204
|
+
mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
|
|
205
|
+
mockGetMasterProfileName.mockReturnValue("default");
|
|
206
|
+
mockSSOSend.mockResolvedValue({
|
|
207
|
+
roleCredentials: { ...mockRoleCredentials.roleCredentials, accessKeyId: undefined },
|
|
208
|
+
});
|
|
209
|
+
try {
|
|
210
|
+
await fromSSO()();
|
|
211
|
+
} catch (e) {
|
|
212
|
+
expect(e.message).toContain("SSO returns an invalid temporary credential.");
|
|
213
|
+
expect(e.tryNextLink).toBeFalsy();
|
|
214
|
+
} finally {
|
|
215
|
+
mockSSOSend.mockResolvedValue(mockRoleCredentials);
|
|
216
|
+
}
|
|
180
217
|
});
|
|
181
|
-
try {
|
|
182
|
-
await fromSSO()();
|
|
183
|
-
} catch (e) {
|
|
184
|
-
expect(e.message).toContain(clientError.message);
|
|
185
|
-
expect(e.tryNextLink).toBeFalsy();
|
|
186
|
-
expect(e.$fault).toBe("client");
|
|
187
|
-
}
|
|
188
218
|
});
|
|
189
219
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
220
|
+
describe("load with sso parameters", () => {
|
|
221
|
+
it("should fetch credentials from resolved token file without reading shared config file", async () => {
|
|
222
|
+
mockParseKnownFiles.mockRejectedValue("Should not call parseKnownFiles()");
|
|
223
|
+
mockGetMasterProfileName.mockRejectedValue("Should not call getMasterProfileName()");
|
|
224
|
+
mockReadFileSync.mockReturnValue(JSON.stringify(token));
|
|
225
|
+
const { roleCredentials } = mockRoleCredentials;
|
|
226
|
+
expect(
|
|
227
|
+
await fromSSO({
|
|
228
|
+
ssoStartUrl,
|
|
229
|
+
ssoAccountId,
|
|
230
|
+
ssoRegion,
|
|
231
|
+
ssoRoleName,
|
|
232
|
+
})()
|
|
233
|
+
).toEqual({ ...roleCredentials, expiration: new Date(roleCredentials.expiration) });
|
|
234
|
+
expect(mockReadFileSync.mock.calls[0][0]).toEqual(
|
|
235
|
+
expect.stringMatching(/fcab95d6966151d97d9ee7776a90d895b5e5fbe6.json$/)
|
|
236
|
+
);
|
|
237
|
+
expect(mockReadFileSync.mock.calls[0][1]).toMatchObject({ encoding: "utf-8" });
|
|
238
|
+
expect(mockGetRoleCredentialsCommand).toHaveBeenCalledWith({
|
|
239
|
+
accountId: ssoAccountId,
|
|
240
|
+
roleName: ssoRoleName,
|
|
241
|
+
accessToken: token.accessToken,
|
|
242
|
+
});
|
|
197
243
|
});
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
244
|
+
|
|
245
|
+
it.each(["ssoStartUrl", "ssoAccountId", "ssoRegion", "ssoRoleName"])(
|
|
246
|
+
"should throw for incomplete sso parameters(missing %s)",
|
|
247
|
+
(undefinedKey) => {
|
|
248
|
+
mockParseKnownFiles.mockRejectedValue("Should not call parseKnownFiles()");
|
|
249
|
+
mockGetMasterProfileName.mockRejectedValue("Should not call getMasterProfileName()");
|
|
250
|
+
mockReadFileSync.mockReturnValue(JSON.stringify(token));
|
|
251
|
+
return expect(
|
|
252
|
+
async () =>
|
|
253
|
+
await fromSSO({
|
|
254
|
+
ssoStartUrl,
|
|
255
|
+
ssoAccountId,
|
|
256
|
+
ssoRegion,
|
|
257
|
+
ssoRoleName,
|
|
258
|
+
...{ [undefinedKey]: undefined },
|
|
259
|
+
} as any)()
|
|
260
|
+
).rejects.toMatchObject({
|
|
261
|
+
name: "CredentialsProviderError",
|
|
262
|
+
message: expect.stringMatching("Incomplete configuration"),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
);
|
|
204
266
|
});
|
|
205
267
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { GetRoleCredentialsCommand, GetRoleCredentialsCommandOutput, SSOClient } from "@aws-sdk/client-sso";
|
|
2
|
-
import { getMasterProfileName, parseKnownFiles, SourceProfileInit } from "@aws-sdk/credential-provider-ini";
|
|
3
2
|
import { CredentialsProviderError } from "@aws-sdk/property-provider";
|
|
4
|
-
import { getHomeDir,
|
|
3
|
+
import { getHomeDir, Profile } from "@aws-sdk/shared-ini-file-loader";
|
|
5
4
|
import { CredentialProvider, Credentials } from "@aws-sdk/types";
|
|
5
|
+
import { getMasterProfileName, parseKnownFiles, SourceProfileInit } from "@aws-sdk/util-credentials";
|
|
6
6
|
import { createHash } from "crypto";
|
|
7
7
|
import { readFileSync } from "fs";
|
|
8
8
|
import { join } from "path";
|
|
@@ -29,6 +29,27 @@ interface SSOToken {
|
|
|
29
29
|
startUrl?: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export interface SsoCredentialsParameters {
|
|
33
|
+
/**
|
|
34
|
+
* The URL to the AWS SSO service.
|
|
35
|
+
*/
|
|
36
|
+
ssoStartUrl: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The ID of the AWS account to use for temporary credentials.
|
|
40
|
+
*/
|
|
41
|
+
ssoAccountId: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The AWS region to use for temporary credentials.
|
|
45
|
+
*/
|
|
46
|
+
ssoRegion: string;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The name of the AWS role to assume.
|
|
50
|
+
*/
|
|
51
|
+
ssoRoleName: string;
|
|
52
|
+
}
|
|
32
53
|
export interface FromSSOInit extends SourceProfileInit {
|
|
33
54
|
ssoClient?: SSOClient;
|
|
34
55
|
}
|
|
@@ -38,34 +59,44 @@ export interface FromSSOInit extends SourceProfileInit {
|
|
|
38
59
|
* in ini files.
|
|
39
60
|
*/
|
|
40
61
|
export const fromSSO =
|
|
41
|
-
(init: FromSSOInit = {}): CredentialProvider =>
|
|
62
|
+
(init: FromSSOInit & Partial<SsoCredentialsParameters> = {} as any): CredentialProvider =>
|
|
42
63
|
async () => {
|
|
43
|
-
const
|
|
44
|
-
|
|
64
|
+
const { ssoStartUrl, ssoAccountId, ssoRegion, ssoRoleName, ssoClient } = init;
|
|
65
|
+
if (!ssoStartUrl && !ssoAccountId && !ssoRegion && !ssoRoleName) {
|
|
66
|
+
// Load the SSO config from shared AWS config file.
|
|
67
|
+
const profiles = await parseKnownFiles(init);
|
|
68
|
+
const profileName = getMasterProfileName(init);
|
|
69
|
+
const profile = profiles[profileName];
|
|
70
|
+
if (!isSsoProfile(profile)) {
|
|
71
|
+
throw new CredentialsProviderError(`Profile ${profileName} is not configured with SSO credentials.`);
|
|
72
|
+
}
|
|
73
|
+
const { sso_start_url, sso_account_id, sso_region, sso_role_name } = validateSsoProfile(profile);
|
|
74
|
+
return resolveSSOCredentials({
|
|
75
|
+
ssoStartUrl: sso_start_url,
|
|
76
|
+
ssoAccountId: sso_account_id,
|
|
77
|
+
ssoRegion: sso_region,
|
|
78
|
+
ssoRoleName: sso_role_name,
|
|
79
|
+
ssoClient: ssoClient,
|
|
80
|
+
});
|
|
81
|
+
} else if (!ssoStartUrl || !ssoAccountId || !ssoRegion || !ssoRoleName) {
|
|
82
|
+
throw new CredentialsProviderError(
|
|
83
|
+
'Incomplete configuration. The fromSSO() argument hash must include "ssoStartUrl",' +
|
|
84
|
+
' "ssoAccountId", "ssoRegion", "ssoRoleName"'
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
return resolveSSOCredentials({ ssoStartUrl, ssoAccountId, ssoRegion, ssoRoleName, ssoClient });
|
|
88
|
+
}
|
|
45
89
|
};
|
|
46
90
|
|
|
47
|
-
const resolveSSOCredentials = async (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
throw new CredentialsProviderError(`Profile ${profileName} could not be found in shared credentials file.`);
|
|
55
|
-
}
|
|
56
|
-
const { sso_start_url: startUrl, sso_account_id: accountId, sso_region: region, sso_role_name: roleName } = profile;
|
|
57
|
-
if (!startUrl && !accountId && !region && !roleName) {
|
|
58
|
-
throw new CredentialsProviderError(`Profile ${profileName} is not configured with SSO credentials.`);
|
|
59
|
-
}
|
|
60
|
-
if (!startUrl || !accountId || !region || !roleName) {
|
|
61
|
-
throw new CredentialsProviderError(
|
|
62
|
-
`Profile ${profileName} does not have valid SSO credentials. Required parameters "sso_account_id", "sso_region", ` +
|
|
63
|
-
`"sso_role_name", "sso_start_url". Reference: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html`,
|
|
64
|
-
SHOULD_FAIL_CREDENTIAL_CHAIN
|
|
65
|
-
);
|
|
66
|
-
}
|
|
91
|
+
const resolveSSOCredentials = async ({
|
|
92
|
+
ssoStartUrl,
|
|
93
|
+
ssoAccountId,
|
|
94
|
+
ssoRegion,
|
|
95
|
+
ssoRoleName,
|
|
96
|
+
ssoClient,
|
|
97
|
+
}: FromSSOInit & SsoCredentialsParameters): Promise<Credentials> => {
|
|
67
98
|
const hasher = createHash("sha1");
|
|
68
|
-
const cacheName = hasher.update(
|
|
99
|
+
const cacheName = hasher.update(ssoStartUrl).digest("hex");
|
|
69
100
|
const tokenFile = join(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
|
|
70
101
|
let token: SSOToken;
|
|
71
102
|
try {
|
|
@@ -81,13 +112,13 @@ const resolveSSOCredentials = async (
|
|
|
81
112
|
);
|
|
82
113
|
}
|
|
83
114
|
const { accessToken } = token;
|
|
84
|
-
const sso =
|
|
115
|
+
const sso = ssoClient || new SSOClient({ region: ssoRegion });
|
|
85
116
|
let ssoResp: GetRoleCredentialsCommandOutput;
|
|
86
117
|
try {
|
|
87
118
|
ssoResp = await sso.send(
|
|
88
119
|
new GetRoleCredentialsCommand({
|
|
89
|
-
accountId,
|
|
90
|
-
roleName,
|
|
120
|
+
accountId: ssoAccountId,
|
|
121
|
+
roleName: ssoRoleName,
|
|
91
122
|
accessToken,
|
|
92
123
|
})
|
|
93
124
|
);
|
|
@@ -100,3 +131,40 @@ const resolveSSOCredentials = async (
|
|
|
100
131
|
}
|
|
101
132
|
return { accessKeyId, secretAccessKey, sessionToken, expiration: new Date(expiration) };
|
|
102
133
|
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @internal
|
|
137
|
+
*/
|
|
138
|
+
export interface SsoProfile extends Profile {
|
|
139
|
+
sso_start_url: string;
|
|
140
|
+
sso_account_id: string;
|
|
141
|
+
sso_region: string;
|
|
142
|
+
sso_role_name: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @internal
|
|
147
|
+
*/
|
|
148
|
+
export const validateSsoProfile = (profile: Partial<SsoProfile>): SsoProfile => {
|
|
149
|
+
const { sso_start_url, sso_account_id, sso_region, sso_role_name } = profile;
|
|
150
|
+
if (!sso_start_url || !sso_account_id || !sso_region || !sso_role_name) {
|
|
151
|
+
throw new CredentialsProviderError(
|
|
152
|
+
`Profile is configured with invalid SSO credentials. Required parameters "sso_account_id", "sso_region", ` +
|
|
153
|
+
`"sso_role_name", "sso_start_url". Got ${Object.keys(profile).join(
|
|
154
|
+
", "
|
|
155
|
+
)}\nReference: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html`,
|
|
156
|
+
SHOULD_FAIL_CREDENTIAL_CHAIN
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return profile as SsoProfile;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @internal
|
|
164
|
+
*/
|
|
165
|
+
export const isSsoProfile = (arg: Profile): arg is Partial<SsoProfile> =>
|
|
166
|
+
arg &&
|
|
167
|
+
(typeof arg.sso_start_url === "string" ||
|
|
168
|
+
typeof arg.sso_account_id === "string" ||
|
|
169
|
+
typeof arg.sso_region === "string" ||
|
|
170
|
+
typeof arg.sso_role_name === "string");
|