az2aws 1.6.2 → 1.8.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/lib/login.js CHANGED
@@ -20,6 +20,8 @@ const promises_2 = __importDefault(require("fs/promises"));
20
20
  const https_1 = require("https");
21
21
  const node_http_handler_1 = require("@smithy/node-http-handler");
22
22
  const loginStates_1 = require("./loginStates");
23
+ const sessionDuration_1 = require("./sessionDuration");
24
+ const sensitiveOutput_1 = require("./sensitiveOutput");
23
25
  const debug = (0, debug_1.default)("az2aws");
24
26
  const WIDTH = 425;
25
27
  const HEIGHT = 550;
@@ -30,6 +32,7 @@ const AZURE_AD_SSO = "autologon.microsoftazuread-sso.com";
30
32
  const AWS_SAML_ENDPOINT = "https://signin.aws.amazon.com/saml";
31
33
  const AWS_CN_SAML_ENDPOINT = "https://signin.amazonaws.cn/saml";
32
34
  const AWS_GOV_SAML_ENDPOINT = "https://signin.amazonaws-us-gov.com/saml";
35
+ const REDACTED = "[redacted]";
33
36
  // Keep the runtime import as native `import()` so CommonJS output can load
34
37
  // the ESM-only https-proxy-agent package.
35
38
  // eslint-disable-next-line @typescript-eslint/no-implied-eval
@@ -38,51 +41,85 @@ const getProxyUrl = () => process.env.https_proxy ||
38
41
  process.env.HTTPS_PROXY ||
39
42
  process.env.http_proxy ||
40
43
  process.env.HTTP_PROXY;
44
+ function printCredentialsReadyMessage(profileName, credentials) {
45
+ console.log();
46
+ console.log(`Credentials expire at ${credentials.aws_expiration}.`);
47
+ console.log(`Use them with AWS CLI by passing --profile "${profileName}".`);
48
+ }
49
+ function handleBackgroundPromise(promise, description) {
50
+ void promise.catch((error) => {
51
+ const message = (0, sensitiveOutput_1.formatDebugErrorMessage)(error);
52
+ debug(`${description}: ${message}`);
53
+ });
54
+ }
41
55
  exports.login = {
42
56
  async _createHttpsProxyAgentAsync(proxyUrl, proxyOptions) {
43
57
  const { HttpsProxyAgent } = await importHttpsProxyAgent();
44
58
  return new HttpsProxyAgent(proxyUrl, proxyOptions);
45
59
  },
46
- async loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu, incognito = false) {
47
- let headless, cliProxy;
48
- if (mode === "cli") {
49
- headless = true;
50
- cliProxy = true;
51
- }
52
- else if (mode === "gui") {
53
- headless = false;
54
- cliProxy = false;
55
- }
56
- else if (mode === "debug") {
57
- headless = false;
58
- cliProxy = true;
59
- }
60
- else {
61
- throw new CLIError_1.CLIError("Invalid mode");
62
- }
63
- const profile = await this._loadProfileAsync(profileName);
64
- console.log(`Using AWS region ${profile.region || "(from AWS SDK defaults)"}`);
65
- if (profile.region && profile.region.startsWith("us-gov")) {
66
- console.warn("GovCloud region detected in profile. Note: Other AWS CLI operations " +
67
- "will use your AWS CLI default region. If needed, set it to match " +
68
- "this GovCloud region (us-gov-west-1 or us-gov-east-1).");
69
- }
70
- let assertionConsumerServiceURL = AWS_SAML_ENDPOINT;
71
- if (profile.region && profile.region.startsWith("us-gov")) {
72
- assertionConsumerServiceURL = AWS_GOV_SAML_ENDPOINT;
60
+ async loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu, incognito = false, credentialProcess = false) {
61
+ const originalConsoleLog = console.log;
62
+ const effectiveNoPrompt = credentialProcess ? true : noPrompt;
63
+ try {
64
+ if (credentialProcess) {
65
+ console.log = (...args) => console.error(...args);
66
+ }
67
+ let headless, cliProxy;
68
+ if (mode === "cli") {
69
+ headless = true;
70
+ cliProxy = true;
71
+ }
72
+ else if (mode === "gui") {
73
+ headless = false;
74
+ cliProxy = false;
75
+ }
76
+ else if (mode === "debug") {
77
+ headless = false;
78
+ cliProxy = true;
79
+ }
80
+ else {
81
+ throw new CLIError_1.CLIError("Invalid mode");
82
+ }
83
+ const profile = await this._loadProfileAsync(profileName);
84
+ console.log(`Using AWS region ${profile.region || "(from AWS SDK defaults)"}`);
85
+ if (profile.region && profile.region.startsWith("us-gov")) {
86
+ console.warn("GovCloud region detected in profile. Note: Other AWS CLI operations " +
87
+ "will use your AWS CLI default region. If needed, set it to match " +
88
+ "this GovCloud region (us-gov-west-1 or us-gov-east-1).");
89
+ }
90
+ let assertionConsumerServiceURL = AWS_SAML_ENDPOINT;
91
+ if (profile.region && profile.region.startsWith("us-gov")) {
92
+ assertionConsumerServiceURL = AWS_GOV_SAML_ENDPOINT;
93
+ }
94
+ if (profile.region && profile.region.startsWith("cn-")) {
95
+ assertionConsumerServiceURL = AWS_CN_SAML_ENDPOINT;
96
+ }
97
+ console.log("Using AWS SAML endpoint", assertionConsumerServiceURL);
98
+ const loginUrl = await this._createLoginUrlAsync(profile.azure_app_id_uri, profile.azure_tenant_id, assertionConsumerServiceURL);
99
+ const allowSensitiveOutput = (0, sensitiveOutput_1.shouldAllowSensitiveOutput)();
100
+ const samlResponse = await this._performLoginAsync(loginUrl, headless, disableSandbox, cliProxy, effectiveNoPrompt, enableChromeNetworkService, profile.azure_default_username, profile.azure_default_password, enableChromeSeamlessSso, profile.azure_default_remember_me, noDisableExtensions, disableGpu, incognito, allowSensitiveOutput);
101
+ const roles = this._parseRolesFromSamlResponse(samlResponse);
102
+ const { role, durationHours } = await this._askUserForRoleAndDurationAsync(roles, effectiveNoPrompt, profile.azure_default_role_arn, profile.azure_default_duration_hours, credentialProcess ? "--credential-process" : "--no-prompt");
103
+ const credentials = await this._assumeRoleAsync(profileName, samlResponse, role, durationHours, awsNoVerifySsl, profile.region, !credentialProcess);
104
+ if (credentialProcess) {
105
+ if (!credentials) {
106
+ throw new CLIError_1.CLIError("Unable to retrieve credentials.");
107
+ }
108
+ originalConsoleLog(JSON.stringify({
109
+ Version: 1,
110
+ AccessKeyId: credentials.aws_access_key_id,
111
+ SecretAccessKey: credentials.aws_secret_access_key,
112
+ SessionToken: credentials.aws_session_token,
113
+ Expiration: credentials.aws_expiration,
114
+ }));
115
+ }
73
116
  }
74
- if (profile.region && profile.region.startsWith("cn-")) {
75
- assertionConsumerServiceURL = AWS_CN_SAML_ENDPOINT;
117
+ finally {
118
+ console.log = originalConsoleLog;
76
119
  }
77
- console.log("Using AWS SAML endpoint", assertionConsumerServiceURL);
78
- const loginUrl = await this._createLoginUrlAsync(profile.azure_app_id_uri, profile.azure_tenant_id, assertionConsumerServiceURL);
79
- 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, incognito);
80
- const roles = this._parseRolesFromSamlResponse(samlResponse);
81
- const { role, durationHours } = await this._askUserForRoleAndDurationAsync(roles, noPrompt, profile.azure_default_role_arn, profile.azure_default_duration_hours);
82
- await this._assumeRoleAsync(profileName, samlResponse, role, durationHours, awsNoVerifySsl, profile.region);
83
120
  },
84
121
  async loginAll(mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, forceRefresh, noDisableExtensions, disableGpu, incognito = false) {
85
- const profiles = await awsConfig_1.awsConfig.getAllProfileNames();
122
+ const profiles = await awsConfig_1.awsConfig.getAz2awsProfileNames();
86
123
  for (const profile of profiles) {
87
124
  debug(`Check if profile ${profile} is expired or is about to expire`);
88
125
  if (!forceRefresh &&
@@ -117,12 +154,22 @@ exports.login = {
117
154
  }
118
155
  }
119
156
  debug("Environment");
120
- debug({
121
- ...env,
122
- azure_default_password: "xxxxxxxxxx",
123
- });
157
+ debug(this._redactProfileForDebug(env));
124
158
  return env;
125
159
  },
160
+ _redactProfileForDebug(env) {
161
+ return Object.fromEntries(Object.entries(env).map(([key, value]) => [
162
+ key,
163
+ key === "azure_default_duration_hours" ? value : REDACTED,
164
+ ]));
165
+ },
166
+ _redactArnForDebug(arn) {
167
+ const match = arn.match(/^(arn:[^:]+:iam::)[^:]+:(.+?\/).+$/);
168
+ if (!match) {
169
+ return arn;
170
+ }
171
+ return `${match[1]}${REDACTED}:${match[2]}${REDACTED}`;
172
+ },
126
173
  // Load the profile
127
174
  async _loadProfileAsync(profileName) {
128
175
  const profile = await awsConfig_1.awsConfig.getProfileConfigAsync(profileName);
@@ -156,7 +203,7 @@ exports.login = {
156
203
  <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"></samlp:NameIDPolicy>
157
204
  </samlp:AuthnRequest>
158
205
  `;
159
- debug("Generated SAML request", samlRequest);
206
+ debug("Generated SAML request");
160
207
  debug("Deflating SAML");
161
208
  return new Promise((resolve, reject) => {
162
209
  zlib_1.default.deflateRaw(samlRequest, (err, samlBuffer) => {
@@ -166,7 +213,7 @@ exports.login = {
166
213
  debug("Encoding SAML in base64");
167
214
  const samlBase64 = samlBuffer.toString("base64");
168
215
  const url = `https://login.microsoftonline.com/${tenantId}/saml2?SAMLRequest=${encodeURIComponent(samlBase64)}`;
169
- debug("Created login URL", url);
216
+ debug("Created login URL", (0, sensitiveOutput_1.redactUrlForLogs)(url));
170
217
  return resolve(url);
171
218
  });
172
219
  });
@@ -189,7 +236,7 @@ exports.login = {
189
236
  * @returns {Promise.<string>} The SAML response.
190
237
  * @private
191
238
  */
192
- async _performLoginAsync(url, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, defaultUsername, defaultPassword, enableChromeSeamlessSso, rememberMe, noDisableExtensions, disableGpu, incognito = false) {
239
+ async _performLoginAsync(url, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, defaultUsername, defaultPassword, enableChromeSeamlessSso, rememberMe, noDisableExtensions, disableGpu, incognito = false, allowSensitiveStateOutput = true) {
193
240
  debug("Loading login page in Chrome");
194
241
  let browser;
195
242
  const useRememberMe = rememberMe && !incognito;
@@ -248,9 +295,14 @@ exports.login = {
248
295
  e.name === "TargetCloseError" &&
249
296
  useRememberMe &&
250
297
  !paths_1.paths.userDataDir) {
251
- debug(`Browser launch failed with TargetCloseError. Resetting profile at ${paths_1.paths.chromium}`);
298
+ debug("Browser launch failed with TargetCloseError. Resetting managed browser profile.");
252
299
  console.warn("Browser profile appears incompatible. Resetting profile data and retrying...");
253
- await promises_2.default.rm(paths_1.paths.chromium, { recursive: true, force: true });
300
+ await promises_2.default.rm(paths_1.paths.chromium, {
301
+ recursive: true,
302
+ force: true,
303
+ maxRetries: 5,
304
+ retryDelay: 100,
305
+ });
254
306
  await promises_2.default.mkdir(paths_1.paths.chromium, { recursive: true });
255
307
  browser = await puppeteer_1.default.launch(launchParams);
256
308
  }
@@ -283,29 +335,27 @@ exports.login = {
283
335
  const samlResponsePromise = new Promise((resolve) => {
284
336
  page.on("request", (req) => {
285
337
  const reqURL = req.url();
286
- debug(`Request: ${reqURL}`);
338
+ const redactedURL = (0, sensitiveOutput_1.redactUrlForLogs)(reqURL);
339
+ debug(`Request: ${redactedURL}`);
287
340
  if (reqURL === AWS_SAML_ENDPOINT ||
288
341
  reqURL === AWS_GOV_SAML_ENDPOINT ||
289
342
  reqURL === AWS_CN_SAML_ENDPOINT) {
290
343
  resolve(undefined);
291
344
  samlResponseData = req.postData();
292
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
293
- req.respond({
345
+ handleBackgroundPromise(req.respond({
294
346
  status: 200,
295
347
  contentType: "text/plain",
296
348
  headers: {},
297
349
  body: "",
298
- });
350
+ }), `Failed to respond to intercepted request ${redactedURL}`);
299
351
  if (browser) {
300
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
301
- browser.close();
352
+ handleBackgroundPromise(browser.close(), "Failed to close browser after receiving SAML response");
302
353
  }
303
354
  browser = undefined;
304
355
  debug(`Received SAML response, browser closed`);
305
356
  }
306
357
  else {
307
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
308
- req.continue();
358
+ handleBackgroundPromise(req.continue(), `Failed to continue intercepted request ${redactedURL}`);
309
359
  }
310
360
  });
311
361
  });
@@ -325,13 +375,12 @@ exports.login = {
325
375
  if (err instanceof Error) {
326
376
  // An error will be thrown if you're still logged in cause the page.goto ot waitForNavigation
327
377
  // will be a redirect to AWS. That's usually OK
328
- debug(`Error occurred during loading the first page: ${err.message}`);
378
+ debug(`Error occurred during loading the first page: ${(0, sensitiveOutput_1.formatDebugErrorMessage)(err)}`);
329
379
  }
330
380
  }
331
381
  if (cliProxy) {
332
382
  let totalUnrecognizedDelay = 0;
333
- // eslint-disable-next-line no-constant-condition
334
- while (true) {
383
+ for (;;) {
335
384
  if (samlResponseData)
336
385
  break;
337
386
  let foundState = false;
@@ -345,7 +394,7 @@ exports.login = {
345
394
  if (err instanceof Error) {
346
395
  // An error can be thrown if the page isn't in a good state.
347
396
  // If one occurs, try again after another loop.
348
- debug(`Error when running state "${state.name}". ${err.toString()}. Retrying...`);
397
+ debug(`Error when running state "${state.name}". ${(0, sensitiveOutput_1.formatDebugErrorMessage)(err)}. Retrying...`);
349
398
  }
350
399
  break;
351
400
  }
@@ -354,7 +403,7 @@ exports.login = {
354
403
  debug(`Found state: ${state.name}`);
355
404
  await Promise.race([
356
405
  samlResponsePromise,
357
- state.handler(page, selected, noPrompt, defaultUsername, defaultPassword, useRememberMe),
406
+ state.handler(page, selected, noPrompt, defaultUsername, defaultPassword, useRememberMe, allowSensitiveStateOutput),
358
407
  ]);
359
408
  debug(`Finished state: ${state.name}`);
360
409
  break;
@@ -366,6 +415,9 @@ exports.login = {
366
415
  else {
367
416
  debug("State not recognized!");
368
417
  if (totalUnrecognizedDelay > MAX_UNRECOGNIZED_PAGE_DELAY) {
418
+ if (!allowSensitiveStateOutput) {
419
+ throw new CLIError_1.CLIError("Unable to recognize page state in a shared environment. Re-run locally with --mode=debug to capture a screenshot.");
420
+ }
369
421
  const path = "az2aws-unrecognized-state.png";
370
422
  await page.screenshot({ path });
371
423
  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`);
@@ -383,13 +435,15 @@ exports.login = {
383
435
  throw new Error("SAML response not found");
384
436
  }
385
437
  const samlResponse = querystring_1.default.parse(samlResponseData).SAMLResponse;
386
- debug("Found SAML response", samlResponse);
387
438
  if (!samlResponse) {
388
439
  throw new Error("SAML response not found");
389
440
  }
390
441
  else if (Array.isArray(samlResponse)) {
391
442
  throw new Error("SAML can't be an array");
392
443
  }
444
+ debug("Found SAML response", {
445
+ base64Length: samlResponse.length,
446
+ });
393
447
  return samlResponse;
394
448
  }
395
449
  finally {
@@ -407,16 +461,16 @@ exports.login = {
407
461
  _parseRolesFromSamlResponse(assertion) {
408
462
  debug("Converting assertion from base64 to UTF-8");
409
463
  const samlText = Buffer.from(assertion, "base64").toString("utf8");
410
- debug("Converted", samlText);
464
+ debug("Converted assertion from base64 to UTF-8", {
465
+ xmlLength: samlText.length,
466
+ });
411
467
  debug("Parsing SAML XML");
412
468
  const saml = (0, cheerio_1.load)(samlText, { xmlMode: true });
413
469
  debug("Looking for role SAML attribute");
414
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
415
- const roles = saml("Attribute[Name='https://aws.amazon.com/SAML/Attributes/Role']>AttributeValue")
416
- .map(function () {
417
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
418
- // @ts-ignore
419
- const roleAndPrincipal = saml(this).text();
470
+ const roleSelection = saml("Attribute[Name='https://aws.amazon.com/SAML/Attributes/Role']>AttributeValue");
471
+ const roleNodes = roleSelection.toArray();
472
+ const roles = roleNodes.map((roleNode) => {
473
+ const roleAndPrincipal = saml(roleNode).text();
420
474
  const parts = roleAndPrincipal.split(",");
421
475
  // Role / Principal claims may be in either order
422
476
  const [roleIdx, principalIdx] = parts[0].includes(":role/")
@@ -425,9 +479,11 @@ exports.login = {
425
479
  const roleArn = parts[roleIdx].trim();
426
480
  const principalArn = parts[principalIdx].trim();
427
481
  return { roleArn, principalArn };
428
- })
429
- .get();
430
- debug("Found roles", roles);
482
+ });
483
+ debug("Found roles", roles.map((role) => ({
484
+ roleArn: this._redactArnForDebug(role.roleArn),
485
+ principalArn: this._redactArnForDebug(role.principalArn),
486
+ })));
431
487
  return roles;
432
488
  },
433
489
  /**
@@ -436,21 +492,15 @@ exports.login = {
436
492
  * @param {bool} [noPrompt] - Enable skipping of user prompting
437
493
  * @param {string} [defaultRoleArn] - The default role ARN
438
494
  * @param {number} [defaultDurationHours] - The default session duration in hours
495
+ * @param {string} [nonInteractiveModeLabel] - CLI flag label to reference in
496
+ * non-interactive errors
439
497
  * @returns {Promise.<{role: string, durationHours: number}>} The selected role and duration
440
498
  * @private
441
499
  */
442
- async _askUserForRoleAndDurationAsync(roles, noPrompt, defaultRoleArn, defaultDurationHours) {
500
+ async _askUserForRoleAndDurationAsync(roles, noPrompt, defaultRoleArn, defaultDurationHours, nonInteractiveModeLabel = "--no-prompt") {
501
+ var _a;
443
502
  let role;
444
- let durationHours = 1;
445
- if (defaultDurationHours) {
446
- const parsedDuration = parseInt(defaultDurationHours, 10);
447
- if (!Number.isNaN(parsedDuration) &&
448
- parsedDuration > 0 &&
449
- parsedDuration <= 12) {
450
- durationHours = parsedDuration;
451
- }
452
- }
453
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
503
+ let durationHours = (_a = (0, sessionDuration_1.parseSessionDurationHours)(defaultDurationHours)) !== null && _a !== void 0 ? _a : 1;
454
504
  const questions = [];
455
505
  if (roles.length === 0) {
456
506
  throw new CLIError_1.CLIError("No roles found in SAML response.");
@@ -462,11 +512,11 @@ exports.login = {
462
512
  else {
463
513
  if (noPrompt) {
464
514
  if (!defaultRoleArn) {
465
- throw new CLIError_1.CLIError("--no-prompt requires azure_default_role_arn when multiple roles are available.");
515
+ throw new CLIError_1.CLIError(`${nonInteractiveModeLabel} requires azure_default_role_arn when multiple roles are available.`);
466
516
  }
467
517
  role = roles.find((r) => r.roleArn === defaultRoleArn);
468
518
  if (!role) {
469
- throw new CLIError_1.CLIError(`Default role ARN '${defaultRoleArn}' was not found in the SAML response.`);
519
+ throw new CLIError_1.CLIError("Configured default role ARN was not found in the SAML response.");
470
520
  }
471
521
  debug("Valid role found. No need to ask.");
472
522
  }
@@ -494,23 +544,23 @@ exports.login = {
494
544
  name: "durationHours",
495
545
  message: "Session Duration Hours (up to 12):",
496
546
  type: "input",
497
- default: defaultDurationHours || 1,
498
- validate: (input) => {
499
- const num = Number(input);
500
- if (num > 0 && num <= 12)
501
- return true;
502
- return "Duration hours must be between 1 and 12";
503
- },
547
+ default: String(durationHours),
548
+ validate: sessionDuration_1.validateSessionDurationHours,
504
549
  });
505
550
  }
506
551
  // Don't prompt for questions if not needed, an unneeded TTYWRAP prevents node from exiting when
507
552
  // user is logged in and using multiple profiles --all-profiles and --no-prompt
508
553
  if (questions.length > 0) {
509
554
  const answers = await inquirer_1.default.prompt(questions);
510
- if (!role)
555
+ if (!role && answers.role) {
511
556
  role = roles.find((r) => r.roleArn === answers.role);
557
+ }
512
558
  if (answers.durationHours) {
513
- durationHours = parseInt(answers.durationHours, 10);
559
+ const parsedDurationHours = (0, sessionDuration_1.parseSessionDurationHours)(answers.durationHours);
560
+ if (parsedDurationHours === null) {
561
+ throw new CLIError_1.CLIError(sessionDuration_1.sessionDurationHoursValidationMessage);
562
+ }
563
+ durationHours = parsedDurationHours;
514
564
  }
515
565
  }
516
566
  if (!role) {
@@ -522,16 +572,19 @@ exports.login = {
522
572
  * Assume the role.
523
573
  * @param {string} profileName - The profile name
524
574
  * @param {string} assertion - The SAML assertion
525
- * @param {string} role - The role to assume
575
+ * @param {Role} role - The role to assume
526
576
  * @param {number} durationHours - The session duration in hours
527
- * @param {bool} awsNoVerifySsl - Whether to have the AWS CLI verify SSL
577
+ * @param {boolean} awsNoVerifySsl - Whether the AWS SDK STS client should
578
+ * disable TLS certificate verification
528
579
  * @param {string} region - AWS region, if specified
529
- * @returns {Promise} A promise
580
+ * @param {boolean} writeProfile - Whether to persist the credentials to the
581
+ * AWS shared credentials file
582
+ * @returns {Promise<AwsCredentials | undefined>} Retrieved credentials, or
583
+ * undefined when STS does not return them
530
584
  * @private
531
585
  */
532
- async _assumeRoleAsync(profileName, assertion, role, durationHours, awsNoVerifySsl, region) {
533
- var _a, _b, _c, _d, _e;
534
- console.log(`Assuming role ${role.roleArn} in region ${region}...`);
586
+ async _assumeRoleAsync(profileName, assertion, role, durationHours, awsNoVerifySsl, region, writeProfile = true) {
587
+ console.log(`Assuming selected role in region ${region}...`);
535
588
  let stsOptions = {};
536
589
  if (awsNoVerifySsl) {
537
590
  console.warn("WARNING: SSL certificate verification is disabled. " +
@@ -573,13 +626,26 @@ exports.login = {
573
626
  });
574
627
  if (!res.Credentials) {
575
628
  debug("Unable to get security credentials from AWS");
576
- return;
629
+ return undefined;
577
630
  }
578
- await awsConfig_1.awsConfig.setProfileCredentialsAsync(profileName, {
579
- aws_access_key_id: (_a = res.Credentials.AccessKeyId) !== null && _a !== void 0 ? _a : "",
580
- aws_secret_access_key: (_b = res.Credentials.SecretAccessKey) !== null && _b !== void 0 ? _b : "",
581
- aws_session_token: (_c = res.Credentials.SessionToken) !== null && _c !== void 0 ? _c : "",
582
- aws_expiration: (_e = (_d = res.Credentials.Expiration) === null || _d === void 0 ? void 0 : _d.toISOString()) !== null && _e !== void 0 ? _e : "",
583
- });
631
+ if (!res.Credentials.AccessKeyId ||
632
+ !res.Credentials.SecretAccessKey ||
633
+ !res.Credentials.SessionToken ||
634
+ !res.Credentials.Expiration) {
635
+ debug("Received incomplete credentials from AWS");
636
+ throw new CLIError_1.CLIError("AWS returned incomplete credentials. One or more required fields " +
637
+ "(AccessKeyId, SecretAccessKey, SessionToken, Expiration) are missing.");
638
+ }
639
+ const credentials = {
640
+ aws_access_key_id: res.Credentials.AccessKeyId,
641
+ aws_secret_access_key: res.Credentials.SecretAccessKey,
642
+ aws_session_token: res.Credentials.SessionToken,
643
+ aws_expiration: res.Credentials.Expiration.toISOString(),
644
+ };
645
+ if (writeProfile) {
646
+ await awsConfig_1.awsConfig.setProfileCredentialsAsync(profileName, credentials);
647
+ printCredentialsReadyMessage(profileName, credentials);
648
+ }
649
+ return credentials;
584
650
  },
585
651
  };