@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.
Files changed (28) hide show
  1. package/dist/cli/fix.d.ts.map +1 -1
  2. package/dist/cli/fix.js +18 -0
  3. package/dist/cli/fix.js.map +1 -1
  4. package/dist/plugins/pipelines/aws/scanfix/credentials.d.ts +10 -8
  5. package/dist/plugins/pipelines/aws/scanfix/credentials.d.ts.map +1 -1
  6. package/dist/plugins/pipelines/aws/scanfix/credentials.js +267 -204
  7. package/dist/plugins/pipelines/aws/scanfix/credentials.js.map +1 -1
  8. package/dist/plugins/pipelines/aws/scanfix/iam.d.ts.map +1 -1
  9. package/dist/plugins/pipelines/aws/scanfix/iam.js +86 -15
  10. package/dist/plugins/pipelines/aws/scanfix/iam.js.map +1 -1
  11. package/dist/plugins/pipelines/aws/utils/aws-helpers.d.ts +19 -0
  12. package/dist/plugins/pipelines/aws/utils/aws-helpers.d.ts.map +1 -1
  13. package/dist/plugins/pipelines/aws/utils/aws-helpers.js +79 -14
  14. package/dist/plugins/pipelines/aws/utils/aws-helpers.js.map +1 -1
  15. package/dist/plugins/pipelines/factiii/scanfix/port-convention.js +6 -6
  16. package/dist/plugins/pipelines/factiii/scanfix/port-convention.js.map +1 -1
  17. package/dist/plugins/pipelines/factiii/scanfix/secrets.d.ts.map +1 -1
  18. package/dist/plugins/pipelines/factiii/scanfix/secrets.js +61 -0
  19. package/dist/plugins/pipelines/factiii/scanfix/secrets.js.map +1 -1
  20. package/dist/plugins/servers/amazon-linux/index.d.ts +1 -0
  21. package/dist/plugins/servers/amazon-linux/index.d.ts.map +1 -1
  22. package/dist/plugins/servers/mac/index.d.ts +1 -0
  23. package/dist/plugins/servers/mac/index.d.ts.map +1 -1
  24. package/dist/plugins/servers/ubuntu/index.d.ts +1 -0
  25. package/dist/plugins/servers/ubuntu/index.d.ts.map +1 -1
  26. package/dist/types/plugin.d.ts +6 -0
  27. package/dist/types/plugin.d.ts.map +1 -1
  28. package/package.json +1 -1
@@ -2,15 +2,17 @@
2
2
  /**
3
3
  * AWS Credential Fixes
4
4
  *
5
- * Handles AWS account setup, credential validation,
6
- * and region configuration checks.
5
+ * Handles AWS credential sync, account setup, and validation.
7
6
  *
8
- * The aws-account-not-setup fix auto-bootstraps:
9
- * 1. Checks if AWS SDK can get caller identity (valid credentials)
10
- * 2. If not, prompts user to login via `aws configure` (root or admin)
11
- * 3. Confirms with user before creating IAM admin user
12
- * 4. Creates IAM user, attaches bootstrap policy, creates access key
13
- * 5. Auto-configures AWS CLI with new IAM credentials
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
- const { extractEnvironments } = await Promise.resolve().then(() => __importStar(require('../../../../utils/config-helpers.js')));
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
- const { extractEnvironments } = await Promise.resolve().then(() => __importStar(require('../../../../utils/config-helpers.js')));
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 "aws configure" first');
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: AWS CLI + auto-store in vault',
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
- const { extractEnvironments } = await Promise.resolve().then(() => __importStar(require('../../../../utils/config-helpers.js')));
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 callerArn = await (0, aws_helpers_js_1.getCallerArn)(awsConfig.region);
575
- if (!callerArn)
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' || callerArn.includes(':root'))
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
- ' aws configure (paste factiii-admin access key + secret)\n' +
675
- ' Or restore from vault: npx stack fix --dev',
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