@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.
@@ -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
- const kms = new AWS.KMS();
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
- // 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)
467
671
  try {
468
- 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
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.a673c47.0",
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.a673c47.0",
26
- "@friggframework/prettier-config": "2.0.0--canary.428.a673c47.0",
27
- "@friggframework/test": "2.0.0--canary.428.a673c47.0",
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": "a673c4784ce0e950d7bbfb3397f855b086524eca"
59
+ "gitHead": "edce2a70ea28312c91e2f5ddff77028619d7fb71"
60
60
  }