@friggframework/core 2.0.0--canary.428.a673c47.0 → 2.0.0--canary.428.edce2a7.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 +234 -3
- 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();
|
|
@@ -416,11 +501,119 @@ const checkKmsDecryptCapability = async () => {
|
|
|
416
501
|
reason: 'KMS_KEY_ARN not configured',
|
|
417
502
|
};
|
|
418
503
|
}
|
|
504
|
+
|
|
505
|
+
// Log environment for debugging
|
|
506
|
+
console.log('KMS Check Debug:', {
|
|
507
|
+
hasKmsKeyArn: !!KMS_KEY_ARN,
|
|
508
|
+
kmsKeyArnPrefix: KMS_KEY_ARN?.substring(0, 30),
|
|
509
|
+
awsRegion: process.env.AWS_REGION,
|
|
510
|
+
awsDefaultRegion: process.env.AWS_DEFAULT_REGION,
|
|
511
|
+
hasDiscoveryKey: !!process.env.AWS_DISCOVERY_KMS_KEY_ID,
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// First, detect VPC configuration
|
|
515
|
+
const vpcConfig = await detectVpcConfiguration();
|
|
516
|
+
console.log('VPC Configuration:', vpcConfig);
|
|
517
|
+
|
|
518
|
+
// Test DNS resolution for KMS endpoint
|
|
519
|
+
try {
|
|
520
|
+
const dns = require('dns').promises;
|
|
521
|
+
const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'eu-central-1';
|
|
522
|
+
const kmsEndpoint = `kms.${region}.amazonaws.com`;
|
|
523
|
+
console.log('Testing DNS resolution for:', kmsEndpoint);
|
|
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]);
|
|
532
|
+
console.log('KMS endpoint resolved to:', addresses);
|
|
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
|
+
|
|
545
|
+
// Test TCP connectivity to KMS (port 443)
|
|
546
|
+
const net = require('net');
|
|
547
|
+
const testConnection = () => new Promise((resolve) => {
|
|
548
|
+
const socket = new net.Socket();
|
|
549
|
+
const connectionTimeout = setTimeout(() => {
|
|
550
|
+
socket.destroy();
|
|
551
|
+
resolve({ connected: false, error: 'Connection timeout' });
|
|
552
|
+
}, 3000);
|
|
553
|
+
|
|
554
|
+
socket.on('connect', () => {
|
|
555
|
+
clearTimeout(connectionTimeout);
|
|
556
|
+
socket.destroy();
|
|
557
|
+
resolve({ connected: true });
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
socket.on('error', (err) => {
|
|
561
|
+
clearTimeout(connectionTimeout);
|
|
562
|
+
resolve({ connected: false, error: err.message });
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Try connecting to first resolved address on HTTPS port
|
|
566
|
+
socket.connect(443, addresses[0]);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
const connResult = await testConnection();
|
|
570
|
+
console.log('TCP connectivity test:', connResult);
|
|
571
|
+
|
|
572
|
+
if (!connResult.connected) {
|
|
573
|
+
return {
|
|
574
|
+
status: 'unhealthy',
|
|
575
|
+
error: `Cannot connect to KMS endpoint: ${connResult.error}`,
|
|
576
|
+
dnsResolved: true,
|
|
577
|
+
tcpConnection: false,
|
|
578
|
+
vpcConfig,
|
|
579
|
+
latencyMs: Date.now() - start,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
} catch (dnsError) {
|
|
583
|
+
console.error('DNS resolution failed:', dnsError.message);
|
|
584
|
+
return {
|
|
585
|
+
status: 'unhealthy',
|
|
586
|
+
error: `Cannot resolve KMS endpoint: ${dnsError.message}`,
|
|
587
|
+
dnsResolved: false,
|
|
588
|
+
vpcConfig,
|
|
589
|
+
latencyMs: Date.now() - start,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
419
593
|
try {
|
|
420
594
|
// Lazy load to avoid cost if unused in some environments
|
|
421
595
|
// eslint-disable-next-line global-require
|
|
422
596
|
const AWS = require('aws-sdk');
|
|
423
|
-
|
|
597
|
+
|
|
598
|
+
// Ensure AWS SDK has proper configuration
|
|
599
|
+
const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
|
|
600
|
+
if (!region) {
|
|
601
|
+
return {
|
|
602
|
+
status: 'unhealthy',
|
|
603
|
+
error: 'AWS_REGION not configured',
|
|
604
|
+
latencyMs: Date.now() - start,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const kms = new AWS.KMS({
|
|
609
|
+
region,
|
|
610
|
+
httpOptions: {
|
|
611
|
+
timeout: 5000, // 5 second timeout
|
|
612
|
+
connectTimeout: 5000,
|
|
613
|
+
},
|
|
614
|
+
maxRetries: 1, // Don't retry on health checks
|
|
615
|
+
});
|
|
616
|
+
|
|
424
617
|
// Generate a data key (without plaintext logging) then immediately decrypt ciphertext to ensure decrypt perms.
|
|
425
618
|
const dataKeyResp = await kms
|
|
426
619
|
.generateDataKey({ KeyId: KMS_KEY_ARN, KeySpec: 'AES_256' })
|
|
@@ -436,12 +629,14 @@ const checkKmsDecryptCapability = async () => {
|
|
|
436
629
|
return {
|
|
437
630
|
status: success ? 'healthy' : 'unhealthy',
|
|
438
631
|
kmsKeyArnSuffix: KMS_KEY_ARN.slice(-12),
|
|
632
|
+
vpcConfig,
|
|
439
633
|
latencyMs: Date.now() - start,
|
|
440
634
|
};
|
|
441
635
|
} catch (error) {
|
|
442
636
|
return {
|
|
443
637
|
status: 'unhealthy',
|
|
444
638
|
error: error.message,
|
|
639
|
+
vpcConfig,
|
|
445
640
|
latencyMs: Date.now() - start,
|
|
446
641
|
};
|
|
447
642
|
}
|
|
@@ -463,9 +658,45 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
463
658
|
const startTime = Date.now();
|
|
464
659
|
const response = buildHealthCheckResponse(startTime);
|
|
465
660
|
|
|
466
|
-
//
|
|
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)
|
|
467
671
|
try {
|
|
468
|
-
|
|
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
|
|
694
|
+
const kmsCheckPromise = checkKmsDecryptCapability();
|
|
695
|
+
const kmsTimeoutPromise = new Promise((_, reject) =>
|
|
696
|
+
setTimeout(() => reject(new Error('KMS check timeout after 8 seconds')), 8000)
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
response.checks.kms = await Promise.race([kmsCheckPromise, kmsTimeoutPromise]);
|
|
469
700
|
if (response.checks.kms.status === 'unhealthy') {
|
|
470
701
|
response.status = 'unhealthy';
|
|
471
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.edce2a7.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.edce2a7.0",
|
|
26
|
+
"@friggframework/prettier-config": "2.0.0--canary.428.edce2a7.0",
|
|
27
|
+
"@friggframework/test": "2.0.0--canary.428.edce2a7.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": "edce2a70ea28312c91e2f5ddff77028619d7fb71"
|
|
60
60
|
}
|