@friggframework/core 2.0.0--canary.427.04558b7.0 → 2.0.0--canary.427.bd07d1c.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/handlers/routers/health.js +204 -80
- package/package.json +5 -5
|
@@ -333,6 +333,178 @@ const testEncryption = async () => {
|
|
|
333
333
|
}
|
|
334
334
|
};
|
|
335
335
|
|
|
336
|
+
const checkKMSAccess = async () => {
|
|
337
|
+
const { KMS_KEY_ARN } = process.env;
|
|
338
|
+
|
|
339
|
+
if (!KMS_KEY_ARN || KMS_KEY_ARN.trim() === '') {
|
|
340
|
+
return {
|
|
341
|
+
status: 'disabled',
|
|
342
|
+
testResult: 'No KMS key configured',
|
|
343
|
+
canAccessKey: false,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
// eslint-disable-next-line no-console
|
|
349
|
+
console.log('Testing KMS key access with key:', KMS_KEY_ARN.substring(0, 50) + '...');
|
|
350
|
+
|
|
351
|
+
const AWS = require('aws-sdk');
|
|
352
|
+
const kms = new AWS.KMS();
|
|
353
|
+
|
|
354
|
+
// First, check if the master key exists and is accessible
|
|
355
|
+
let keyExists = false;
|
|
356
|
+
let keyMetadata = null;
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
// eslint-disable-next-line no-console
|
|
360
|
+
console.log('Checking if KMS master key exists...');
|
|
361
|
+
const describeResult = await withTimeout(
|
|
362
|
+
kms.describeKey({ KeyId: KMS_KEY_ARN }).promise(),
|
|
363
|
+
5000,
|
|
364
|
+
'KMS describeKey operation timed out after 5 seconds'
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
keyMetadata = describeResult.KeyMetadata;
|
|
368
|
+
keyExists = true;
|
|
369
|
+
// eslint-disable-next-line no-console
|
|
370
|
+
console.log('KMS master key found:', {
|
|
371
|
+
KeyId: keyMetadata.KeyId,
|
|
372
|
+
KeyState: keyMetadata.KeyState,
|
|
373
|
+
Enabled: keyMetadata.Enabled,
|
|
374
|
+
KeyUsage: keyMetadata.KeyUsage,
|
|
375
|
+
});
|
|
376
|
+
} catch (describeError) {
|
|
377
|
+
// eslint-disable-next-line no-console
|
|
378
|
+
console.error('KMS master key does not exist or is not accessible:', describeError.message);
|
|
379
|
+
|
|
380
|
+
if (describeError.code === 'NotFoundException') {
|
|
381
|
+
return {
|
|
382
|
+
status: 'unhealthy',
|
|
383
|
+
testResult: 'KMS master key not found',
|
|
384
|
+
canAccessKey: false,
|
|
385
|
+
error: 'Master key does not exist',
|
|
386
|
+
keyExists: false,
|
|
387
|
+
};
|
|
388
|
+
} else if (describeError.code === 'AccessDeniedException') {
|
|
389
|
+
return {
|
|
390
|
+
status: 'unhealthy',
|
|
391
|
+
testResult: 'No permission to access KMS master key',
|
|
392
|
+
canAccessKey: false,
|
|
393
|
+
error: 'Access denied to master key',
|
|
394
|
+
keyExists: 'unknown',
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
// Continue to try generateDataKey even if describeKey fails
|
|
398
|
+
// as permissions might be limited
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Check if key is in a usable state
|
|
402
|
+
if (keyExists && keyMetadata) {
|
|
403
|
+
// Only 'Enabled' state allows cryptographic operations
|
|
404
|
+
if (keyMetadata.KeyState !== 'Enabled') {
|
|
405
|
+
// eslint-disable-next-line no-console
|
|
406
|
+
console.error(`KMS master key exists but is in state: ${keyMetadata.KeyState}`);
|
|
407
|
+
|
|
408
|
+
let testResult = '';
|
|
409
|
+
switch (keyMetadata.KeyState) {
|
|
410
|
+
case 'Disabled':
|
|
411
|
+
testResult = 'KMS master key is disabled';
|
|
412
|
+
break;
|
|
413
|
+
case 'PendingDeletion':
|
|
414
|
+
testResult = 'KMS master key is pending deletion';
|
|
415
|
+
break;
|
|
416
|
+
case 'PendingImport':
|
|
417
|
+
testResult = 'KMS master key is pending import';
|
|
418
|
+
break;
|
|
419
|
+
case 'Unavailable':
|
|
420
|
+
testResult = 'KMS master key is unavailable (custom key store disconnected)';
|
|
421
|
+
break;
|
|
422
|
+
case 'Creating':
|
|
423
|
+
testResult = 'KMS master key is still being created';
|
|
424
|
+
break;
|
|
425
|
+
case 'Updating':
|
|
426
|
+
testResult = 'KMS master key is being updated';
|
|
427
|
+
break;
|
|
428
|
+
default:
|
|
429
|
+
testResult = `KMS master key is in unusable state: ${keyMetadata.KeyState}`;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
status: 'unhealthy',
|
|
434
|
+
testResult,
|
|
435
|
+
canAccessKey: false,
|
|
436
|
+
keyExists: true,
|
|
437
|
+
keyState: keyMetadata.KeyState,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Try to generate a data key to test full KMS access
|
|
443
|
+
// eslint-disable-next-line no-console
|
|
444
|
+
console.log('Attempting to generate data key...');
|
|
445
|
+
const startTime = Date.now();
|
|
446
|
+
const result = await withTimeout(
|
|
447
|
+
kms.generateDataKey({
|
|
448
|
+
KeyId: KMS_KEY_ARN,
|
|
449
|
+
KeySpec: 'AES_256'
|
|
450
|
+
}).promise(),
|
|
451
|
+
10000,
|
|
452
|
+
'KMS generateDataKey operation timed out after 10 seconds'
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
const responseTime = Date.now() - startTime;
|
|
456
|
+
|
|
457
|
+
// If we got a result with plaintext key, KMS access works
|
|
458
|
+
if (result && result.Plaintext) {
|
|
459
|
+
// eslint-disable-next-line no-console
|
|
460
|
+
console.log(`KMS key access successful, response time: ${responseTime}ms`);
|
|
461
|
+
return {
|
|
462
|
+
status: 'healthy',
|
|
463
|
+
testResult: 'Successfully requested and received decrypt key from KMS',
|
|
464
|
+
canAccessKey: true,
|
|
465
|
+
responseTime,
|
|
466
|
+
keyExists: true,
|
|
467
|
+
keyState: keyMetadata?.KeyState || 'Enabled',
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
status: 'unhealthy',
|
|
473
|
+
testResult: 'KMS responded but no key data received',
|
|
474
|
+
canAccessKey: false,
|
|
475
|
+
responseTime,
|
|
476
|
+
keyExists: keyExists,
|
|
477
|
+
};
|
|
478
|
+
} catch (error) {
|
|
479
|
+
// eslint-disable-next-line no-console
|
|
480
|
+
console.error('KMS generateDataKey failed:', error.message);
|
|
481
|
+
|
|
482
|
+
// Provide more specific error messages based on error codes
|
|
483
|
+
let testResult = `KMS access failed: ${error.message}`;
|
|
484
|
+
if (error.code === 'NotFoundException') {
|
|
485
|
+
testResult = 'KMS master key not found during data key generation';
|
|
486
|
+
// eslint-disable-next-line no-console
|
|
487
|
+
console.error('Master key does not exist in KMS');
|
|
488
|
+
} else if (error.code === 'AccessDeniedException') {
|
|
489
|
+
testResult = 'Access denied - check IAM permissions for kms:GenerateDataKey';
|
|
490
|
+
// eslint-disable-next-line no-console
|
|
491
|
+
console.error('IAM permissions insufficient for KMS operations');
|
|
492
|
+
} else if (error.code === 'InvalidKeyId.NotFound') {
|
|
493
|
+
testResult = 'Invalid KMS key ID format or key not found';
|
|
494
|
+
// eslint-disable-next-line no-console
|
|
495
|
+
console.error('KMS key ID is invalid or does not exist');
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return {
|
|
499
|
+
status: 'unhealthy',
|
|
500
|
+
testResult,
|
|
501
|
+
canAccessKey: false,
|
|
502
|
+
error: error.message,
|
|
503
|
+
errorCode: error.code,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|
|
336
508
|
const checkEncryptionHealth = async () => {
|
|
337
509
|
const config = getEncryptionConfiguration();
|
|
338
510
|
|
|
@@ -466,129 +638,81 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
466
638
|
const startTime = Date.now();
|
|
467
639
|
const response = buildHealthCheckResponse(startTime);
|
|
468
640
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
databaseResult,
|
|
472
|
-
encryptionResult,
|
|
473
|
-
externalApisResult,
|
|
474
|
-
integrationsResult,
|
|
475
|
-
] = await Promise.allSettled([
|
|
476
|
-
checkDatabaseHealth().catch((error) => ({
|
|
477
|
-
status: 'unhealthy',
|
|
478
|
-
error: error.message,
|
|
479
|
-
})),
|
|
480
|
-
checkEncryptionHealth().catch((error) => ({
|
|
481
|
-
status: 'unhealthy',
|
|
482
|
-
error: error.message,
|
|
483
|
-
})),
|
|
484
|
-
checkExternalAPIs(),
|
|
485
|
-
Promise.resolve().then(() => {
|
|
486
|
-
try {
|
|
487
|
-
return checkIntegrations();
|
|
488
|
-
} catch (error) {
|
|
489
|
-
return {
|
|
490
|
-
status: 'unhealthy',
|
|
491
|
-
error: error.message,
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
}),
|
|
495
|
-
]);
|
|
496
|
-
|
|
497
|
-
// Process database check results
|
|
498
|
-
if (databaseResult.status === 'fulfilled') {
|
|
499
|
-
response.checks.database = databaseResult.value;
|
|
641
|
+
try {
|
|
642
|
+
response.checks.database = await checkDatabaseHealth();
|
|
500
643
|
const dbState = getDatabaseState();
|
|
501
|
-
if (
|
|
502
|
-
!dbState.isConnected ||
|
|
503
|
-
response.checks.database.status === 'unhealthy'
|
|
504
|
-
) {
|
|
644
|
+
if (!dbState.isConnected) {
|
|
505
645
|
response.status = 'unhealthy';
|
|
506
646
|
}
|
|
507
647
|
// eslint-disable-next-line no-console
|
|
508
648
|
console.log('Database check completed:', response.checks.database);
|
|
509
|
-
}
|
|
649
|
+
} catch (error) {
|
|
510
650
|
response.checks.database = {
|
|
511
651
|
status: 'unhealthy',
|
|
512
|
-
error:
|
|
652
|
+
error: error.message,
|
|
513
653
|
};
|
|
514
654
|
response.status = 'unhealthy';
|
|
515
655
|
// eslint-disable-next-line no-console
|
|
516
|
-
console.log('Database check error:',
|
|
656
|
+
console.log('Database check error:', error.message);
|
|
517
657
|
}
|
|
518
658
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
response.checks.encryption = encryptionResult.value;
|
|
659
|
+
try {
|
|
660
|
+
response.checks.encryption = await checkEncryptionHealth();
|
|
522
661
|
if (response.checks.encryption.status === 'unhealthy') {
|
|
523
662
|
response.status = 'unhealthy';
|
|
524
663
|
}
|
|
525
664
|
// eslint-disable-next-line no-console
|
|
526
665
|
console.log('Encryption check completed:', response.checks.encryption);
|
|
527
|
-
}
|
|
666
|
+
} catch (error) {
|
|
528
667
|
response.checks.encryption = {
|
|
529
668
|
status: 'unhealthy',
|
|
530
|
-
error:
|
|
531
|
-
encryptionResult.reason?.message || 'Encryption check failed',
|
|
669
|
+
error: error.message,
|
|
532
670
|
};
|
|
533
671
|
response.status = 'unhealthy';
|
|
534
672
|
// eslint-disable-next-line no-console
|
|
535
|
-
console.log(
|
|
536
|
-
'Encryption check error:',
|
|
537
|
-
encryptionResult.reason?.message
|
|
538
|
-
);
|
|
673
|
+
console.log('Encryption check error:', error.message);
|
|
539
674
|
}
|
|
540
675
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
response.checks.externalApis = apiStatuses;
|
|
545
|
-
if (!allReachable) {
|
|
676
|
+
try {
|
|
677
|
+
response.checks.kmsAccess = await checkKMSAccess();
|
|
678
|
+
if (response.checks.kmsAccess.status === 'unhealthy') {
|
|
546
679
|
response.status = 'unhealthy';
|
|
547
680
|
}
|
|
548
681
|
// eslint-disable-next-line no-console
|
|
549
|
-
console.log(
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
);
|
|
553
|
-
} else {
|
|
554
|
-
response.checks.externalApis = {
|
|
682
|
+
console.log('KMS access check completed:', response.checks.kmsAccess);
|
|
683
|
+
} catch (error) {
|
|
684
|
+
response.checks.kmsAccess = {
|
|
555
685
|
status: 'unhealthy',
|
|
556
|
-
error:
|
|
557
|
-
externalApisResult.reason?.message ||
|
|
558
|
-
'External APIs check failed',
|
|
686
|
+
error: error.message,
|
|
559
687
|
};
|
|
560
688
|
response.status = 'unhealthy';
|
|
561
689
|
// eslint-disable-next-line no-console
|
|
562
|
-
console.log(
|
|
563
|
-
'External APIs check error:',
|
|
564
|
-
externalApisResult.reason?.message
|
|
565
|
-
);
|
|
690
|
+
console.log('KMS access check error:', error.message);
|
|
566
691
|
}
|
|
567
692
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
693
|
+
const { apiStatuses, allReachable } = await checkExternalAPIs();
|
|
694
|
+
response.checks.externalApis = apiStatuses;
|
|
695
|
+
if (!allReachable) {
|
|
696
|
+
response.status = 'unhealthy';
|
|
697
|
+
}
|
|
698
|
+
// eslint-disable-next-line no-console
|
|
699
|
+
console.log('External APIs check completed:', response.checks.externalApis);
|
|
700
|
+
|
|
701
|
+
try {
|
|
702
|
+
response.checks.integrations = checkIntegrations();
|
|
574
703
|
// eslint-disable-next-line no-console
|
|
575
704
|
console.log(
|
|
576
705
|
'Integrations check completed:',
|
|
577
706
|
response.checks.integrations
|
|
578
707
|
);
|
|
579
|
-
}
|
|
708
|
+
} catch (error) {
|
|
580
709
|
response.checks.integrations = {
|
|
581
710
|
status: 'unhealthy',
|
|
582
|
-
error:
|
|
583
|
-
integrationsResult.reason?.message ||
|
|
584
|
-
'Integrations check failed',
|
|
711
|
+
error: error.message,
|
|
585
712
|
};
|
|
586
713
|
response.status = 'unhealthy';
|
|
587
714
|
// eslint-disable-next-line no-console
|
|
588
|
-
console.log(
|
|
589
|
-
'Integrations check error:',
|
|
590
|
-
integrationsResult.reason?.message
|
|
591
|
-
);
|
|
715
|
+
console.log('Integrations check error:', error.message);
|
|
592
716
|
}
|
|
593
717
|
|
|
594
718
|
response.responseTime = response.calculateResponseTime();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.427.
|
|
4
|
+
"version": "2.0.0--canary.427.bd07d1c.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@hapi/boom": "^10.0.1",
|
|
7
7
|
"aws-sdk": "^2.1200.0",
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"uuid": "^9.0.1"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@friggframework/eslint-config": "2.0.0--canary.427.
|
|
26
|
-
"@friggframework/prettier-config": "2.0.0--canary.427.
|
|
27
|
-
"@friggframework/test": "2.0.0--canary.427.
|
|
25
|
+
"@friggframework/eslint-config": "2.0.0--canary.427.bd07d1c.0",
|
|
26
|
+
"@friggframework/prettier-config": "2.0.0--canary.427.bd07d1c.0",
|
|
27
|
+
"@friggframework/test": "2.0.0--canary.427.bd07d1c.0",
|
|
28
28
|
"@types/lodash": "4.17.15",
|
|
29
29
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
30
30
|
"chai": "^4.3.6",
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "bd07d1c1eee5c2cc2881ba62beeeef06d17c4abf"
|
|
60
60
|
}
|