@factiii/stack 0.1.182 → 0.1.184
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/dist/cli/fix.d.ts.map +1 -1
- package/dist/cli/fix.js +18 -0
- package/dist/cli/fix.js.map +1 -1
- package/dist/plugins/pipelines/aws/scanfix/credentials.d.ts +10 -8
- package/dist/plugins/pipelines/aws/scanfix/credentials.d.ts.map +1 -1
- package/dist/plugins/pipelines/aws/scanfix/credentials.js +267 -204
- package/dist/plugins/pipelines/aws/scanfix/credentials.js.map +1 -1
- package/dist/plugins/pipelines/aws/scanfix/iam.d.ts.map +1 -1
- package/dist/plugins/pipelines/aws/scanfix/iam.js +86 -15
- package/dist/plugins/pipelines/aws/scanfix/iam.js.map +1 -1
- package/dist/plugins/pipelines/aws/utils/aws-helpers.d.ts +19 -0
- package/dist/plugins/pipelines/aws/utils/aws-helpers.d.ts.map +1 -1
- package/dist/plugins/pipelines/aws/utils/aws-helpers.js +79 -14
- package/dist/plugins/pipelines/aws/utils/aws-helpers.js.map +1 -1
- package/dist/plugins/pipelines/factiii/scanfix/port-convention.js +6 -6
- package/dist/plugins/pipelines/factiii/scanfix/port-convention.js.map +1 -1
- package/dist/plugins/pipelines/factiii/scanfix/secrets.d.ts.map +1 -1
- package/dist/plugins/pipelines/factiii/scanfix/secrets.js +61 -0
- package/dist/plugins/pipelines/factiii/scanfix/secrets.js.map +1 -1
- package/dist/plugins/servers/amazon-linux/index.d.ts +1 -0
- package/dist/plugins/servers/amazon-linux/index.d.ts.map +1 -1
- package/dist/plugins/servers/mac/index.d.ts +1 -0
- package/dist/plugins/servers/mac/index.d.ts.map +1 -1
- package/dist/plugins/servers/ubuntu/index.d.ts +1 -0
- package/dist/plugins/servers/ubuntu/index.d.ts.map +1 -1
- package/dist/types/plugin.d.ts +6 -0
- package/dist/types/plugin.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* AWS Credential Fixes
|
|
4
4
|
*
|
|
5
|
-
* Handles AWS account setup,
|
|
6
|
-
* and region configuration checks.
|
|
5
|
+
* Handles AWS credential sync, account setup, and validation.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
* 1.
|
|
10
|
-
* 2.
|
|
11
|
-
* 3.
|
|
12
|
-
* 4.
|
|
13
|
-
* 5.
|
|
7
|
+
* Order matters — aws-credentials-sync MUST be first:
|
|
8
|
+
* 1. Sync vault → ~/.aws/credentials (ensure CLI uses the right project's key)
|
|
9
|
+
* 2. Bootstrap if no credentials exist at all
|
|
10
|
+
* 3. Region check
|
|
11
|
+
* 4. Vault has credentials stored
|
|
12
|
+
* 5. Credentials not expired
|
|
13
|
+
* 6. Prod provisioning credentials valid
|
|
14
|
+
*
|
|
15
|
+
* See .spec/aws-iam.md for the full IAM user model.
|
|
14
16
|
*/
|
|
15
17
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
18
|
if (k2 === undefined) k2 = k;
|
|
@@ -51,6 +53,16 @@ const fs = __importStar(require("fs"));
|
|
|
51
53
|
const path = __importStar(require("path"));
|
|
52
54
|
const os = __importStar(require("os"));
|
|
53
55
|
const aws_helpers_js_1 = require("../utils/aws-helpers.js");
|
|
56
|
+
/**
|
|
57
|
+
* Check if this project uses AWS (shared guard for all credential fixes)
|
|
58
|
+
*/
|
|
59
|
+
async function isAwsProject(config) {
|
|
60
|
+
if (config.aws)
|
|
61
|
+
return true;
|
|
62
|
+
const { extractEnvironments } = await Promise.resolve().then(() => __importStar(require('../../../../utils/config-helpers.js')));
|
|
63
|
+
const environments = extractEnvironments(config);
|
|
64
|
+
return Object.values(environments).some((e) => !!e.access_key_id || !!e.config);
|
|
65
|
+
}
|
|
54
66
|
/**
|
|
55
67
|
* Read the bootstrap policy JSON from the policies directory
|
|
56
68
|
*/
|
|
@@ -191,21 +203,245 @@ async function bootstrapAwsAccount(config) {
|
|
|
191
203
|
return false;
|
|
192
204
|
}
|
|
193
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Sync ~/.aws/credentials from vault, verifying it matches stack.yml.
|
|
208
|
+
* Returns true if credentials are synced and valid, false otherwise.
|
|
209
|
+
*/
|
|
210
|
+
async function _syncCredentials(config, rootDir) {
|
|
211
|
+
const awsConfig = (0, aws_helpers_js_1.getAwsConfig)(config);
|
|
212
|
+
const configKeyId = awsConfig.accessKeyId;
|
|
213
|
+
const localKeyId = (0, aws_helpers_js_1.getLocalAccessKeyId)();
|
|
214
|
+
const region = awsConfig.region || 'us-east-1';
|
|
215
|
+
if (localKeyId && localKeyId !== configKeyId) {
|
|
216
|
+
console.log('');
|
|
217
|
+
console.log(' ============================================================');
|
|
218
|
+
console.log(' AWS CREDENTIAL MISMATCH');
|
|
219
|
+
console.log(' ============================================================');
|
|
220
|
+
console.log(' stack.yml access_key_id: ' + configKeyId);
|
|
221
|
+
console.log(' ~/.aws/credentials key: ' + localKeyId);
|
|
222
|
+
const identity = await (0, aws_helpers_js_1.getCallerArn)(region);
|
|
223
|
+
if (identity) {
|
|
224
|
+
console.log(' Logged in as: ' + identity);
|
|
225
|
+
}
|
|
226
|
+
console.log('');
|
|
227
|
+
console.log(' ~/.aws/credentials has keys from a different project.');
|
|
228
|
+
console.log(' ============================================================');
|
|
229
|
+
}
|
|
230
|
+
// Try to sync from vault
|
|
231
|
+
if (config.ansible?.vault_path) {
|
|
232
|
+
try {
|
|
233
|
+
const { AnsibleVaultSecrets } = await Promise.resolve().then(() => __importStar(require('../../../../utils/ansible-vault-secrets.js')));
|
|
234
|
+
const vault = new AnsibleVaultSecrets({
|
|
235
|
+
vault_path: config.ansible.vault_path,
|
|
236
|
+
vault_password_file: config.ansible.vault_password_file,
|
|
237
|
+
});
|
|
238
|
+
const vaultKeyId = await vault.getSecret('AWS_ACCESS_KEY_ID');
|
|
239
|
+
const vaultSecret = await vault.getSecret('AWS_SECRET_ACCESS_KEY');
|
|
240
|
+
if (!vaultKeyId || !vaultSecret) {
|
|
241
|
+
console.log(' AWS credentials not found in vault');
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
// Verify vault key matches stack.yml
|
|
245
|
+
if (vaultKeyId !== configKeyId) {
|
|
246
|
+
const { promptSingleLine } = await Promise.resolve().then(() => __importStar(require('../../../../utils/secret-prompts.js')));
|
|
247
|
+
console.log('');
|
|
248
|
+
console.log(' ============================================================');
|
|
249
|
+
console.log(' VAULT / STACK.YML MISMATCH');
|
|
250
|
+
console.log(' ============================================================');
|
|
251
|
+
console.log(' Vault AWS_ACCESS_KEY_ID: ' + vaultKeyId);
|
|
252
|
+
console.log(' stack.yml access_key_id: ' + configKeyId);
|
|
253
|
+
console.log(' ============================================================');
|
|
254
|
+
console.log('');
|
|
255
|
+
console.log(' Which is correct?');
|
|
256
|
+
console.log(' 1) Vault is correct → update stack.yml to match vault');
|
|
257
|
+
console.log(' 2) stack.yml is correct → update vault (you will need the secret key)');
|
|
258
|
+
console.log('');
|
|
259
|
+
const choice = await promptSingleLine(' Enter 1 or 2: ');
|
|
260
|
+
if (choice === '1') {
|
|
261
|
+
// Update stack.yml access_key_id to match vault
|
|
262
|
+
const stackPath = path.join(rootDir, 'stack.yml');
|
|
263
|
+
if (fs.existsSync(stackPath)) {
|
|
264
|
+
let content = fs.readFileSync(stackPath, 'utf8');
|
|
265
|
+
content = content.replace('access_key_id: ' + configKeyId, 'access_key_id: ' + vaultKeyId);
|
|
266
|
+
fs.writeFileSync(stackPath, content, 'utf8');
|
|
267
|
+
console.log(' [OK] Updated stack.yml access_key_id to ' + vaultKeyId);
|
|
268
|
+
// Now vault matches — write to ~/.aws/credentials
|
|
269
|
+
(0, aws_helpers_js_1.writeAwsCredentials)(vaultKeyId, vaultSecret, region);
|
|
270
|
+
(0, aws_helpers_js_1.clearClientCache)();
|
|
271
|
+
const accountId = await (0, aws_helpers_js_1.getAwsAccountId)(region);
|
|
272
|
+
if (accountId) {
|
|
273
|
+
const identity = await (0, aws_helpers_js_1.getCallerArn)(region);
|
|
274
|
+
console.log(' [OK] Synced ~/.aws/credentials from vault');
|
|
275
|
+
console.log(' Logged in as: ' + (identity ?? vaultKeyId));
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
console.log(' Vault credentials failed to authenticate');
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
console.log(' stack.yml not found');
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
else if (choice === '2') {
|
|
285
|
+
// stack.yml is correct — prompt for secret key and store in vault
|
|
286
|
+
console.log('');
|
|
287
|
+
console.log(' Enter the AWS Secret Access Key for ' + configKeyId + ':');
|
|
288
|
+
const newSecret = await promptSingleLine(' AWS Secret Access Key: ', { hidden: true });
|
|
289
|
+
if (!newSecret) {
|
|
290
|
+
console.log(' Secret Access Key is required.');
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
const trimmedSecret = newSecret.trim();
|
|
294
|
+
// Verify credentials by passing them explicitly to STS (don't rely on file cache)
|
|
295
|
+
let verifyId = null;
|
|
296
|
+
try {
|
|
297
|
+
const { STSClient: STS, GetCallerIdentityCommand: GetId } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-sts')));
|
|
298
|
+
const sts = new STS({
|
|
299
|
+
region,
|
|
300
|
+
credentials: {
|
|
301
|
+
accessKeyId: configKeyId,
|
|
302
|
+
secretAccessKey: trimmedSecret,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
const result = await sts.send(new GetId({}));
|
|
306
|
+
verifyId = result.Account ?? null;
|
|
307
|
+
}
|
|
308
|
+
catch (stsErr) {
|
|
309
|
+
const errMsg = stsErr instanceof Error ? stsErr.message : String(stsErr);
|
|
310
|
+
console.log(' Credentials invalid — check that the secret key matches ' + configKeyId);
|
|
311
|
+
console.log(' AWS error: ' + errMsg);
|
|
312
|
+
console.log(' TIP: New IAM keys can take ~10 seconds to propagate. Try again shortly.');
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
if (!verifyId) {
|
|
316
|
+
console.log(' Credentials invalid — no account ID returned');
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
// Credentials verified — write to ~/.aws/credentials
|
|
320
|
+
(0, aws_helpers_js_1.writeAwsCredentials)(configKeyId, trimmedSecret, region);
|
|
321
|
+
(0, aws_helpers_js_1.clearClientCache)();
|
|
322
|
+
const verifyIdentity = await (0, aws_helpers_js_1.getCallerArn)(region);
|
|
323
|
+
console.log(' [OK] Verified: ' + (verifyIdentity ?? configKeyId));
|
|
324
|
+
// Store in vault
|
|
325
|
+
try {
|
|
326
|
+
const r1 = await vault.setSecret('AWS_ACCESS_KEY_ID', configKeyId);
|
|
327
|
+
const r2 = await vault.setSecret('AWS_SECRET_ACCESS_KEY', newSecret);
|
|
328
|
+
if (r1.success && r2.success) {
|
|
329
|
+
console.log(' [OK] Stored credentials in Ansible Vault');
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
console.log(' Failed to store in vault');
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
catch (e2) {
|
|
336
|
+
console.log(' Error storing in vault: ' + (e2 instanceof Error ? e2.message : String(e2)));
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
console.log(' Skipped — run npx stack fix again to retry');
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Vault matches stack.yml — write to ~/.aws/credentials
|
|
346
|
+
(0, aws_helpers_js_1.writeAwsCredentials)(vaultKeyId, vaultSecret, region);
|
|
347
|
+
(0, aws_helpers_js_1.clearClientCache)();
|
|
348
|
+
// Verify the synced credentials work
|
|
349
|
+
const accountId = await (0, aws_helpers_js_1.getAwsAccountId)(region);
|
|
350
|
+
if (!accountId) {
|
|
351
|
+
console.log(' Synced credentials from vault but they failed to authenticate');
|
|
352
|
+
console.log(' The credentials may be expired or deactivated in AWS');
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
const identity = await (0, aws_helpers_js_1.getCallerArn)(region);
|
|
356
|
+
console.log(' [OK] Synced ~/.aws/credentials from vault');
|
|
357
|
+
console.log(' Logged in as: ' + (identity ?? vaultKeyId));
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
catch (e) {
|
|
361
|
+
console.log(' Error reading vault: ' + (e instanceof Error ? e.message : String(e)));
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// No vault — can't auto-fix
|
|
366
|
+
console.log('');
|
|
367
|
+
console.log(' To fix, store the correct credentials in the vault:');
|
|
368
|
+
console.log(' npx stack deploy --secrets set AWS_ACCESS_KEY_ID');
|
|
369
|
+
console.log(' npx stack deploy --secrets set AWS_SECRET_ACCESS_KEY');
|
|
370
|
+
console.log(' Then run: npx stack fix');
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
// CRITICAL: aws-credentials-sync MUST be the first fix in this array.
|
|
374
|
+
// It ensures ~/.aws/credentials matches stack.yml before any other AWS operation runs.
|
|
375
|
+
// See .spec/aws-iam.md for why.
|
|
194
376
|
exports.credentialsFixes = [
|
|
377
|
+
{
|
|
378
|
+
id: 'aws-credentials-sync',
|
|
379
|
+
stage: 'dev',
|
|
380
|
+
severity: 'critical',
|
|
381
|
+
blocking: true,
|
|
382
|
+
description: '🔑 AWS credentials out of sync (vault → ~/.aws/credentials)',
|
|
383
|
+
scan: async function (config, _rootDir) {
|
|
384
|
+
if (!(await isAwsProject(config)))
|
|
385
|
+
return false;
|
|
386
|
+
const awsConfig = (0, aws_helpers_js_1.getAwsConfig)(config);
|
|
387
|
+
const configKeyId = awsConfig.accessKeyId;
|
|
388
|
+
if (!configKeyId)
|
|
389
|
+
return false; // No access_key_id in stack.yml — nothing to sync against
|
|
390
|
+
// Read what ~/.aws/credentials currently has
|
|
391
|
+
const localKeyId = (0, aws_helpers_js_1.getLocalAccessKeyId)();
|
|
392
|
+
// Case 1: ~/.aws/credentials matches stack.yml — already synced
|
|
393
|
+
if (localKeyId === configKeyId)
|
|
394
|
+
return false;
|
|
395
|
+
// Case 2: ~/.aws/credentials is missing or has a different key — needs sync
|
|
396
|
+
// Check if vault can provide the right credentials
|
|
397
|
+
if (config.ansible?.vault_path) {
|
|
398
|
+
try {
|
|
399
|
+
const { AnsibleVaultSecrets } = await Promise.resolve().then(() => __importStar(require('../../../../utils/ansible-vault-secrets.js')));
|
|
400
|
+
const vault = new AnsibleVaultSecrets({
|
|
401
|
+
vault_path: config.ansible.vault_path,
|
|
402
|
+
vault_password_file: config.ansible.vault_password_file,
|
|
403
|
+
});
|
|
404
|
+
const check = await vault.checkSecrets(['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY']);
|
|
405
|
+
if (check.status?.AWS_ACCESS_KEY_ID && check.status?.AWS_SECRET_ACCESS_KEY) {
|
|
406
|
+
// Vault has credentials — we can auto-fix
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// Vault unreadable
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// No vault credentials — still flag if there's a mismatch
|
|
415
|
+
if (localKeyId && localKeyId !== configKeyId) {
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
return false;
|
|
419
|
+
},
|
|
420
|
+
fix: async function (config, _rootDir) {
|
|
421
|
+
const result = await _syncCredentials(config, _rootDir);
|
|
422
|
+
if (!result)
|
|
423
|
+
(0, aws_helpers_js_1.setCredentialsSyncFailed)();
|
|
424
|
+
return result;
|
|
425
|
+
},
|
|
426
|
+
manualFix: [
|
|
427
|
+
'AWS credentials in ~/.aws/credentials do not match stack.yml access_key_id.',
|
|
428
|
+
'The vault is the source of truth — sync by running:',
|
|
429
|
+
' npx stack fix --dev',
|
|
430
|
+
'',
|
|
431
|
+
'Or update vault credentials manually:',
|
|
432
|
+
' npx stack deploy --secrets set AWS_ACCESS_KEY_ID',
|
|
433
|
+
' npx stack deploy --secrets set AWS_SECRET_ACCESS_KEY',
|
|
434
|
+
].join('\n'),
|
|
435
|
+
},
|
|
195
436
|
{
|
|
196
437
|
id: 'aws-account-not-setup',
|
|
197
438
|
stage: 'dev',
|
|
198
439
|
severity: 'critical',
|
|
199
440
|
description: '☁️ AWS credentials not configured',
|
|
200
441
|
scan: async (config, _rootDir) => {
|
|
442
|
+
if (!(await isAwsProject(config)))
|
|
443
|
+
return false;
|
|
201
444
|
const awsConfig = (0, aws_helpers_js_1.getAwsConfig)(config);
|
|
202
|
-
if (!awsConfig.accessKeyId && !config.aws) {
|
|
203
|
-
const { extractEnvironments } = await Promise.resolve().then(() => __importStar(require('../../../../utils/config-helpers.js')));
|
|
204
|
-
const environments = extractEnvironments(config);
|
|
205
|
-
const hasAwsEnv = Object.values(environments).some((e) => !!e.access_key_id || !!e.config);
|
|
206
|
-
if (!hasAwsEnv)
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
445
|
const accountId = await (0, aws_helpers_js_1.getAwsAccountId)(awsConfig.region);
|
|
210
446
|
return !accountId;
|
|
211
447
|
},
|
|
@@ -237,10 +473,7 @@ exports.credentialsFixes = [
|
|
|
237
473
|
severity: 'warning',
|
|
238
474
|
description: '🌍 AWS region not configured in stack.yml',
|
|
239
475
|
scan: async (config, _rootDir) => {
|
|
240
|
-
|
|
241
|
-
const environments = extractEnvironments(config);
|
|
242
|
-
const hasAwsEnv = Object.values(environments).some((e) => !!e.access_key_id || !!e.config);
|
|
243
|
-
if (!hasAwsEnv && !config.aws)
|
|
476
|
+
if (!(await isAwsProject(config)))
|
|
244
477
|
return false;
|
|
245
478
|
const awsConfig = (0, aws_helpers_js_1.getAwsConfig)(config);
|
|
246
479
|
return !awsConfig.region || awsConfig.region === 'us-east-1' && !config.aws?.region;
|
|
@@ -280,10 +513,7 @@ exports.credentialsFixes = [
|
|
|
280
513
|
severity: 'critical',
|
|
281
514
|
description: '🔑 AWS credentials not available (env vars or Ansible Vault)',
|
|
282
515
|
scan: async (config, _rootDir) => {
|
|
283
|
-
|
|
284
|
-
const environments = extractEnvironments(config);
|
|
285
|
-
const hasAwsEnv = Object.values(environments).some((e) => !!e.access_key_id || !!e.config);
|
|
286
|
-
if (!hasAwsEnv && !config.aws)
|
|
516
|
+
if (!(await isAwsProject(config)))
|
|
287
517
|
return false;
|
|
288
518
|
if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
|
|
289
519
|
return false;
|
|
@@ -315,10 +545,10 @@ exports.credentialsFixes = [
|
|
|
315
545
|
return false;
|
|
316
546
|
}
|
|
317
547
|
try {
|
|
318
|
-
// Read from ~/.aws/credentials (set by aws configure)
|
|
548
|
+
// Read from ~/.aws/credentials (set by aws configure or vault sync)
|
|
319
549
|
const awsCredsPath = path.join(os.homedir(), '.aws', 'credentials');
|
|
320
550
|
if (!fs.existsSync(awsCredsPath)) {
|
|
321
|
-
console.log(' ~/.aws/credentials not found — run "
|
|
551
|
+
console.log(' ~/.aws/credentials not found — run "npx stack fix --dev" first');
|
|
322
552
|
return false;
|
|
323
553
|
}
|
|
324
554
|
const content = fs.readFileSync(awsCredsPath, 'utf8');
|
|
@@ -356,176 +586,11 @@ exports.credentialsFixes = [
|
|
|
356
586
|
' export AWS_ACCESS_KEY_ID=AKIA...',
|
|
357
587
|
' export AWS_SECRET_ACCESS_KEY=...',
|
|
358
588
|
'',
|
|
359
|
-
' Option B:
|
|
360
|
-
' aws configure (then run: npx stack fix --secrets)',
|
|
361
|
-
'',
|
|
362
|
-
' Option C: Ansible Vault (manual)',
|
|
589
|
+
' Option B: Store in Ansible Vault',
|
|
363
590
|
' npx stack deploy --secrets set AWS_ACCESS_KEY_ID',
|
|
364
591
|
' npx stack deploy --secrets set AWS_SECRET_ACCESS_KEY',
|
|
365
592
|
].join('\n'),
|
|
366
593
|
},
|
|
367
|
-
{
|
|
368
|
-
id: 'aws-cli-not-configured-from-vault',
|
|
369
|
-
stage: 'dev',
|
|
370
|
-
severity: 'warning',
|
|
371
|
-
description: '🔐 AWS CLI not configured on dev machine (credentials exist in vault)',
|
|
372
|
-
scan: async (config, _rootDir) => {
|
|
373
|
-
// Skip if not using AWS
|
|
374
|
-
const { extractEnvironments } = await Promise.resolve().then(() => __importStar(require('../../../../utils/config-helpers.js')));
|
|
375
|
-
const environments = extractEnvironments(config);
|
|
376
|
-
const hasAwsEnv = Object.values(environments).some((e) => !!e.access_key_id || !!e.config);
|
|
377
|
-
if (!hasAwsEnv && !config.aws)
|
|
378
|
-
return false;
|
|
379
|
-
// Skip if AWS CLI already configured
|
|
380
|
-
const os = await Promise.resolve().then(() => __importStar(require('os')));
|
|
381
|
-
const awsCredsPath = path.join(os.homedir(), '.aws', 'credentials');
|
|
382
|
-
if (fs.existsSync(awsCredsPath)) {
|
|
383
|
-
// Check if credentials are valid
|
|
384
|
-
const content = fs.readFileSync(awsCredsPath, 'utf8');
|
|
385
|
-
if (content.includes('aws_access_key_id')) {
|
|
386
|
-
return false; // Already configured
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
// Check if credentials exist in vault
|
|
390
|
-
if (!config.ansible?.vault_path)
|
|
391
|
-
return false;
|
|
392
|
-
try {
|
|
393
|
-
const { AnsibleVaultSecrets } = await Promise.resolve().then(() => __importStar(require('../../../../utils/ansible-vault-secrets.js')));
|
|
394
|
-
const vault = new AnsibleVaultSecrets({
|
|
395
|
-
vault_path: config.ansible.vault_path,
|
|
396
|
-
vault_password_file: config.ansible.vault_password_file,
|
|
397
|
-
});
|
|
398
|
-
const result = await vault.checkSecrets(['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY']);
|
|
399
|
-
// Flag if credentials exist in vault but not in ~/.aws/credentials
|
|
400
|
-
return !!(result.status?.AWS_ACCESS_KEY_ID && result.status?.AWS_SECRET_ACCESS_KEY);
|
|
401
|
-
}
|
|
402
|
-
catch {
|
|
403
|
-
return false;
|
|
404
|
-
}
|
|
405
|
-
},
|
|
406
|
-
fix: async (config, _rootDir) => {
|
|
407
|
-
if (!config.ansible?.vault_path) {
|
|
408
|
-
console.log(' Ansible Vault not configured');
|
|
409
|
-
return false;
|
|
410
|
-
}
|
|
411
|
-
try {
|
|
412
|
-
const { AnsibleVaultSecrets } = await Promise.resolve().then(() => __importStar(require('../../../../utils/ansible-vault-secrets.js')));
|
|
413
|
-
const vault = new AnsibleVaultSecrets({
|
|
414
|
-
vault_path: config.ansible.vault_path,
|
|
415
|
-
vault_password_file: config.ansible.vault_password_file,
|
|
416
|
-
});
|
|
417
|
-
const accessKeyId = await vault.getSecret('AWS_ACCESS_KEY_ID');
|
|
418
|
-
const secretKey = await vault.getSecret('AWS_SECRET_ACCESS_KEY');
|
|
419
|
-
if (!accessKeyId || !secretKey) {
|
|
420
|
-
console.log(' AWS credentials not found in vault');
|
|
421
|
-
return false;
|
|
422
|
-
}
|
|
423
|
-
const region = config.aws?.region ?? 'us-east-1';
|
|
424
|
-
(0, aws_helpers_js_1.writeAwsCredentials)(accessKeyId, secretKey, region);
|
|
425
|
-
(0, aws_helpers_js_1.clearClientCache)(); // Pick up vault credentials
|
|
426
|
-
console.log(' ✅ Configured ~/.aws/credentials from Ansible Vault');
|
|
427
|
-
console.log(' ✅ Configured ~/.aws/config (region: ' + region + ')');
|
|
428
|
-
return true;
|
|
429
|
-
}
|
|
430
|
-
catch (e) {
|
|
431
|
-
console.log(' Error: ' + (e instanceof Error ? e.message : String(e)));
|
|
432
|
-
return false;
|
|
433
|
-
}
|
|
434
|
-
},
|
|
435
|
-
manualFix: 'Extract AWS credentials from vault and configure CLI:\n' +
|
|
436
|
-
' npx stack fix --dev',
|
|
437
|
-
},
|
|
438
|
-
{
|
|
439
|
-
id: 'aws-credentials-wrong-user',
|
|
440
|
-
stage: 'dev',
|
|
441
|
-
severity: 'warning',
|
|
442
|
-
get description() {
|
|
443
|
-
const arn = this._callerArn;
|
|
444
|
-
if (arn) {
|
|
445
|
-
const userName = arn.split('/').pop() ?? 'unknown';
|
|
446
|
-
return '🔑 AWS CLI using "' + userName + '" instead of admin user (factiii-admin)';
|
|
447
|
-
}
|
|
448
|
-
return '🔑 AWS CLI credentials do not match expected admin user';
|
|
449
|
-
},
|
|
450
|
-
scan: async (config, _rootDir) => {
|
|
451
|
-
// Only check if AWS is configured
|
|
452
|
-
const { extractEnvironments } = await Promise.resolve().then(() => __importStar(require('../../../../utils/config-helpers.js')));
|
|
453
|
-
const environments = extractEnvironments(config);
|
|
454
|
-
const hasAwsEnv = Object.values(environments).some((e) => !!e.access_key_id || !!e.config);
|
|
455
|
-
if (!hasAwsEnv && !config.aws)
|
|
456
|
-
return false;
|
|
457
|
-
const awsConfig = (0, aws_helpers_js_1.getAwsConfig)(config);
|
|
458
|
-
const accountId = await (0, aws_helpers_js_1.getAwsAccountId)(awsConfig.region);
|
|
459
|
-
if (!accountId)
|
|
460
|
-
return false; // No valid creds — handled by other fixes
|
|
461
|
-
// Get current caller identity
|
|
462
|
-
const callerArn = await (0, aws_helpers_js_1.getCallerArn)(awsConfig.region);
|
|
463
|
-
if (!callerArn)
|
|
464
|
-
return false;
|
|
465
|
-
// Extract username from ARN: arn:aws:iam::ACCOUNT:user/USERNAME
|
|
466
|
-
const arnParts = callerArn.split('/');
|
|
467
|
-
const currentUser = arnParts.length > 1 ? arnParts[arnParts.length - 1] : null;
|
|
468
|
-
if (!currentUser)
|
|
469
|
-
return false;
|
|
470
|
-
// The admin user should be factiii-admin
|
|
471
|
-
// Project scoped users (factiii-{project}-dev, factiii-{project}-prod) don't have IAM perms
|
|
472
|
-
if (currentUser === 'factiii-admin')
|
|
473
|
-
return false; // Correct user
|
|
474
|
-
// Also allow root account (no user path in ARN)
|
|
475
|
-
if (callerArn.includes(':root'))
|
|
476
|
-
return false;
|
|
477
|
-
// Flag ANY scoped factiii user — they lack IAM permissions and may be from
|
|
478
|
-
// a different project (e.g., last project's user still loaded in ~/.aws/credentials)
|
|
479
|
-
if (currentUser.startsWith('factiii-') &&
|
|
480
|
-
(currentUser.endsWith('-dev') || currentUser.endsWith('-prod'))) {
|
|
481
|
-
this._callerArn = callerArn;
|
|
482
|
-
return true;
|
|
483
|
-
}
|
|
484
|
-
// Unknown user — don't flag (could be custom setup)
|
|
485
|
-
return false;
|
|
486
|
-
},
|
|
487
|
-
fix: async (config, _rootDir) => {
|
|
488
|
-
// Try to swap to vault admin credentials
|
|
489
|
-
if (!config.ansible?.vault_path) {
|
|
490
|
-
console.log(' No Ansible Vault configured — update ~/.aws/credentials manually');
|
|
491
|
-
return false;
|
|
492
|
-
}
|
|
493
|
-
try {
|
|
494
|
-
const { AnsibleVaultSecrets } = await Promise.resolve().then(() => __importStar(require('../../../../utils/ansible-vault-secrets.js')));
|
|
495
|
-
const vault = new AnsibleVaultSecrets({
|
|
496
|
-
vault_path: config.ansible.vault_path,
|
|
497
|
-
vault_password_file: config.ansible.vault_password_file,
|
|
498
|
-
});
|
|
499
|
-
const check = await vault.checkSecrets(['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY']);
|
|
500
|
-
if (!check.status?.AWS_ACCESS_KEY_ID || !check.status?.AWS_SECRET_ACCESS_KEY) {
|
|
501
|
-
console.log(' Admin credentials not found in vault');
|
|
502
|
-
return false;
|
|
503
|
-
}
|
|
504
|
-
const accessKeyId = await vault.getSecret('AWS_ACCESS_KEY_ID');
|
|
505
|
-
const secretKey = await vault.getSecret('AWS_SECRET_ACCESS_KEY');
|
|
506
|
-
if (!accessKeyId || !secretKey) {
|
|
507
|
-
console.log(' Failed to read credentials from vault');
|
|
508
|
-
return false;
|
|
509
|
-
}
|
|
510
|
-
const awsConfig = (0, aws_helpers_js_1.getAwsConfig)(config);
|
|
511
|
-
(0, aws_helpers_js_1.writeAwsCredentials)(accessKeyId, secretKey, awsConfig.region || 'us-east-1');
|
|
512
|
-
// Clear cached clients so they pick up new credentials
|
|
513
|
-
(0, aws_helpers_js_1.clearClientCache)();
|
|
514
|
-
// Verify the swapped credentials are the admin user
|
|
515
|
-
const newArn = await (0, aws_helpers_js_1.getCallerArn)(awsConfig.region);
|
|
516
|
-
console.log(' Switched to: ' + (newArn ?? 'vault credentials'));
|
|
517
|
-
return true;
|
|
518
|
-
}
|
|
519
|
-
catch (e) {
|
|
520
|
-
console.log(' Error: ' + (e instanceof Error ? e.message : String(e)));
|
|
521
|
-
return false;
|
|
522
|
-
}
|
|
523
|
-
},
|
|
524
|
-
manualFix: 'Update ~/.aws/credentials with admin (factiii-admin) credentials:\n' +
|
|
525
|
-
' npx stack deploy --secrets set AWS_ACCESS_KEY_ID\n' +
|
|
526
|
-
' npx stack deploy --secrets set AWS_SECRET_ACCESS_KEY\n' +
|
|
527
|
-
' Then: npx stack fix --dev',
|
|
528
|
-
},
|
|
529
594
|
{
|
|
530
595
|
id: 'aws-credentials-invalid',
|
|
531
596
|
stage: 'secrets',
|
|
@@ -562,24 +627,21 @@ exports.credentialsFixes = [
|
|
|
562
627
|
severity: 'critical',
|
|
563
628
|
description: '🔑 AWS credentials not valid for prod provisioning',
|
|
564
629
|
scan: async (config, _rootDir) => {
|
|
565
|
-
|
|
566
|
-
const environments = extractEnvironments(config);
|
|
567
|
-
const hasAwsEnv = Object.values(environments).some((e) => !!e.access_key_id || !!e.config);
|
|
568
|
-
if (!hasAwsEnv && !config.aws)
|
|
630
|
+
if (!(await isAwsProject(config)))
|
|
569
631
|
return false;
|
|
570
632
|
const awsConfig = (0, aws_helpers_js_1.getAwsConfig)(config);
|
|
571
633
|
const accountId = await (0, aws_helpers_js_1.getAwsAccountId)(awsConfig.region);
|
|
572
634
|
if (accountId) {
|
|
573
635
|
// Credentials work — but check if we're using a scoped user that lacks provisioning perms
|
|
574
|
-
const
|
|
575
|
-
if (!
|
|
576
|
-
return false;
|
|
577
|
-
const arnParts = callerArn.split('/');
|
|
578
|
-
const currentUser = arnParts.length > 1 ? arnParts[arnParts.length - 1] : null;
|
|
579
|
-
if (!currentUser)
|
|
636
|
+
const callerIdentity = await (0, aws_helpers_js_1.getCallerArn)(awsConfig.region);
|
|
637
|
+
if (!callerIdentity)
|
|
580
638
|
return false;
|
|
639
|
+
// Extract username from formatted string "userName (AKIA...)"
|
|
640
|
+
const currentUser = callerIdentity.includes(' (')
|
|
641
|
+
? (callerIdentity.split(' (')[0] ?? callerIdentity)
|
|
642
|
+
: callerIdentity;
|
|
581
643
|
// Admin and root are fine for provisioning
|
|
582
|
-
if (currentUser === 'factiii-admin' ||
|
|
644
|
+
if (currentUser === 'factiii-admin' || callerIdentity.includes(':root'))
|
|
583
645
|
return false;
|
|
584
646
|
// Scoped users (factiii-xxx-dev, factiii-xxx-prod) lack VPC/EC2 create permissions
|
|
585
647
|
if (currentUser.startsWith('factiii-') &&
|
|
@@ -671,8 +733,9 @@ exports.credentialsFixes = [
|
|
|
671
733
|
return true;
|
|
672
734
|
},
|
|
673
735
|
manualFix: 'Configure AWS admin credentials:\n' +
|
|
674
|
-
'
|
|
675
|
-
'
|
|
736
|
+
' npx stack deploy --secrets set AWS_ACCESS_KEY_ID\n' +
|
|
737
|
+
' npx stack deploy --secrets set AWS_SECRET_ACCESS_KEY\n' +
|
|
738
|
+
' Then: npx stack fix --dev',
|
|
676
739
|
},
|
|
677
740
|
];
|
|
678
741
|
//# sourceMappingURL=credentials.js.map
|