az2aws 1.6.2 → 1.7.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/.gitattributes +1 -0
- package/CHANGELOG.md +18 -0
- package/CONTRIBUTING.md +20 -0
- package/README.md +42 -2
- package/lib/awsConfig.js +103 -10
- package/lib/configureProfileAsync.js +10 -8
- package/lib/index.js +16 -3
- package/lib/login.js +157 -102
- package/lib/loginStates.js +64 -81
- package/lib/sensitiveOutput.js +85 -0
- package/lib/sessionDuration.js +27 -0
- package/lib/updateNotifier.js +25 -11
- package/lib/validateCliOptions.js +12 -0
- package/package.json +4 -1
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,48 +41,77 @@ 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 handleBackgroundPromise(promise, description) {
|
|
45
|
+
void promise.catch((error) => {
|
|
46
|
+
const message = (0, sensitiveOutput_1.formatDebugErrorMessage)(error);
|
|
47
|
+
debug(`${description}: ${message}`);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
41
50
|
exports.login = {
|
|
42
51
|
async _createHttpsProxyAgentAsync(proxyUrl, proxyOptions) {
|
|
43
52
|
const { HttpsProxyAgent } = await importHttpsProxyAgent();
|
|
44
53
|
return new HttpsProxyAgent(proxyUrl, proxyOptions);
|
|
45
54
|
},
|
|
46
|
-
async loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu, incognito = false) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
headless
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
55
|
+
async loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu, incognito = false, credentialProcess = false) {
|
|
56
|
+
const originalConsoleLog = console.log;
|
|
57
|
+
const effectiveNoPrompt = credentialProcess ? true : noPrompt;
|
|
58
|
+
try {
|
|
59
|
+
if (credentialProcess) {
|
|
60
|
+
console.log = (...args) => console.error(...args);
|
|
61
|
+
}
|
|
62
|
+
let headless, cliProxy;
|
|
63
|
+
if (mode === "cli") {
|
|
64
|
+
headless = true;
|
|
65
|
+
cliProxy = true;
|
|
66
|
+
}
|
|
67
|
+
else if (mode === "gui") {
|
|
68
|
+
headless = false;
|
|
69
|
+
cliProxy = false;
|
|
70
|
+
}
|
|
71
|
+
else if (mode === "debug") {
|
|
72
|
+
headless = false;
|
|
73
|
+
cliProxy = true;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
throw new CLIError_1.CLIError("Invalid mode");
|
|
77
|
+
}
|
|
78
|
+
const profile = await this._loadProfileAsync(profileName);
|
|
79
|
+
console.log(`Using AWS region ${profile.region || "(from AWS SDK defaults)"}`);
|
|
80
|
+
if (profile.region && profile.region.startsWith("us-gov")) {
|
|
81
|
+
console.warn("GovCloud region detected in profile. Note: Other AWS CLI operations " +
|
|
82
|
+
"will use your AWS CLI default region. If needed, set it to match " +
|
|
83
|
+
"this GovCloud region (us-gov-west-1 or us-gov-east-1).");
|
|
84
|
+
}
|
|
85
|
+
let assertionConsumerServiceURL = AWS_SAML_ENDPOINT;
|
|
86
|
+
if (profile.region && profile.region.startsWith("us-gov")) {
|
|
87
|
+
assertionConsumerServiceURL = AWS_GOV_SAML_ENDPOINT;
|
|
88
|
+
}
|
|
89
|
+
if (profile.region && profile.region.startsWith("cn-")) {
|
|
90
|
+
assertionConsumerServiceURL = AWS_CN_SAML_ENDPOINT;
|
|
91
|
+
}
|
|
92
|
+
console.log("Using AWS SAML endpoint", assertionConsumerServiceURL);
|
|
93
|
+
const loginUrl = await this._createLoginUrlAsync(profile.azure_app_id_uri, profile.azure_tenant_id, assertionConsumerServiceURL);
|
|
94
|
+
const allowSensitiveOutput = (0, sensitiveOutput_1.shouldAllowSensitiveOutput)();
|
|
95
|
+
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);
|
|
96
|
+
const roles = this._parseRolesFromSamlResponse(samlResponse);
|
|
97
|
+
const { role, durationHours } = await this._askUserForRoleAndDurationAsync(roles, effectiveNoPrompt, profile.azure_default_role_arn, profile.azure_default_duration_hours, credentialProcess ? "--credential-process" : "--no-prompt");
|
|
98
|
+
const credentials = await this._assumeRoleAsync(profileName, samlResponse, role, durationHours, awsNoVerifySsl, profile.region, !credentialProcess);
|
|
99
|
+
if (credentialProcess) {
|
|
100
|
+
if (!credentials) {
|
|
101
|
+
throw new CLIError_1.CLIError("Unable to retrieve credentials.");
|
|
102
|
+
}
|
|
103
|
+
originalConsoleLog(JSON.stringify({
|
|
104
|
+
Version: 1,
|
|
105
|
+
AccessKeyId: credentials.aws_access_key_id,
|
|
106
|
+
SecretAccessKey: credentials.aws_secret_access_key,
|
|
107
|
+
SessionToken: credentials.aws_session_token,
|
|
108
|
+
Expiration: credentials.aws_expiration,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
73
111
|
}
|
|
74
|
-
|
|
75
|
-
|
|
112
|
+
finally {
|
|
113
|
+
console.log = originalConsoleLog;
|
|
76
114
|
}
|
|
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
115
|
},
|
|
84
116
|
async loginAll(mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, forceRefresh, noDisableExtensions, disableGpu, incognito = false) {
|
|
85
117
|
const profiles = await awsConfig_1.awsConfig.getAllProfileNames();
|
|
@@ -117,12 +149,22 @@ exports.login = {
|
|
|
117
149
|
}
|
|
118
150
|
}
|
|
119
151
|
debug("Environment");
|
|
120
|
-
debug(
|
|
121
|
-
...env,
|
|
122
|
-
azure_default_password: "xxxxxxxxxx",
|
|
123
|
-
});
|
|
152
|
+
debug(this._redactProfileForDebug(env));
|
|
124
153
|
return env;
|
|
125
154
|
},
|
|
155
|
+
_redactProfileForDebug(env) {
|
|
156
|
+
return Object.fromEntries(Object.entries(env).map(([key, value]) => [
|
|
157
|
+
key,
|
|
158
|
+
key === "azure_default_duration_hours" ? value : REDACTED,
|
|
159
|
+
]));
|
|
160
|
+
},
|
|
161
|
+
_redactArnForDebug(arn) {
|
|
162
|
+
const match = arn.match(/^(arn:[^:]+:iam::)[^:]+:(.+?\/).+$/);
|
|
163
|
+
if (!match) {
|
|
164
|
+
return arn;
|
|
165
|
+
}
|
|
166
|
+
return `${match[1]}${REDACTED}:${match[2]}${REDACTED}`;
|
|
167
|
+
},
|
|
126
168
|
// Load the profile
|
|
127
169
|
async _loadProfileAsync(profileName) {
|
|
128
170
|
const profile = await awsConfig_1.awsConfig.getProfileConfigAsync(profileName);
|
|
@@ -156,7 +198,7 @@ exports.login = {
|
|
|
156
198
|
<samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"></samlp:NameIDPolicy>
|
|
157
199
|
</samlp:AuthnRequest>
|
|
158
200
|
`;
|
|
159
|
-
debug("Generated SAML request"
|
|
201
|
+
debug("Generated SAML request");
|
|
160
202
|
debug("Deflating SAML");
|
|
161
203
|
return new Promise((resolve, reject) => {
|
|
162
204
|
zlib_1.default.deflateRaw(samlRequest, (err, samlBuffer) => {
|
|
@@ -166,7 +208,7 @@ exports.login = {
|
|
|
166
208
|
debug("Encoding SAML in base64");
|
|
167
209
|
const samlBase64 = samlBuffer.toString("base64");
|
|
168
210
|
const url = `https://login.microsoftonline.com/${tenantId}/saml2?SAMLRequest=${encodeURIComponent(samlBase64)}`;
|
|
169
|
-
debug("Created login URL", url);
|
|
211
|
+
debug("Created login URL", (0, sensitiveOutput_1.redactUrlForLogs)(url));
|
|
170
212
|
return resolve(url);
|
|
171
213
|
});
|
|
172
214
|
});
|
|
@@ -189,7 +231,7 @@ exports.login = {
|
|
|
189
231
|
* @returns {Promise.<string>} The SAML response.
|
|
190
232
|
* @private
|
|
191
233
|
*/
|
|
192
|
-
async _performLoginAsync(url, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, defaultUsername, defaultPassword, enableChromeSeamlessSso, rememberMe, noDisableExtensions, disableGpu, incognito = false) {
|
|
234
|
+
async _performLoginAsync(url, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, defaultUsername, defaultPassword, enableChromeSeamlessSso, rememberMe, noDisableExtensions, disableGpu, incognito = false, allowSensitiveStateOutput = true) {
|
|
193
235
|
debug("Loading login page in Chrome");
|
|
194
236
|
let browser;
|
|
195
237
|
const useRememberMe = rememberMe && !incognito;
|
|
@@ -248,7 +290,7 @@ exports.login = {
|
|
|
248
290
|
e.name === "TargetCloseError" &&
|
|
249
291
|
useRememberMe &&
|
|
250
292
|
!paths_1.paths.userDataDir) {
|
|
251
|
-
debug(
|
|
293
|
+
debug("Browser launch failed with TargetCloseError. Resetting managed browser profile.");
|
|
252
294
|
console.warn("Browser profile appears incompatible. Resetting profile data and retrying...");
|
|
253
295
|
await promises_2.default.rm(paths_1.paths.chromium, { recursive: true, force: true });
|
|
254
296
|
await promises_2.default.mkdir(paths_1.paths.chromium, { recursive: true });
|
|
@@ -283,29 +325,27 @@ exports.login = {
|
|
|
283
325
|
const samlResponsePromise = new Promise((resolve) => {
|
|
284
326
|
page.on("request", (req) => {
|
|
285
327
|
const reqURL = req.url();
|
|
286
|
-
|
|
328
|
+
const redactedURL = (0, sensitiveOutput_1.redactUrlForLogs)(reqURL);
|
|
329
|
+
debug(`Request: ${redactedURL}`);
|
|
287
330
|
if (reqURL === AWS_SAML_ENDPOINT ||
|
|
288
331
|
reqURL === AWS_GOV_SAML_ENDPOINT ||
|
|
289
332
|
reqURL === AWS_CN_SAML_ENDPOINT) {
|
|
290
333
|
resolve(undefined);
|
|
291
334
|
samlResponseData = req.postData();
|
|
292
|
-
|
|
293
|
-
req.respond({
|
|
335
|
+
handleBackgroundPromise(req.respond({
|
|
294
336
|
status: 200,
|
|
295
337
|
contentType: "text/plain",
|
|
296
338
|
headers: {},
|
|
297
339
|
body: "",
|
|
298
|
-
});
|
|
340
|
+
}), `Failed to respond to intercepted request ${redactedURL}`);
|
|
299
341
|
if (browser) {
|
|
300
|
-
|
|
301
|
-
browser.close();
|
|
342
|
+
handleBackgroundPromise(browser.close(), "Failed to close browser after receiving SAML response");
|
|
302
343
|
}
|
|
303
344
|
browser = undefined;
|
|
304
345
|
debug(`Received SAML response, browser closed`);
|
|
305
346
|
}
|
|
306
347
|
else {
|
|
307
|
-
|
|
308
|
-
req.continue();
|
|
348
|
+
handleBackgroundPromise(req.continue(), `Failed to continue intercepted request ${redactedURL}`);
|
|
309
349
|
}
|
|
310
350
|
});
|
|
311
351
|
});
|
|
@@ -325,13 +365,12 @@ exports.login = {
|
|
|
325
365
|
if (err instanceof Error) {
|
|
326
366
|
// An error will be thrown if you're still logged in cause the page.goto ot waitForNavigation
|
|
327
367
|
// will be a redirect to AWS. That's usually OK
|
|
328
|
-
debug(`Error occurred during loading the first page: ${err
|
|
368
|
+
debug(`Error occurred during loading the first page: ${(0, sensitiveOutput_1.formatDebugErrorMessage)(err)}`);
|
|
329
369
|
}
|
|
330
370
|
}
|
|
331
371
|
if (cliProxy) {
|
|
332
372
|
let totalUnrecognizedDelay = 0;
|
|
333
|
-
|
|
334
|
-
while (true) {
|
|
373
|
+
for (;;) {
|
|
335
374
|
if (samlResponseData)
|
|
336
375
|
break;
|
|
337
376
|
let foundState = false;
|
|
@@ -345,7 +384,7 @@ exports.login = {
|
|
|
345
384
|
if (err instanceof Error) {
|
|
346
385
|
// An error can be thrown if the page isn't in a good state.
|
|
347
386
|
// If one occurs, try again after another loop.
|
|
348
|
-
debug(`Error when running state "${state.name}". ${
|
|
387
|
+
debug(`Error when running state "${state.name}". ${(0, sensitiveOutput_1.formatDebugErrorMessage)(err)}. Retrying...`);
|
|
349
388
|
}
|
|
350
389
|
break;
|
|
351
390
|
}
|
|
@@ -354,7 +393,7 @@ exports.login = {
|
|
|
354
393
|
debug(`Found state: ${state.name}`);
|
|
355
394
|
await Promise.race([
|
|
356
395
|
samlResponsePromise,
|
|
357
|
-
state.handler(page, selected, noPrompt, defaultUsername, defaultPassword, useRememberMe),
|
|
396
|
+
state.handler(page, selected, noPrompt, defaultUsername, defaultPassword, useRememberMe, allowSensitiveStateOutput),
|
|
358
397
|
]);
|
|
359
398
|
debug(`Finished state: ${state.name}`);
|
|
360
399
|
break;
|
|
@@ -366,6 +405,9 @@ exports.login = {
|
|
|
366
405
|
else {
|
|
367
406
|
debug("State not recognized!");
|
|
368
407
|
if (totalUnrecognizedDelay > MAX_UNRECOGNIZED_PAGE_DELAY) {
|
|
408
|
+
if (!allowSensitiveStateOutput) {
|
|
409
|
+
throw new CLIError_1.CLIError("Unable to recognize page state in a shared environment. Re-run locally with --mode=debug to capture a screenshot.");
|
|
410
|
+
}
|
|
369
411
|
const path = "az2aws-unrecognized-state.png";
|
|
370
412
|
await page.screenshot({ path });
|
|
371
413
|
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 +425,15 @@ exports.login = {
|
|
|
383
425
|
throw new Error("SAML response not found");
|
|
384
426
|
}
|
|
385
427
|
const samlResponse = querystring_1.default.parse(samlResponseData).SAMLResponse;
|
|
386
|
-
debug("Found SAML response", samlResponse);
|
|
387
428
|
if (!samlResponse) {
|
|
388
429
|
throw new Error("SAML response not found");
|
|
389
430
|
}
|
|
390
431
|
else if (Array.isArray(samlResponse)) {
|
|
391
432
|
throw new Error("SAML can't be an array");
|
|
392
433
|
}
|
|
434
|
+
debug("Found SAML response", {
|
|
435
|
+
base64Length: samlResponse.length,
|
|
436
|
+
});
|
|
393
437
|
return samlResponse;
|
|
394
438
|
}
|
|
395
439
|
finally {
|
|
@@ -407,16 +451,16 @@ exports.login = {
|
|
|
407
451
|
_parseRolesFromSamlResponse(assertion) {
|
|
408
452
|
debug("Converting assertion from base64 to UTF-8");
|
|
409
453
|
const samlText = Buffer.from(assertion, "base64").toString("utf8");
|
|
410
|
-
debug("Converted",
|
|
454
|
+
debug("Converted assertion from base64 to UTF-8", {
|
|
455
|
+
xmlLength: samlText.length,
|
|
456
|
+
});
|
|
411
457
|
debug("Parsing SAML XML");
|
|
412
458
|
const saml = (0, cheerio_1.load)(samlText, { xmlMode: true });
|
|
413
459
|
debug("Looking for role SAML attribute");
|
|
414
|
-
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
// @ts-ignore
|
|
419
|
-
const roleAndPrincipal = saml(this).text();
|
|
460
|
+
const roleSelection = saml("Attribute[Name='https://aws.amazon.com/SAML/Attributes/Role']>AttributeValue");
|
|
461
|
+
const roleNodes = roleSelection.toArray();
|
|
462
|
+
const roles = roleNodes.map((roleNode) => {
|
|
463
|
+
const roleAndPrincipal = saml(roleNode).text();
|
|
420
464
|
const parts = roleAndPrincipal.split(",");
|
|
421
465
|
// Role / Principal claims may be in either order
|
|
422
466
|
const [roleIdx, principalIdx] = parts[0].includes(":role/")
|
|
@@ -425,9 +469,11 @@ exports.login = {
|
|
|
425
469
|
const roleArn = parts[roleIdx].trim();
|
|
426
470
|
const principalArn = parts[principalIdx].trim();
|
|
427
471
|
return { roleArn, principalArn };
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
|
|
472
|
+
});
|
|
473
|
+
debug("Found roles", roles.map((role) => ({
|
|
474
|
+
roleArn: this._redactArnForDebug(role.roleArn),
|
|
475
|
+
principalArn: this._redactArnForDebug(role.principalArn),
|
|
476
|
+
})));
|
|
431
477
|
return roles;
|
|
432
478
|
},
|
|
433
479
|
/**
|
|
@@ -436,21 +482,15 @@ exports.login = {
|
|
|
436
482
|
* @param {bool} [noPrompt] - Enable skipping of user prompting
|
|
437
483
|
* @param {string} [defaultRoleArn] - The default role ARN
|
|
438
484
|
* @param {number} [defaultDurationHours] - The default session duration in hours
|
|
485
|
+
* @param {string} [nonInteractiveModeLabel] - CLI flag label to reference in
|
|
486
|
+
* non-interactive errors
|
|
439
487
|
* @returns {Promise.<{role: string, durationHours: number}>} The selected role and duration
|
|
440
488
|
* @private
|
|
441
489
|
*/
|
|
442
|
-
async _askUserForRoleAndDurationAsync(roles, noPrompt, defaultRoleArn, defaultDurationHours) {
|
|
490
|
+
async _askUserForRoleAndDurationAsync(roles, noPrompt, defaultRoleArn, defaultDurationHours, nonInteractiveModeLabel = "--no-prompt") {
|
|
491
|
+
var _a;
|
|
443
492
|
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
|
|
493
|
+
let durationHours = (_a = (0, sessionDuration_1.parseSessionDurationHours)(defaultDurationHours)) !== null && _a !== void 0 ? _a : 1;
|
|
454
494
|
const questions = [];
|
|
455
495
|
if (roles.length === 0) {
|
|
456
496
|
throw new CLIError_1.CLIError("No roles found in SAML response.");
|
|
@@ -462,11 +502,11 @@ exports.login = {
|
|
|
462
502
|
else {
|
|
463
503
|
if (noPrompt) {
|
|
464
504
|
if (!defaultRoleArn) {
|
|
465
|
-
throw new CLIError_1.CLIError(
|
|
505
|
+
throw new CLIError_1.CLIError(`${nonInteractiveModeLabel} requires azure_default_role_arn when multiple roles are available.`);
|
|
466
506
|
}
|
|
467
507
|
role = roles.find((r) => r.roleArn === defaultRoleArn);
|
|
468
508
|
if (!role) {
|
|
469
|
-
throw new CLIError_1.CLIError(
|
|
509
|
+
throw new CLIError_1.CLIError("Configured default role ARN was not found in the SAML response.");
|
|
470
510
|
}
|
|
471
511
|
debug("Valid role found. No need to ask.");
|
|
472
512
|
}
|
|
@@ -494,23 +534,23 @@ exports.login = {
|
|
|
494
534
|
name: "durationHours",
|
|
495
535
|
message: "Session Duration Hours (up to 12):",
|
|
496
536
|
type: "input",
|
|
497
|
-
default:
|
|
498
|
-
validate:
|
|
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
|
-
},
|
|
537
|
+
default: String(durationHours),
|
|
538
|
+
validate: sessionDuration_1.validateSessionDurationHours,
|
|
504
539
|
});
|
|
505
540
|
}
|
|
506
541
|
// Don't prompt for questions if not needed, an unneeded TTYWRAP prevents node from exiting when
|
|
507
542
|
// user is logged in and using multiple profiles --all-profiles and --no-prompt
|
|
508
543
|
if (questions.length > 0) {
|
|
509
544
|
const answers = await inquirer_1.default.prompt(questions);
|
|
510
|
-
if (!role)
|
|
545
|
+
if (!role && answers.role) {
|
|
511
546
|
role = roles.find((r) => r.roleArn === answers.role);
|
|
547
|
+
}
|
|
512
548
|
if (answers.durationHours) {
|
|
513
|
-
|
|
549
|
+
const parsedDurationHours = (0, sessionDuration_1.parseSessionDurationHours)(answers.durationHours);
|
|
550
|
+
if (parsedDurationHours === null) {
|
|
551
|
+
throw new CLIError_1.CLIError(sessionDuration_1.sessionDurationHoursValidationMessage);
|
|
552
|
+
}
|
|
553
|
+
durationHours = parsedDurationHours;
|
|
514
554
|
}
|
|
515
555
|
}
|
|
516
556
|
if (!role) {
|
|
@@ -522,16 +562,19 @@ exports.login = {
|
|
|
522
562
|
* Assume the role.
|
|
523
563
|
* @param {string} profileName - The profile name
|
|
524
564
|
* @param {string} assertion - The SAML assertion
|
|
525
|
-
* @param {
|
|
565
|
+
* @param {Role} role - The role to assume
|
|
526
566
|
* @param {number} durationHours - The session duration in hours
|
|
527
|
-
* @param {
|
|
567
|
+
* @param {boolean} awsNoVerifySsl - Whether the AWS SDK STS client should
|
|
568
|
+
* disable TLS certificate verification
|
|
528
569
|
* @param {string} region - AWS region, if specified
|
|
529
|
-
* @
|
|
570
|
+
* @param {boolean} writeProfile - Whether to persist the credentials to the
|
|
571
|
+
* AWS shared credentials file
|
|
572
|
+
* @returns {Promise<AwsCredentials | undefined>} Retrieved credentials, or
|
|
573
|
+
* undefined when STS does not return them
|
|
530
574
|
* @private
|
|
531
575
|
*/
|
|
532
|
-
async _assumeRoleAsync(profileName, assertion, role, durationHours, awsNoVerifySsl, region) {
|
|
533
|
-
|
|
534
|
-
console.log(`Assuming role ${role.roleArn} in region ${region}...`);
|
|
576
|
+
async _assumeRoleAsync(profileName, assertion, role, durationHours, awsNoVerifySsl, region, writeProfile = true) {
|
|
577
|
+
console.log(`Assuming selected role in region ${region}...`);
|
|
535
578
|
let stsOptions = {};
|
|
536
579
|
if (awsNoVerifySsl) {
|
|
537
580
|
console.warn("WARNING: SSL certificate verification is disabled. " +
|
|
@@ -573,13 +616,25 @@ exports.login = {
|
|
|
573
616
|
});
|
|
574
617
|
if (!res.Credentials) {
|
|
575
618
|
debug("Unable to get security credentials from AWS");
|
|
576
|
-
return;
|
|
619
|
+
return undefined;
|
|
577
620
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
621
|
+
if (!res.Credentials.AccessKeyId ||
|
|
622
|
+
!res.Credentials.SecretAccessKey ||
|
|
623
|
+
!res.Credentials.SessionToken ||
|
|
624
|
+
!res.Credentials.Expiration) {
|
|
625
|
+
debug("Received incomplete credentials from AWS");
|
|
626
|
+
throw new CLIError_1.CLIError("AWS returned incomplete credentials. One or more required fields " +
|
|
627
|
+
"(AccessKeyId, SecretAccessKey, SessionToken, Expiration) are missing.");
|
|
628
|
+
}
|
|
629
|
+
const credentials = {
|
|
630
|
+
aws_access_key_id: res.Credentials.AccessKeyId,
|
|
631
|
+
aws_secret_access_key: res.Credentials.SecretAccessKey,
|
|
632
|
+
aws_session_token: res.Credentials.SessionToken,
|
|
633
|
+
aws_expiration: res.Credentials.Expiration.toISOString(),
|
|
634
|
+
};
|
|
635
|
+
if (writeProfile) {
|
|
636
|
+
await awsConfig_1.awsConfig.setProfileCredentialsAsync(profileName, credentials);
|
|
637
|
+
}
|
|
638
|
+
return credentials;
|
|
584
639
|
},
|
|
585
640
|
};
|