@friggframework/core 2.0.0--canary.428.42e0806.0 → 2.0.0--canary.428.a3d2e56.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 +153 -6
- package/package.json +5 -5
|
@@ -406,6 +406,91 @@ const buildHealthCheckResponse = (startTime) => {
|
|
|
406
406
|
};
|
|
407
407
|
};
|
|
408
408
|
|
|
409
|
+
// Helper to detect VPC configuration
|
|
410
|
+
const detectVpcConfiguration = async () => {
|
|
411
|
+
const results = {
|
|
412
|
+
isInVpc: false,
|
|
413
|
+
hasInternetAccess: false,
|
|
414
|
+
canResolvePublicDns: false,
|
|
415
|
+
canConnectToAws: false,
|
|
416
|
+
vpcEndpoints: [],
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
// Check if we're in a VPC by looking for VPC-specific environment
|
|
421
|
+
// Lambda in VPC has specific network interface configuration
|
|
422
|
+
const dns = require('dns').promises;
|
|
423
|
+
|
|
424
|
+
// Test 1: Can we resolve public DNS? (indicates DNS configuration)
|
|
425
|
+
try {
|
|
426
|
+
await Promise.race([
|
|
427
|
+
dns.resolve4('www.google.com'),
|
|
428
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 2000))
|
|
429
|
+
]);
|
|
430
|
+
results.canResolvePublicDns = true;
|
|
431
|
+
} catch (e) {
|
|
432
|
+
console.log('Public DNS resolution failed:', e.message);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Test 2: Can we reach internet? (indicates NAT gateway)
|
|
436
|
+
try {
|
|
437
|
+
const https = require('https');
|
|
438
|
+
await new Promise((resolve, reject) => {
|
|
439
|
+
const req = https.get('https://www.google.com', { timeout: 2000 }, (res) => {
|
|
440
|
+
res.destroy();
|
|
441
|
+
resolve(true);
|
|
442
|
+
});
|
|
443
|
+
req.on('error', reject);
|
|
444
|
+
req.on('timeout', () => {
|
|
445
|
+
req.destroy();
|
|
446
|
+
reject(new Error('timeout'));
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
results.hasInternetAccess = true;
|
|
450
|
+
} catch (e) {
|
|
451
|
+
console.log('Internet connectivity test failed:', e.message);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Test 3: Check for VPC endpoints by trying to resolve internal AWS endpoints
|
|
455
|
+
const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'eu-central-1';
|
|
456
|
+
const vpcEndpointDomains = [
|
|
457
|
+
`com.amazonaws.${region}.kms`,
|
|
458
|
+
`com.amazonaws.vpce.${region}`,
|
|
459
|
+
`kms.${region}.amazonaws.com`,
|
|
460
|
+
];
|
|
461
|
+
|
|
462
|
+
for (const domain of vpcEndpointDomains) {
|
|
463
|
+
try {
|
|
464
|
+
const addresses = await Promise.race([
|
|
465
|
+
dns.resolve4(domain).catch(() => dns.resolve6(domain)),
|
|
466
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 1000))
|
|
467
|
+
]);
|
|
468
|
+
if (addresses && addresses.length > 0) {
|
|
469
|
+
// Check if it's a private IP (VPC endpoint indicator)
|
|
470
|
+
const isPrivateIp = addresses.some(ip =>
|
|
471
|
+
ip.startsWith('10.') ||
|
|
472
|
+
ip.startsWith('172.') ||
|
|
473
|
+
ip.startsWith('192.168.')
|
|
474
|
+
);
|
|
475
|
+
if (isPrivateIp) {
|
|
476
|
+
results.vpcEndpoints.push(domain);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
} catch (e) {
|
|
480
|
+
// Expected for non-existent endpoints
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
results.isInVpc = !results.hasInternetAccess || results.vpcEndpoints.length > 0;
|
|
485
|
+
results.canConnectToAws = results.hasInternetAccess || results.vpcEndpoints.length > 0;
|
|
486
|
+
|
|
487
|
+
} catch (error) {
|
|
488
|
+
console.error('VPC detection error:', error.message);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return results;
|
|
492
|
+
};
|
|
493
|
+
|
|
409
494
|
// KMS decrypt capability check
|
|
410
495
|
const checkKmsDecryptCapability = async () => {
|
|
411
496
|
const start = Date.now();
|
|
@@ -426,15 +511,37 @@ const checkKmsDecryptCapability = async () => {
|
|
|
426
511
|
hasDiscoveryKey: !!process.env.AWS_DISCOVERY_KMS_KEY_ID,
|
|
427
512
|
});
|
|
428
513
|
|
|
514
|
+
// First, detect VPC configuration
|
|
515
|
+
const vpcConfig = await detectVpcConfiguration();
|
|
516
|
+
console.log('VPC Configuration:', vpcConfig);
|
|
517
|
+
|
|
429
518
|
// Test DNS resolution for KMS endpoint
|
|
430
519
|
try {
|
|
431
520
|
const dns = require('dns').promises;
|
|
432
521
|
const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'eu-central-1';
|
|
433
522
|
const kmsEndpoint = `kms.${region}.amazonaws.com`;
|
|
434
523
|
console.log('Testing DNS resolution for:', kmsEndpoint);
|
|
435
|
-
|
|
524
|
+
|
|
525
|
+
// Wrap DNS resolution in a timeout
|
|
526
|
+
const dnsPromise = dns.resolve4(kmsEndpoint);
|
|
527
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
528
|
+
setTimeout(() => reject(new Error('DNS resolution timeout')), 3000)
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
const addresses = await Promise.race([dnsPromise, timeoutPromise]);
|
|
436
532
|
console.log('KMS endpoint resolved to:', addresses);
|
|
437
533
|
|
|
534
|
+
// Check if resolved to private IP (VPC endpoint)
|
|
535
|
+
const isVpcEndpoint = addresses.some(ip =>
|
|
536
|
+
ip.startsWith('10.') ||
|
|
537
|
+
ip.startsWith('172.') ||
|
|
538
|
+
ip.startsWith('192.168.')
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
if (isVpcEndpoint) {
|
|
542
|
+
console.log('KMS VPC Endpoint detected - using private connectivity');
|
|
543
|
+
}
|
|
544
|
+
|
|
438
545
|
// Test TCP connectivity to KMS (port 443)
|
|
439
546
|
const net = require('net');
|
|
440
547
|
const testConnection = () => new Promise((resolve) => {
|
|
@@ -468,6 +575,7 @@ const checkKmsDecryptCapability = async () => {
|
|
|
468
575
|
error: `Cannot connect to KMS endpoint: ${connResult.error}`,
|
|
469
576
|
dnsResolved: true,
|
|
470
577
|
tcpConnection: false,
|
|
578
|
+
vpcConfig,
|
|
471
579
|
latencyMs: Date.now() - start,
|
|
472
580
|
};
|
|
473
581
|
}
|
|
@@ -477,6 +585,7 @@ const checkKmsDecryptCapability = async () => {
|
|
|
477
585
|
status: 'unhealthy',
|
|
478
586
|
error: `Cannot resolve KMS endpoint: ${dnsError.message}`,
|
|
479
587
|
dnsResolved: false,
|
|
588
|
+
vpcConfig,
|
|
480
589
|
latencyMs: Date.now() - start,
|
|
481
590
|
};
|
|
482
591
|
}
|
|
@@ -499,10 +608,10 @@ const checkKmsDecryptCapability = async () => {
|
|
|
499
608
|
const kms = new AWS.KMS({
|
|
500
609
|
region,
|
|
501
610
|
httpOptions: {
|
|
502
|
-
timeout:
|
|
503
|
-
connectTimeout:
|
|
611
|
+
timeout: 25000, // 25 second timeout for slow VPC connections
|
|
612
|
+
connectTimeout: 10000, // 10 second connection timeout
|
|
504
613
|
},
|
|
505
|
-
maxRetries:
|
|
614
|
+
maxRetries: 0, // No retries on health checks
|
|
506
615
|
});
|
|
507
616
|
|
|
508
617
|
// Generate a data key (without plaintext logging) then immediately decrypt ciphertext to ensure decrypt perms.
|
|
@@ -520,12 +629,14 @@ const checkKmsDecryptCapability = async () => {
|
|
|
520
629
|
return {
|
|
521
630
|
status: success ? 'healthy' : 'unhealthy',
|
|
522
631
|
kmsKeyArnSuffix: KMS_KEY_ARN.slice(-12),
|
|
632
|
+
vpcConfig,
|
|
523
633
|
latencyMs: Date.now() - start,
|
|
524
634
|
};
|
|
525
635
|
} catch (error) {
|
|
526
636
|
return {
|
|
527
637
|
status: 'unhealthy',
|
|
528
638
|
error: error.message,
|
|
639
|
+
vpcConfig,
|
|
529
640
|
latencyMs: Date.now() - start,
|
|
530
641
|
};
|
|
531
642
|
}
|
|
@@ -547,9 +658,45 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
547
658
|
const startTime = Date.now();
|
|
548
659
|
const response = buildHealthCheckResponse(startTime);
|
|
549
660
|
|
|
550
|
-
//
|
|
661
|
+
// Log environment before any async operations
|
|
662
|
+
console.log('Health Check Environment:', {
|
|
663
|
+
hasKmsKeyArn: !!process.env.KMS_KEY_ARN,
|
|
664
|
+
awsRegion: process.env.AWS_REGION,
|
|
665
|
+
awsDefaultRegion: process.env.AWS_DEFAULT_REGION,
|
|
666
|
+
nodeEnv: process.env.NODE_ENV,
|
|
667
|
+
stage: process.env.STAGE,
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// 1. Network diagnostics (run first to understand connectivity)
|
|
551
671
|
try {
|
|
552
|
-
|
|
672
|
+
console.log('Running network diagnostics...');
|
|
673
|
+
const networkStart = Date.now();
|
|
674
|
+
response.checks.network = await Promise.race([
|
|
675
|
+
detectVpcConfiguration(),
|
|
676
|
+
new Promise((_, reject) =>
|
|
677
|
+
setTimeout(() => reject(new Error('Network diagnostics timeout')), 5000)
|
|
678
|
+
)
|
|
679
|
+
]);
|
|
680
|
+
response.checks.network.latencyMs = Date.now() - networkStart;
|
|
681
|
+
console.log('Network diagnostics completed:', response.checks.network);
|
|
682
|
+
} catch (error) {
|
|
683
|
+
response.checks.network = {
|
|
684
|
+
status: 'error',
|
|
685
|
+
error: error.message,
|
|
686
|
+
};
|
|
687
|
+
console.log('Network diagnostics error:', error.message);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// 2. KMS decrypt capability (must succeed before DB assumed healthy if encryption depends on KMS)
|
|
691
|
+
try {
|
|
692
|
+
console.log('About to check KMS capability...');
|
|
693
|
+
// Wrap the entire KMS check in a timeout (allow up to 25 seconds for slow VPC)
|
|
694
|
+
const kmsCheckPromise = checkKmsDecryptCapability();
|
|
695
|
+
const kmsTimeoutPromise = new Promise((_, reject) =>
|
|
696
|
+
setTimeout(() => reject(new Error('KMS check timeout after 25 seconds')), 25000)
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
response.checks.kms = await Promise.race([kmsCheckPromise, kmsTimeoutPromise]);
|
|
553
700
|
if (response.checks.kms.status === 'unhealthy') {
|
|
554
701
|
response.status = 'unhealthy';
|
|
555
702
|
}
|
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.428.
|
|
4
|
+
"version": "2.0.0--canary.428.a3d2e56.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.428.
|
|
26
|
-
"@friggframework/prettier-config": "2.0.0--canary.428.
|
|
27
|
-
"@friggframework/test": "2.0.0--canary.428.
|
|
25
|
+
"@friggframework/eslint-config": "2.0.0--canary.428.a3d2e56.0",
|
|
26
|
+
"@friggframework/prettier-config": "2.0.0--canary.428.a3d2e56.0",
|
|
27
|
+
"@friggframework/test": "2.0.0--canary.428.a3d2e56.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": "a3d2e56038d2324c64df71c6ed49ff8e3d57873e"
|
|
60
60
|
}
|