az2aws 1.0.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/.claude/settings.local.json +16 -0
- package/CONTRIBUTING.md +54 -0
- package/LICENSE +22 -0
- package/README.md +250 -0
- package/lib/CLIError.js +12 -0
- package/lib/awsConfig.js +103 -0
- package/lib/configureProfileAsync.js +77 -0
- package/lib/index.js +54 -0
- package/lib/login.js +824 -0
- package/lib/paths.js +18 -0
- package/package.json +64 -0
package/lib/login.js
ADDED
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.login = void 0;
|
|
7
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
+
const bluebird_1 = __importDefault(require("bluebird"));
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const zlib_1 = __importDefault(require("zlib"));
|
|
11
|
+
const client_sts_1 = require("@aws-sdk/client-sts");
|
|
12
|
+
const cheerio_1 = require("cheerio");
|
|
13
|
+
const uuid_1 = require("uuid");
|
|
14
|
+
const puppeteer_1 = __importDefault(require("puppeteer"));
|
|
15
|
+
const querystring_1 = __importDefault(require("querystring"));
|
|
16
|
+
const debug_1 = __importDefault(require("debug"));
|
|
17
|
+
const CLIError_1 = require("./CLIError");
|
|
18
|
+
const awsConfig_1 = require("./awsConfig");
|
|
19
|
+
const proxy_agent_1 = __importDefault(require("proxy-agent"));
|
|
20
|
+
const paths_1 = require("./paths");
|
|
21
|
+
const mkdirp_1 = __importDefault(require("mkdirp"));
|
|
22
|
+
const https_1 = require("https");
|
|
23
|
+
const node_http_handler_1 = require("@smithy/node-http-handler");
|
|
24
|
+
const debug = (0, debug_1.default)("az2aws");
|
|
25
|
+
const WIDTH = 425;
|
|
26
|
+
const HEIGHT = 550;
|
|
27
|
+
const DELAY_ON_UNRECOGNIZED_PAGE = 1000;
|
|
28
|
+
const MAX_UNRECOGNIZED_PAGE_DELAY = 30 * 1000;
|
|
29
|
+
// source: https://docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-sso-quick-start#google-chrome-all-platforms
|
|
30
|
+
const AZURE_AD_SSO = "autologon.microsoftazuread-sso.com";
|
|
31
|
+
const AWS_SAML_ENDPOINT = "https://signin.aws.amazon.com/saml";
|
|
32
|
+
const AWS_CN_SAML_ENDPOINT = "https://signin.amazonaws.cn/saml";
|
|
33
|
+
const AWS_GOV_SAML_ENDPOINT = "https://signin.amazonaws-us-gov.com/saml";
|
|
34
|
+
/**
|
|
35
|
+
* To proxy the input/output of the Azure login page, it's easiest to run a loop that
|
|
36
|
+
* monitors the state of the page and then perform the corresponding CLI behavior.
|
|
37
|
+
* The states have a name that is used for the debug messages, a selector that is used
|
|
38
|
+
* with puppeteer's page.$(selector) to determine if the state is active, and a handler
|
|
39
|
+
* that is called if the state is active.
|
|
40
|
+
*/
|
|
41
|
+
const states = [
|
|
42
|
+
{
|
|
43
|
+
name: "username input",
|
|
44
|
+
selector: `input[name="loginfmt"]:not(.moveOffScreen)`,
|
|
45
|
+
async handler(page, _selected, noPrompt, defaultUsername) {
|
|
46
|
+
const error = await page.$(".alert-error");
|
|
47
|
+
if (error) {
|
|
48
|
+
debug("Found error message. Displaying");
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
50
|
+
const errorMessage = await page.evaluate(
|
|
51
|
+
// eslint-disable-next-line
|
|
52
|
+
(err) => { var _a; return (_a = err === null || err === void 0 ? void 0 : err.textContent) !== null && _a !== void 0 ? _a : ""; }, error);
|
|
53
|
+
console.log(errorMessage);
|
|
54
|
+
}
|
|
55
|
+
let username;
|
|
56
|
+
if (noPrompt && defaultUsername) {
|
|
57
|
+
debug("Not prompting user for username");
|
|
58
|
+
username = defaultUsername;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
debug("Prompting user for username");
|
|
62
|
+
({ username } = await inquirer_1.default.prompt([
|
|
63
|
+
{
|
|
64
|
+
name: "username",
|
|
65
|
+
message: "Username:",
|
|
66
|
+
default: defaultUsername,
|
|
67
|
+
},
|
|
68
|
+
]));
|
|
69
|
+
}
|
|
70
|
+
debug("Waiting for username input to be visible");
|
|
71
|
+
await page.waitForSelector(`input[name="loginfmt"]`, {
|
|
72
|
+
visible: true,
|
|
73
|
+
timeout: 60000,
|
|
74
|
+
});
|
|
75
|
+
debug("Focusing on username input");
|
|
76
|
+
await page.focus(`input[name="loginfmt"]`);
|
|
77
|
+
debug("Clearing input");
|
|
78
|
+
for (let i = 0; i < 100; i++) {
|
|
79
|
+
await page.keyboard.press("Backspace");
|
|
80
|
+
}
|
|
81
|
+
debug("Typing username");
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
83
|
+
await page.keyboard.type(username);
|
|
84
|
+
await bluebird_1.default.delay(500);
|
|
85
|
+
debug("Waiting for submit button to be visible");
|
|
86
|
+
await page.waitForSelector(`input[type=submit]`, {
|
|
87
|
+
visible: true,
|
|
88
|
+
timeout: 60000,
|
|
89
|
+
});
|
|
90
|
+
debug("Submitting form");
|
|
91
|
+
await page.click("input[type=submit]");
|
|
92
|
+
await bluebird_1.default.delay(500);
|
|
93
|
+
debug("Waiting for submission to finish");
|
|
94
|
+
await Promise.race([
|
|
95
|
+
page.waitForSelector(`input[name=loginfmt].has-error,input[name=loginfmt].moveOffScreen`, { timeout: 60000 }),
|
|
96
|
+
(async () => {
|
|
97
|
+
await bluebird_1.default.delay(1000);
|
|
98
|
+
await page.waitForSelector(`input[name=loginfmt]`, {
|
|
99
|
+
hidden: true,
|
|
100
|
+
timeout: 60000,
|
|
101
|
+
});
|
|
102
|
+
})(),
|
|
103
|
+
]);
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "account selection",
|
|
108
|
+
selector: `#aadTile > div > div.table-cell.tile-img > img`,
|
|
109
|
+
async handler(page) {
|
|
110
|
+
debug("Multiple accounts associated with username.");
|
|
111
|
+
const aadTile = await page.$("#aadTileTitle");
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
113
|
+
const aadTileMessage = await page.evaluate(
|
|
114
|
+
// eslint-disable-next-line
|
|
115
|
+
(a) => { var _a; return (_a = a === null || a === void 0 ? void 0 : a.textContent) !== null && _a !== void 0 ? _a : ""; }, aadTile);
|
|
116
|
+
const msaTile = await page.$("#msaTileTitle");
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
118
|
+
const msaTileMessage = await page.evaluate(
|
|
119
|
+
// eslint-disable-next-line
|
|
120
|
+
(m) => { var _a; return (_a = m === null || m === void 0 ? void 0 : m.textContent) !== null && _a !== void 0 ? _a : ""; }, msaTile);
|
|
121
|
+
const accounts = [
|
|
122
|
+
{ message: aadTileMessage, selector: "#aadTileTitle" },
|
|
123
|
+
{ message: msaTileMessage, selector: "#msaTileTitle" },
|
|
124
|
+
];
|
|
125
|
+
let account;
|
|
126
|
+
if (accounts.length === 0) {
|
|
127
|
+
throw new CLIError_1.CLIError("No accounts found on account selection screen.");
|
|
128
|
+
}
|
|
129
|
+
else if (accounts.length === 1) {
|
|
130
|
+
account = accounts[0];
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
debug("Asking user to choose account");
|
|
134
|
+
console.log("It looks like this Username is used with more than one account from Microsoft. Which one do you want to use?");
|
|
135
|
+
const answers = await inquirer_1.default.prompt([
|
|
136
|
+
{
|
|
137
|
+
name: "account",
|
|
138
|
+
message: "Account:",
|
|
139
|
+
type: "list",
|
|
140
|
+
choices: lodash_1.default.map(accounts, "message"),
|
|
141
|
+
default: aadTileMessage,
|
|
142
|
+
},
|
|
143
|
+
]);
|
|
144
|
+
account = lodash_1.default.find(accounts, ["message", answers.account]);
|
|
145
|
+
}
|
|
146
|
+
if (!account) {
|
|
147
|
+
throw new Error("Unable to find account");
|
|
148
|
+
}
|
|
149
|
+
debug(`Proceeding with account ${account.selector}`);
|
|
150
|
+
await page.click(account.selector);
|
|
151
|
+
await bluebird_1.default.delay(500);
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "passwordless",
|
|
156
|
+
selector: `input[value='Send notification']`,
|
|
157
|
+
async handler(page) {
|
|
158
|
+
debug("Sending notification");
|
|
159
|
+
// eslint-disable-next-line
|
|
160
|
+
await page.click("input[value='Send notification']");
|
|
161
|
+
debug("Waiting for auth code");
|
|
162
|
+
// eslint-disable-next-line
|
|
163
|
+
await page.waitForSelector(`#idRemoteNGC_DisplaySign`, {
|
|
164
|
+
visible: true,
|
|
165
|
+
timeout: 60000,
|
|
166
|
+
});
|
|
167
|
+
debug("Printing the message displayed");
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
169
|
+
const messageElement = await page.$("#idDiv_RemoteNGC_PollingDescription");
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
171
|
+
const codeElement = await page.$("#idRemoteNGC_DisplaySign");
|
|
172
|
+
// eslint-disable-next-line
|
|
173
|
+
const message = await page.evaluate(
|
|
174
|
+
// eslint-disable-next-line
|
|
175
|
+
(el) => { var _a; return (_a = el === null || el === void 0 ? void 0 : el.textContent) !== null && _a !== void 0 ? _a : ""; }, messageElement);
|
|
176
|
+
console.log(message);
|
|
177
|
+
debug("Printing the auth code");
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
179
|
+
const authCode = await page.evaluate(
|
|
180
|
+
// eslint-disable-next-line
|
|
181
|
+
(el) => { var _a; return (_a = el === null || el === void 0 ? void 0 : el.textContent) !== null && _a !== void 0 ? _a : ""; }, codeElement);
|
|
182
|
+
console.log(authCode);
|
|
183
|
+
debug("Waiting for response");
|
|
184
|
+
await page.waitForSelector(`#idRemoteNGC_DisplaySign`, {
|
|
185
|
+
hidden: true,
|
|
186
|
+
timeout: 60000,
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "password input",
|
|
192
|
+
selector: `input[name="Password"]:not(.moveOffScreen),input[name="passwd"]:not(.moveOffScreen)`,
|
|
193
|
+
async handler(page, _selected, noPrompt, _defaultUsername, defaultPassword) {
|
|
194
|
+
const error = await page.$(".alert-error");
|
|
195
|
+
if (error) {
|
|
196
|
+
debug("Found error message. Displaying");
|
|
197
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
198
|
+
const errorMessage = await page.evaluate(
|
|
199
|
+
// eslint-disable-next-line
|
|
200
|
+
(err) => { var _a; return (_a = err === null || err === void 0 ? void 0 : err.textContent) !== null && _a !== void 0 ? _a : ""; }, error);
|
|
201
|
+
console.log(errorMessage);
|
|
202
|
+
defaultPassword = ""; // Password error. Unset the default and allow user to enter it.
|
|
203
|
+
}
|
|
204
|
+
let password;
|
|
205
|
+
if (noPrompt && defaultPassword) {
|
|
206
|
+
debug("Not prompting user for password");
|
|
207
|
+
password = defaultPassword;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
debug("Prompting user for password");
|
|
211
|
+
({ password } = await inquirer_1.default.prompt([
|
|
212
|
+
{
|
|
213
|
+
name: "password",
|
|
214
|
+
message: "Password:",
|
|
215
|
+
type: "password",
|
|
216
|
+
},
|
|
217
|
+
]));
|
|
218
|
+
}
|
|
219
|
+
debug("Focusing on password input");
|
|
220
|
+
await page.focus(`input[name="Password"],input[name="passwd"]`);
|
|
221
|
+
debug("Typing password");
|
|
222
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
223
|
+
await page.keyboard.type(password);
|
|
224
|
+
debug("Submitting form");
|
|
225
|
+
await page.click("span[class=submit],input[type=submit]");
|
|
226
|
+
debug("Waiting for a delay");
|
|
227
|
+
await bluebird_1.default.delay(500);
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "TFA instructions",
|
|
232
|
+
selector: `#idDiv_SAOTCAS_Description`,
|
|
233
|
+
async handler(page, selected) {
|
|
234
|
+
const descriptionMessage = await page.evaluate(
|
|
235
|
+
// eslint-disable-next-line
|
|
236
|
+
(description) => { var _a; return (_a = description === null || description === void 0 ? void 0 : description.textContent) !== null && _a !== void 0 ? _a : ""; }, selected);
|
|
237
|
+
console.log(descriptionMessage);
|
|
238
|
+
try {
|
|
239
|
+
debug("Checking if authentication code is displayed");
|
|
240
|
+
const authenticationCodeElement = await page.$("#idRichContext_DisplaySign");
|
|
241
|
+
debug("Reading the authentication code");
|
|
242
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
243
|
+
const authenticationCode = await page.evaluate(
|
|
244
|
+
// eslint-disable-next-line
|
|
245
|
+
(d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.textContent) !== null && _a !== void 0 ? _a : ""; }, authenticationCodeElement);
|
|
246
|
+
debug("Printing the authentication code to console");
|
|
247
|
+
console.log(authenticationCode);
|
|
248
|
+
}
|
|
249
|
+
catch (_a) {
|
|
250
|
+
debug("No authentication code found on page");
|
|
251
|
+
}
|
|
252
|
+
debug("Waiting for response");
|
|
253
|
+
await page.waitForSelector(`#idDiv_SAOTCAS_Description`, {
|
|
254
|
+
hidden: true,
|
|
255
|
+
timeout: 60000,
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "TFA failed",
|
|
261
|
+
selector: `#idDiv_SAASDS_Description,#idDiv_SAASTO_Description`,
|
|
262
|
+
async handler(page, selected) {
|
|
263
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
264
|
+
const descriptionMessage = await page.evaluate(
|
|
265
|
+
// eslint-disable-next-line
|
|
266
|
+
(description) => { var _a; return (_a = description === null || description === void 0 ? void 0 : description.textContent) !== null && _a !== void 0 ? _a : ""; }, selected);
|
|
267
|
+
throw new CLIError_1.CLIError(descriptionMessage);
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: "TFA code input",
|
|
272
|
+
selector: "input[name=otc]:not(.moveOffScreen)",
|
|
273
|
+
async handler(page) {
|
|
274
|
+
const error = await page.$(".alert-error");
|
|
275
|
+
if (error) {
|
|
276
|
+
debug("Found error message. Displaying");
|
|
277
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
278
|
+
const errorMessage = await page.evaluate(
|
|
279
|
+
// eslint-disable-next-line
|
|
280
|
+
(err) => { var _a; return (_a = err === null || err === void 0 ? void 0 : err.textContent) !== null && _a !== void 0 ? _a : ""; }, error);
|
|
281
|
+
console.log(errorMessage);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
const description = await page.$("#idDiv_SAOTCC_Description");
|
|
285
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
286
|
+
const descriptionMessage = await page.evaluate(
|
|
287
|
+
// eslint-disable-next-line
|
|
288
|
+
(d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.textContent) !== null && _a !== void 0 ? _a : ""; }, description);
|
|
289
|
+
console.log(descriptionMessage);
|
|
290
|
+
}
|
|
291
|
+
const { verificationCode } = await inquirer_1.default.prompt([
|
|
292
|
+
{
|
|
293
|
+
name: "verificationCode",
|
|
294
|
+
message: "Verification Code:",
|
|
295
|
+
},
|
|
296
|
+
]);
|
|
297
|
+
debug("Focusing on verification code input");
|
|
298
|
+
await page.focus(`input[name="otc"]`);
|
|
299
|
+
debug("Clearing input");
|
|
300
|
+
for (let i = 0; i < 100; i++) {
|
|
301
|
+
await page.keyboard.press("Backspace");
|
|
302
|
+
}
|
|
303
|
+
debug("Typing verification code");
|
|
304
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
305
|
+
await page.keyboard.type(verificationCode);
|
|
306
|
+
debug("Submitting form");
|
|
307
|
+
await page.click("input[type=submit]");
|
|
308
|
+
debug("Waiting for submission to finish");
|
|
309
|
+
await Promise.race([
|
|
310
|
+
page.waitForSelector(`input[name=otc].has-error,input[name=otc].moveOffScreen`, { timeout: 60000 }),
|
|
311
|
+
(async () => {
|
|
312
|
+
await bluebird_1.default.delay(1000);
|
|
313
|
+
await page.waitForSelector(`input[name=otc]`, {
|
|
314
|
+
hidden: true,
|
|
315
|
+
timeout: 60000,
|
|
316
|
+
});
|
|
317
|
+
})(),
|
|
318
|
+
]);
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "Remember me",
|
|
323
|
+
selector: `#KmsiDescription`,
|
|
324
|
+
async handler(page, _selected, _noPrompt, _defaultUsername, _defaultPassword, rememberMe) {
|
|
325
|
+
if (rememberMe) {
|
|
326
|
+
debug("Clicking remember me button");
|
|
327
|
+
await page.click("#idSIButton9");
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
debug("Clicking don't remember button");
|
|
331
|
+
await page.click("#idBtn_Back");
|
|
332
|
+
}
|
|
333
|
+
debug("Waiting for a delay");
|
|
334
|
+
await bluebird_1.default.delay(500);
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: "Service exception",
|
|
339
|
+
selector: "#service_exception_message",
|
|
340
|
+
async handler(page, selected) {
|
|
341
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
342
|
+
const descriptionMessage = await page.evaluate(
|
|
343
|
+
// eslint-disable-next-line
|
|
344
|
+
(description) => { var _a; return (_a = description === null || description === void 0 ? void 0 : description.textContent) !== null && _a !== void 0 ? _a : ""; }, selected);
|
|
345
|
+
throw new CLIError_1.CLIError(descriptionMessage);
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
];
|
|
349
|
+
exports.login = {
|
|
350
|
+
async loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu) {
|
|
351
|
+
let headless, cliProxy;
|
|
352
|
+
if (mode === "cli") {
|
|
353
|
+
headless = true;
|
|
354
|
+
cliProxy = true;
|
|
355
|
+
}
|
|
356
|
+
else if (mode === "gui") {
|
|
357
|
+
headless = false;
|
|
358
|
+
cliProxy = false;
|
|
359
|
+
}
|
|
360
|
+
else if (mode === "debug") {
|
|
361
|
+
headless = false;
|
|
362
|
+
cliProxy = true;
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
throw new CLIError_1.CLIError("Invalid mode");
|
|
366
|
+
}
|
|
367
|
+
const profile = await this._loadProfileAsync(profileName);
|
|
368
|
+
let assertionConsumerServiceURL = AWS_SAML_ENDPOINT;
|
|
369
|
+
if (profile.region && profile.region.startsWith("us-gov")) {
|
|
370
|
+
assertionConsumerServiceURL = AWS_GOV_SAML_ENDPOINT;
|
|
371
|
+
}
|
|
372
|
+
if (profile.region && profile.region.startsWith("cn-")) {
|
|
373
|
+
assertionConsumerServiceURL = AWS_CN_SAML_ENDPOINT;
|
|
374
|
+
}
|
|
375
|
+
console.log("Using AWS SAML endpoint", assertionConsumerServiceURL);
|
|
376
|
+
const loginUrl = await this._createLoginUrlAsync(profile.azure_app_id_uri, profile.azure_tenant_id, assertionConsumerServiceURL);
|
|
377
|
+
const samlResponse = await this._performLoginAsync(loginUrl, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, profile.azure_default_username, profile.azure_default_password, enableChromeSeamlessSso, profile.azure_default_remember_me, noDisableExtensions, disableGpu);
|
|
378
|
+
const roles = this._parseRolesFromSamlResponse(samlResponse);
|
|
379
|
+
const { role, durationHours } = await this._askUserForRoleAndDurationAsync(roles, noPrompt, profile.azure_default_role_arn, profile.azure_default_duration_hours);
|
|
380
|
+
await this._assumeRoleAsync(profileName, samlResponse, role, durationHours, awsNoVerifySsl, profile.region);
|
|
381
|
+
},
|
|
382
|
+
async loginAll(mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, forceRefresh, noDisableExtensions, disableGpu) {
|
|
383
|
+
const profiles = await awsConfig_1.awsConfig.getAllProfileNames();
|
|
384
|
+
if (!profiles) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
for (const profile of profiles) {
|
|
388
|
+
debug(`Check if profile ${profile} is expired or is about to expire`);
|
|
389
|
+
if (!forceRefresh &&
|
|
390
|
+
!(await awsConfig_1.awsConfig.isProfileAboutToExpireAsync(profile))) {
|
|
391
|
+
debug(`Profile ${profile} not yet due for refresh.`);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
debug(`Run login for profile: ${profile}`);
|
|
395
|
+
await this.loginAsync(profile, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu);
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
// Gather data from environment variables
|
|
399
|
+
_loadProfileFromEnv() {
|
|
400
|
+
const env = {};
|
|
401
|
+
const options = [
|
|
402
|
+
"azure_tenant_id",
|
|
403
|
+
"azure_app_id_uri",
|
|
404
|
+
"azure_default_username",
|
|
405
|
+
"azure_default_password",
|
|
406
|
+
"azure_default_role_arn",
|
|
407
|
+
"azure_default_duration_hours",
|
|
408
|
+
];
|
|
409
|
+
for (let i = 0; i < options.length; i++) {
|
|
410
|
+
const opt = options[i];
|
|
411
|
+
const envVar = process.env[opt];
|
|
412
|
+
const envVarUpperCase = process.env[opt.toUpperCase()];
|
|
413
|
+
if (envVar) {
|
|
414
|
+
env[opt] = envVar;
|
|
415
|
+
}
|
|
416
|
+
else if (envVarUpperCase) {
|
|
417
|
+
env[opt] = envVarUpperCase;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
debug("Environment");
|
|
421
|
+
debug({
|
|
422
|
+
...env,
|
|
423
|
+
azure_default_password: "xxxxxxxxxx",
|
|
424
|
+
});
|
|
425
|
+
return env;
|
|
426
|
+
},
|
|
427
|
+
// Load the profile
|
|
428
|
+
async _loadProfileAsync(profileName) {
|
|
429
|
+
const profile = await awsConfig_1.awsConfig.getProfileConfigAsync(profileName);
|
|
430
|
+
if (!profile)
|
|
431
|
+
throw new CLIError_1.CLIError(`Unknown profile '${profileName}'. You must configure it first with --configure.`);
|
|
432
|
+
const env = this._loadProfileFromEnv();
|
|
433
|
+
for (const prop in env) {
|
|
434
|
+
if (env[prop]) {
|
|
435
|
+
profile[prop] = env[prop] === null ? profile[prop] : env[prop];
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (!profile.azure_tenant_id || !profile.azure_app_id_uri)
|
|
439
|
+
throw new CLIError_1.CLIError(`Profile '${profileName}' is not configured properly.`);
|
|
440
|
+
console.log(`Logging in with profile '${profileName}'...`);
|
|
441
|
+
return profile;
|
|
442
|
+
},
|
|
443
|
+
/**
|
|
444
|
+
* Create the Azure login SAML URL.
|
|
445
|
+
* @param {string} appIdUri - The app ID URI
|
|
446
|
+
* @param {string} tenantId - The Azure tenant ID
|
|
447
|
+
* @param {string} assertionConsumerServiceURL - The AWS SAML endpoint that Azure should send the SAML response to
|
|
448
|
+
* @returns {string} The login URL
|
|
449
|
+
* @private
|
|
450
|
+
*/
|
|
451
|
+
_createLoginUrlAsync(appIdUri, tenantId, assertionConsumerServiceURL) {
|
|
452
|
+
debug("Generating UUID for SAML request");
|
|
453
|
+
const id = (0, uuid_1.v4)();
|
|
454
|
+
const samlRequest = `
|
|
455
|
+
<samlp:AuthnRequest xmlns="urn:oasis:names:tc:SAML:2.0:metadata" ID="id${id}" Version="2.0" IssueInstant="${new Date().toISOString()}" IsPassive="false" AssertionConsumerServiceURL="${assertionConsumerServiceURL}" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
|
|
456
|
+
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">${appIdUri}</Issuer>
|
|
457
|
+
<samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"></samlp:NameIDPolicy>
|
|
458
|
+
</samlp:AuthnRequest>
|
|
459
|
+
`;
|
|
460
|
+
debug("Generated SAML request", samlRequest);
|
|
461
|
+
debug("Deflating SAML");
|
|
462
|
+
return new Promise((resolve, reject) => {
|
|
463
|
+
zlib_1.default.deflateRaw(samlRequest, (err, samlBuffer) => {
|
|
464
|
+
if (err) {
|
|
465
|
+
return reject(err);
|
|
466
|
+
}
|
|
467
|
+
debug("Encoding SAML in base64");
|
|
468
|
+
const samlBase64 = samlBuffer.toString("base64");
|
|
469
|
+
const url = `https://login.microsoftonline.com/${tenantId}/saml2?SAMLRequest=${encodeURIComponent(samlBase64)}`;
|
|
470
|
+
debug("Created login URL", url);
|
|
471
|
+
return resolve(url);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
},
|
|
475
|
+
/**
|
|
476
|
+
* Perform the login using Chrome.
|
|
477
|
+
* @param {string} url - The login URL
|
|
478
|
+
* @param {boolean} headless - True to hide the GUI, false to show it.
|
|
479
|
+
* @param {boolean} disableSandbox - True to disable the Puppeteer sandbox.
|
|
480
|
+
* @param {boolean} cliProxy - True to proxy input/output through the CLI, false to leave it in the GUI
|
|
481
|
+
* @param {bool} [noPrompt] - Enable skipping of user prompting
|
|
482
|
+
* @param {bool} [enableChromeNetworkService] - Enable chrome network service.
|
|
483
|
+
* @param {string} [defaultUsername] - The default username
|
|
484
|
+
* @param {string} [defaultPassword] - The default password
|
|
485
|
+
* @param {bool} [enableChromeSeamlessSso] - chrome seamless SSO
|
|
486
|
+
* @param {bool} [rememberMe] - Enable remembering the session
|
|
487
|
+
* @param {bool} [noDisableExtensions] - True to prevent Puppeteer from disabling Chromium extensions
|
|
488
|
+
* @param {bool} [disableGpu] - Disables GPU Acceleration
|
|
489
|
+
* @returns {Promise.<string>} The SAML response.
|
|
490
|
+
* @private
|
|
491
|
+
*/
|
|
492
|
+
async _performLoginAsync(url, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, defaultUsername, defaultPassword, enableChromeSeamlessSso, rememberMe, noDisableExtensions, disableGpu) {
|
|
493
|
+
debug("Loading login page in Chrome");
|
|
494
|
+
let browser;
|
|
495
|
+
try {
|
|
496
|
+
const args = headless
|
|
497
|
+
? []
|
|
498
|
+
: [`--app=${url}`, `--window-size=${WIDTH},${HEIGHT}`];
|
|
499
|
+
if (disableSandbox)
|
|
500
|
+
args.push("--no-sandbox");
|
|
501
|
+
if (enableChromeNetworkService)
|
|
502
|
+
args.push("--enable-features=NetworkService");
|
|
503
|
+
if (enableChromeSeamlessSso)
|
|
504
|
+
args.push(`--auth-server-whitelist=${AZURE_AD_SSO}`, `--auth-negotiate-delegate-whitelist=${AZURE_AD_SSO}`);
|
|
505
|
+
debug(`rememberMe value: ${rememberMe} (type: ${typeof rememberMe})`);
|
|
506
|
+
if (rememberMe) {
|
|
507
|
+
if (paths_1.paths.userDataDir) {
|
|
508
|
+
args.push(`--user-data-dir=${paths_1.paths.userDataDir}`);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
await (0, mkdirp_1.default)(paths_1.paths.chromium);
|
|
512
|
+
args.push(`--user-data-dir=${paths_1.paths.chromium}`);
|
|
513
|
+
}
|
|
514
|
+
// --profile-directory requires --user-data-dir to work properly
|
|
515
|
+
if (paths_1.paths.profileDir) {
|
|
516
|
+
args.push(`--profile-directory=${paths_1.paths.profileDir}`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (process.env.https_proxy) {
|
|
520
|
+
args.push(`--proxy-server=${process.env.https_proxy}`);
|
|
521
|
+
}
|
|
522
|
+
const ignoreDefaultArgs = noDisableExtensions
|
|
523
|
+
? ["--disable-extensions"]
|
|
524
|
+
: [];
|
|
525
|
+
if (disableGpu) {
|
|
526
|
+
args.push("--disable-gpu");
|
|
527
|
+
}
|
|
528
|
+
const launchParams = {
|
|
529
|
+
headless,
|
|
530
|
+
args,
|
|
531
|
+
ignoreDefaultArgs,
|
|
532
|
+
};
|
|
533
|
+
if (paths_1.paths.chromeBin) {
|
|
534
|
+
launchParams.executablePath = paths_1.paths.chromeBin;
|
|
535
|
+
}
|
|
536
|
+
browser = await puppeteer_1.default.launch(launchParams);
|
|
537
|
+
// Wait for a bit as sometimes the browser isn't ready.
|
|
538
|
+
await bluebird_1.default.delay(200);
|
|
539
|
+
const pages = await browser.pages();
|
|
540
|
+
const page = pages[0];
|
|
541
|
+
await page.setExtraHTTPHeaders({
|
|
542
|
+
"Accept-Language": "en",
|
|
543
|
+
});
|
|
544
|
+
await page.setViewport({ width: WIDTH - 15, height: HEIGHT - 35 });
|
|
545
|
+
// Prevent redirection to AWS
|
|
546
|
+
let samlResponseData;
|
|
547
|
+
const samlResponsePromise = new Promise((resolve) => {
|
|
548
|
+
page.on("request", (req) => {
|
|
549
|
+
const reqURL = req.url();
|
|
550
|
+
debug(`Request: ${url}`);
|
|
551
|
+
if (reqURL === AWS_SAML_ENDPOINT ||
|
|
552
|
+
reqURL === AWS_GOV_SAML_ENDPOINT ||
|
|
553
|
+
reqURL === AWS_CN_SAML_ENDPOINT) {
|
|
554
|
+
resolve(undefined);
|
|
555
|
+
samlResponseData = req.postData();
|
|
556
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
557
|
+
req.respond({
|
|
558
|
+
status: 200,
|
|
559
|
+
contentType: "text/plain",
|
|
560
|
+
headers: {},
|
|
561
|
+
body: "",
|
|
562
|
+
});
|
|
563
|
+
if (browser) {
|
|
564
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
565
|
+
browser.close();
|
|
566
|
+
}
|
|
567
|
+
browser = undefined;
|
|
568
|
+
debug(`Received SAML response, browser closed`);
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
572
|
+
req.continue();
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
debug("Enabling request interception");
|
|
577
|
+
await page.setRequestInterception(true);
|
|
578
|
+
try {
|
|
579
|
+
if (headless || (!headless && cliProxy)) {
|
|
580
|
+
debug("Going to login page");
|
|
581
|
+
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
debug("Waiting for login page to load");
|
|
585
|
+
await page.waitForNavigation({ waitUntil: "networkidle0" });
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
catch (err) {
|
|
589
|
+
if (err instanceof Error) {
|
|
590
|
+
// An error will be thrown if you're still logged in cause the page.goto ot waitForNavigation
|
|
591
|
+
// will be a redirect to AWS. That's usually OK
|
|
592
|
+
debug(`Error occured during loading the first page: ${err.message}`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (cliProxy) {
|
|
596
|
+
let totalUnrecognizedDelay = 0;
|
|
597
|
+
// eslint-disable-next-line no-constant-condition
|
|
598
|
+
while (true) {
|
|
599
|
+
if (samlResponseData)
|
|
600
|
+
break;
|
|
601
|
+
let foundState = false;
|
|
602
|
+
for (let i = 0; i < states.length; i++) {
|
|
603
|
+
const state = states[i];
|
|
604
|
+
let selected;
|
|
605
|
+
try {
|
|
606
|
+
selected = await page.$(state.selector);
|
|
607
|
+
}
|
|
608
|
+
catch (err) {
|
|
609
|
+
if (err instanceof Error) {
|
|
610
|
+
// An error can be thrown if the page isn't in a good state.
|
|
611
|
+
// If one occurs, try again after another loop.
|
|
612
|
+
debug(`Error when running state "${state.name}". ${err.toString()}. Retrying...`);
|
|
613
|
+
}
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
if (selected) {
|
|
617
|
+
foundState = true;
|
|
618
|
+
debug(`Found state: ${state.name}`);
|
|
619
|
+
await Promise.race([
|
|
620
|
+
samlResponsePromise,
|
|
621
|
+
state.handler(page, selected, noPrompt, defaultUsername, defaultPassword, rememberMe),
|
|
622
|
+
]);
|
|
623
|
+
debug(`Finished state: ${state.name}`);
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (foundState) {
|
|
628
|
+
totalUnrecognizedDelay = 0;
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
debug("State not recognized!");
|
|
632
|
+
if (totalUnrecognizedDelay > MAX_UNRECOGNIZED_PAGE_DELAY) {
|
|
633
|
+
const path = "az2aws-unrecognized-state.png";
|
|
634
|
+
await page.screenshot({ path });
|
|
635
|
+
throw new CLIError_1.CLIError(`Unable to recognize page state! A screenshot has been dumped to ${path}. If this problem persists, try running with --mode=gui or --mode=debug`);
|
|
636
|
+
}
|
|
637
|
+
totalUnrecognizedDelay += DELAY_ON_UNRECOGNIZED_PAGE;
|
|
638
|
+
await bluebird_1.default.delay(DELAY_ON_UNRECOGNIZED_PAGE);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
console.log("Please complete the login in the opened window");
|
|
644
|
+
await samlResponsePromise;
|
|
645
|
+
}
|
|
646
|
+
if (!samlResponseData) {
|
|
647
|
+
throw new Error("SAML response not found");
|
|
648
|
+
}
|
|
649
|
+
const samlResponse = querystring_1.default.parse(samlResponseData).SAMLResponse;
|
|
650
|
+
debug("Found SAML response", samlResponse);
|
|
651
|
+
if (!samlResponse) {
|
|
652
|
+
throw new Error("SAML response not found");
|
|
653
|
+
}
|
|
654
|
+
else if (Array.isArray(samlResponse)) {
|
|
655
|
+
throw new Error("SAML can't be an array");
|
|
656
|
+
}
|
|
657
|
+
return samlResponse;
|
|
658
|
+
}
|
|
659
|
+
finally {
|
|
660
|
+
if (browser) {
|
|
661
|
+
await browser.close();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
/**
|
|
666
|
+
* Parse AWS roles out of the SAML response
|
|
667
|
+
* @param {string} assertion - The SAML assertion
|
|
668
|
+
* @returns {Array.<{roleArn: string, principalArn: string}>} The roles
|
|
669
|
+
* @private
|
|
670
|
+
*/
|
|
671
|
+
_parseRolesFromSamlResponse(assertion) {
|
|
672
|
+
debug("Converting assertion from base64 to ASCII");
|
|
673
|
+
const samlText = Buffer.from(assertion, "base64").toString("ascii");
|
|
674
|
+
debug("Converted", samlText);
|
|
675
|
+
debug("Parsing SAML XML");
|
|
676
|
+
const saml = (0, cheerio_1.load)(samlText, { xmlMode: true });
|
|
677
|
+
debug("Looking for role SAML attribute");
|
|
678
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
679
|
+
const roles = saml("Attribute[Name='https://aws.amazon.com/SAML/Attributes/Role']>AttributeValue")
|
|
680
|
+
.map(function () {
|
|
681
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
682
|
+
// @ts-ignore
|
|
683
|
+
const roleAndPrincipal = saml(this).text();
|
|
684
|
+
const parts = roleAndPrincipal.split(",");
|
|
685
|
+
// Role / Principal claims may be in either order
|
|
686
|
+
const [roleIdx, principalIdx] = parts[0].includes(":role/")
|
|
687
|
+
? [0, 1]
|
|
688
|
+
: [1, 0];
|
|
689
|
+
const roleArn = parts[roleIdx].trim();
|
|
690
|
+
const principalArn = parts[principalIdx].trim();
|
|
691
|
+
return { roleArn, principalArn };
|
|
692
|
+
})
|
|
693
|
+
.get();
|
|
694
|
+
debug("Found roles", roles);
|
|
695
|
+
return roles;
|
|
696
|
+
},
|
|
697
|
+
/**
|
|
698
|
+
* Ask the user for the role they want to use.
|
|
699
|
+
* @param {Array.<{roleArn: string, principalArn: string}>} roles - The roles to pick from
|
|
700
|
+
* @param {bool} [noPrompt] - Enable skipping of user prompting
|
|
701
|
+
* @param {string} [defaultRoleArn] - The default role ARN
|
|
702
|
+
* @param {number} [defaultDurationHours] - The default session duration in hours
|
|
703
|
+
* @returns {Promise.<{role: string, durationHours: number}>} The selected role and duration
|
|
704
|
+
* @private
|
|
705
|
+
*/
|
|
706
|
+
async _askUserForRoleAndDurationAsync(roles, noPrompt, defaultRoleArn, defaultDurationHours) {
|
|
707
|
+
let role;
|
|
708
|
+
let durationHours = parseInt(defaultDurationHours, 10);
|
|
709
|
+
const questions = [];
|
|
710
|
+
if (roles.length === 0) {
|
|
711
|
+
throw new CLIError_1.CLIError("No roles found in SAML response.");
|
|
712
|
+
}
|
|
713
|
+
else if (roles.length === 1) {
|
|
714
|
+
debug("Choosing the only role in response");
|
|
715
|
+
role = roles[0];
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
if (noPrompt && defaultRoleArn) {
|
|
719
|
+
role = lodash_1.default.find(roles, ["roleArn", defaultRoleArn]);
|
|
720
|
+
}
|
|
721
|
+
if (role) {
|
|
722
|
+
debug("Valid role found. No need to ask.");
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
debug("Asking user to choose role");
|
|
726
|
+
questions.push({
|
|
727
|
+
name: "role",
|
|
728
|
+
message: "Role:",
|
|
729
|
+
type: "list",
|
|
730
|
+
choices: lodash_1.default.sortBy(lodash_1.default.map(roles, "roleArn")),
|
|
731
|
+
default: defaultRoleArn,
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (noPrompt && defaultDurationHours) {
|
|
736
|
+
debug("Default durationHours found. No need to ask.");
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
questions.push({
|
|
740
|
+
name: "durationHours",
|
|
741
|
+
message: "Session Duration Hours (up to 12):",
|
|
742
|
+
type: "input",
|
|
743
|
+
default: defaultDurationHours || 1,
|
|
744
|
+
validate: (input) => {
|
|
745
|
+
input = Number(input);
|
|
746
|
+
if (input > 0 && input <= 12)
|
|
747
|
+
return true;
|
|
748
|
+
return "Duration hours must be between 0 and 12";
|
|
749
|
+
},
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
// Don't prompt for questions if not needed, an unneeded TTYWRAP prevents node from exiting when
|
|
753
|
+
// user is logged in and using multiple profiles --all-profiles and --no-prompt
|
|
754
|
+
if (questions.length > 0) {
|
|
755
|
+
const answers = await inquirer_1.default.prompt(questions);
|
|
756
|
+
if (!role)
|
|
757
|
+
role = lodash_1.default.find(roles, ["roleArn", answers.role]);
|
|
758
|
+
if (answers.durationHours) {
|
|
759
|
+
durationHours = parseInt(answers.durationHours, 10);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (!role) {
|
|
763
|
+
throw new Error(`Unable to find role`);
|
|
764
|
+
}
|
|
765
|
+
return { role, durationHours };
|
|
766
|
+
},
|
|
767
|
+
/**
|
|
768
|
+
* Assume the role.
|
|
769
|
+
* @param {string} profileName - The profile name
|
|
770
|
+
* @param {string} assertion - The SAML assertion
|
|
771
|
+
* @param {string} role - The role to assume
|
|
772
|
+
* @param {number} durationHours - The session duration in hours
|
|
773
|
+
* @param {bool} awsNoVerifySsl - Whether to have the AWS CLI verify SSL
|
|
774
|
+
* @param {string} region - AWS region, if specified
|
|
775
|
+
* @returns {Promise} A promise
|
|
776
|
+
* @private
|
|
777
|
+
*/
|
|
778
|
+
async _assumeRoleAsync(profileName, assertion, role, durationHours, awsNoVerifySsl, region) {
|
|
779
|
+
var _a, _b, _c, _d, _e;
|
|
780
|
+
console.log(`Assuming role ${role.roleArn} in region ${region}...`);
|
|
781
|
+
let stsOptions = {};
|
|
782
|
+
if (process.env.https_proxy) {
|
|
783
|
+
stsOptions = {
|
|
784
|
+
...stsOptions,
|
|
785
|
+
requestHandler: new node_http_handler_1.NodeHttpHandler({
|
|
786
|
+
httpsAgent: (0, proxy_agent_1.default)(process.env.https_proxy),
|
|
787
|
+
}),
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
if (awsNoVerifySsl) {
|
|
791
|
+
stsOptions = {
|
|
792
|
+
...stsOptions,
|
|
793
|
+
requestHandler: new node_http_handler_1.NodeHttpHandler({
|
|
794
|
+
httpsAgent: new https_1.Agent({
|
|
795
|
+
rejectUnauthorized: false,
|
|
796
|
+
}),
|
|
797
|
+
}),
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
if (region) {
|
|
801
|
+
stsOptions = {
|
|
802
|
+
...stsOptions,
|
|
803
|
+
region,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
const sts = new client_sts_1.STS(stsOptions);
|
|
807
|
+
const res = await sts.assumeRoleWithSAML({
|
|
808
|
+
PrincipalArn: role.principalArn,
|
|
809
|
+
RoleArn: role.roleArn,
|
|
810
|
+
SAMLAssertion: assertion,
|
|
811
|
+
DurationSeconds: Math.round(durationHours * 60 * 60),
|
|
812
|
+
});
|
|
813
|
+
if (!res.Credentials) {
|
|
814
|
+
debug("Unable to get security credentials from AWS");
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
await awsConfig_1.awsConfig.setProfileCredentialsAsync(profileName, {
|
|
818
|
+
aws_access_key_id: (_a = res.Credentials.AccessKeyId) !== null && _a !== void 0 ? _a : "",
|
|
819
|
+
aws_secret_access_key: (_b = res.Credentials.SecretAccessKey) !== null && _b !== void 0 ? _b : "",
|
|
820
|
+
aws_session_token: (_c = res.Credentials.SessionToken) !== null && _c !== void 0 ? _c : "",
|
|
821
|
+
aws_expiration: (_e = (_d = res.Credentials.Expiration) === null || _d === void 0 ? void 0 : _d.toISOString()) !== null && _e !== void 0 ? _e : "",
|
|
822
|
+
});
|
|
823
|
+
},
|
|
824
|
+
};
|