@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.
@@ -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
- const addresses = await dns.resolve4(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]);
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: 5000, // 5 second timeout
503
- connectTimeout: 5000,
611
+ timeout: 25000, // 25 second timeout for slow VPC connections
612
+ connectTimeout: 10000, // 10 second connection timeout
504
613
  },
505
- maxRetries: 1, // Don't retry on health checks
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
- // 1. KMS decrypt capability (must succeed before DB assumed healthy if encryption depends on KMS)
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
- response.checks.kms = await checkKmsDecryptCapability();
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.42e0806.0",
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.42e0806.0",
26
- "@friggframework/prettier-config": "2.0.0--canary.428.42e0806.0",
27
- "@friggframework/test": "2.0.0--canary.428.42e0806.0",
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": "42e0806db6ef878a5397b3e1752ae6cf21e6a845"
59
+ "gitHead": "a3d2e56038d2324c64df71c6ed49ff8e3d57873e"
60
60
  }