cdk-factory 0.7.22__tar.gz → 0.7.25__tar.gz

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.

Potentially problematic release.


This version of cdk-factory might be problematic. Click here for more details.

Files changed (130) hide show
  1. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/PKG-INFO +1 -1
  2. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/pyproject.toml +1 -1
  3. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/interfaces/live_ssm_resolver.py +1 -1
  4. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/api_gateway/api_gateway_stack.py +125 -1
  5. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/utilities/api_gateway_integration_utility.py +235 -3
  6. cdk_factory-0.7.25/src/cdk_factory/version.py +1 -0
  7. cdk_factory-0.7.22/src/cdk_factory/version.py +0 -1
  8. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/.gitignore +0 -0
  9. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/LICENSE +0 -0
  10. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/README.md +0 -0
  11. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/archive/README.md +0 -0
  12. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/archive/migrate_to_enhanced_ssm.py +0 -0
  13. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/mypy.ini +0 -0
  14. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/publish_to_pypi.py +0 -0
  15. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/publish_to_pypi.sh +0 -0
  16. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/pysetup.py +0 -0
  17. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/pysetup.sh +0 -0
  18. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/requirements.dev.txt +0 -0
  19. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/requirements.tests.txt +0 -0
  20. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/requirements.txt +0 -0
  21. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/run-checks.sh +0 -0
  22. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/run-tests.sh +0 -0
  23. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/__init__.py +0 -0
  24. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/app.py +0 -0
  25. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/builds/README.md +0 -0
  26. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/cdk.json +0 -0
  27. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/commands/command_loader.py +0 -0
  28. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/base_config.py +0 -0
  29. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/cdk_config.py +0 -0
  30. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/deployment.py +0 -0
  31. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/deployment_wave.py +0 -0
  32. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/devops.py +0 -0
  33. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/enhanced_base_config.py +0 -0
  34. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/enhanced_ssm_config.py +0 -0
  35. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/management.py +0 -0
  36. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/pipeline.py +0 -0
  37. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/pipeline_stage.py +0 -0
  38. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/_resources.py +0 -0
  39. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/api_gateway.py +0 -0
  40. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/apigateway_route_config.py +0 -0
  41. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/auto_scaling.py +0 -0
  42. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/cloudfront.py +0 -0
  43. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/cloudwatch_widget.py +0 -0
  44. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/code_artifact.py +0 -0
  45. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/code_artifact_login.py +0 -0
  46. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/code_repository.py +0 -0
  47. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/cognito.py +0 -0
  48. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/docker.py +0 -0
  49. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/dynamodb.py +0 -0
  50. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/ecr.py +0 -0
  51. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/exisiting.py +0 -0
  52. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/lambda_function.py +0 -0
  53. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/lambda_layers.py +0 -0
  54. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/lambda_triggers.py +0 -0
  55. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/load_balancer.py +0 -0
  56. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/rds.py +0 -0
  57. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/resource_mapping.py +0 -0
  58. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/resource_naming.py +0 -0
  59. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/resource_types.py +0 -0
  60. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/route53.py +0 -0
  61. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/route53_hosted_zone.py +0 -0
  62. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/s3.py +0 -0
  63. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/security_group.py +0 -0
  64. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/security_group_full_stack.py +0 -0
  65. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/sqs.py +0 -0
  66. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/resources/vpc.py +0 -0
  67. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/stack.py +0 -0
  68. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/configurations/workload.py +0 -0
  69. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +0 -0
  70. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/constructs/ecr/ecr_construct.py +0 -0
  71. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/constructs/lambdas/lambda_function_construct.py +0 -0
  72. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/constructs/lambdas/lambda_function_docker_construct.py +0 -0
  73. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/constructs/lambdas/lambda_function_role_construct.py +0 -0
  74. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/constructs/lambdas/policies/policy_docs.py +0 -0
  75. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/constructs/lambdas/policies/policy_statements.py +0 -0
  76. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/constructs/s3_buckets/s3_bucket_construct.py +0 -0
  77. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/constructs/s3_buckets/s3_bucket_replication_destination_construct.py +0 -0
  78. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/constructs/s3_buckets/s3_bucket_replication_source_construct.py +0 -0
  79. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/constructs/sqs/policies/sqs_policies.py +0 -0
  80. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -0
  81. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/interfaces/istack.py +0 -0
  82. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/interfaces/ssm_parameter_mixin.py +0 -0
  83. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/lambdas/health_handler.py +0 -0
  84. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/pipeline/pipeline_factory.py +0 -0
  85. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/pipeline/security/policies.py +0 -0
  86. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/pipeline/security/roles.py +0 -0
  87. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/pipeline/stage.py +0 -0
  88. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack/istack.py +0 -0
  89. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack/stack_factory.py +0 -0
  90. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack/stack_module_loader.py +0 -0
  91. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack/stack_module_registry.py +0 -0
  92. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack/stack_modules.py +0 -0
  93. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/__init__.py +0 -0
  94. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/auto_scaling/__init__.py +0 -0
  95. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +0 -0
  96. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/aws_lambdas/lambda_stack.py +0 -0
  97. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/buckets/README.md +0 -0
  98. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/buckets/bucket_stack.py +0 -0
  99. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/code_artifact/code_artifact_stack.py +0 -0
  100. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/cognito/cognito_stack.py +0 -0
  101. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/dynamodb/dynamodb_stack.py +0 -0
  102. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/ecr/README.md +0 -0
  103. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/ecr/ecr_stack.py +0 -0
  104. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/load_balancer/__init__.py +0 -0
  105. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/load_balancer/load_balancer_stack.py +0 -0
  106. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/rds/__init__.py +0 -0
  107. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/rds/rds_stack.py +0 -0
  108. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/route53/__init__.py +0 -0
  109. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/route53/route53_stack.py +0 -0
  110. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/security_group/__init__.py +0 -0
  111. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/security_group/security_group_full_stack.py +0 -0
  112. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/security_group/security_group_stack.py +0 -0
  113. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/simple_queue_service/sqs_stack.py +0 -0
  114. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/stack_base.py +0 -0
  115. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/vpc/__init__.py +0 -0
  116. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/vpc/vpc_stack.py +0 -0
  117. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stack_library/websites/static_website_stack.py +0 -0
  118. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/stages/websites/static_website_stage.py +0 -0
  119. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/utilities/commandline_args.py +0 -0
  120. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/utilities/configuration_loader.py +0 -0
  121. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/utilities/docker_utilities.py +0 -0
  122. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/utilities/environment_services.py +0 -0
  123. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/utilities/file_operations.py +0 -0
  124. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/utilities/git_utilities.py +0 -0
  125. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/utilities/json_loading_utility.py +0 -0
  126. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/utilities/lambda_function_utilities.py +0 -0
  127. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/utilities/os_execute.py +0 -0
  128. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/utils/api_gateway_utilities.py +0 -0
  129. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/cdk_factory/workload/workload_factory.py +0 -0
  130. {cdk_factory-0.7.22 → cdk_factory-0.7.25}/src/handlers/test/handler.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdk_factory
3
- Version: 0.7.22
3
+ Version: 0.7.25
4
4
  Summary: CDK Factory. A QuickStarter and best practices setup for CDK projects
5
5
  Author-email: Eric Wilson <eric.wilson@geekcafe.com>
6
6
  License: MIT License
@@ -33,7 +33,7 @@ markers = [
33
33
  [project]
34
34
 
35
35
  name = "cdk_factory"
36
- version = "0.7.22"
36
+ version = "0.7.25"
37
37
  authors = [
38
38
  { name="Eric Wilson", email="eric.wilson@geekcafe.com" }
39
39
  ]
@@ -60,7 +60,7 @@ class LiveSsmResolver:
60
60
  Resolve SSM parameter with live API call.
61
61
 
62
62
  Args:
63
- parameter_path: SSM parameter path (e.g., /movatra/dev/cognito/user-pool/user-pool-arn)
63
+ parameter_path: SSM parameter path (e.g., /workload/dev/cognito/user-pool/user-pool-arn)
64
64
  fallback_value: Value to return if live resolution fails
65
65
 
66
66
  Returns:
@@ -347,15 +347,125 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
347
347
  # Setup CORS using centralized utility
348
348
  self.integration_utility.setup_route_cors(resource, route_path, route)
349
349
 
350
+ def _validate_authorization_configuration(self, route, has_cognito_authorizer):
351
+ """
352
+ Validate authorization configuration for security and clarity.
353
+
354
+ This method implements 'secure by default' with explicit overrides:
355
+ - If Cognito is available and route wants NONE auth, requires explicit override
356
+ - If Cognito is not available and route wants COGNITO auth, raises error
357
+ - Provides verbose warnings for monitoring and security awareness
358
+
359
+ Args:
360
+ route (dict): Route configuration
361
+ has_cognito_authorizer (bool): Whether a Cognito authorizer is configured
362
+
363
+ Raises:
364
+ ValueError: When there are security conflicts without explicit overrides
365
+ """
366
+ import logging
367
+
368
+ auth_type = route.get("authorization_type", "COGNITO")
369
+ explicit_override = route.get("allow_public_override", False)
370
+ route_path = route.get("path", "unknown")
371
+ method = route.get("method", "unknown")
372
+
373
+ logger = logging.getLogger(__name__)
374
+
375
+ # Case 1: Cognito available + NONE requested + No explicit override = ERROR
376
+ if has_cognito_authorizer and auth_type == "NONE" and not explicit_override:
377
+ error_msg = (
378
+ f"🚨 SECURITY CONFLICT DETECTED for route {route_path} ({method}):\n"
379
+ f" ❌ Cognito authorizer is configured (manual or auto-import)\n"
380
+ f" ❌ authorization_type is set to 'NONE' (public access)\n"
381
+ f" ❌ This creates a security risk - public endpoint with auth available\n\n"
382
+ f"💡 SOLUTIONS:\n"
383
+ f" 1. Remove Cognito configuration if you want public access\n"
384
+ f" 2. Add 'allow_public_override': true to explicitly allow public access\n"
385
+ f" 3. Remove 'authorization_type': 'NONE' to use secure Cognito auth\n\n"
386
+ f"🔒 This prevents accidental public endpoints when authentication is available."
387
+ )
388
+ raise ValueError(error_msg)
389
+
390
+ # Case 2: No Cognito + COGNITO explicitly requested = ERROR
391
+ # Only error if COGNITO was explicitly requested, not if it's the default
392
+ if not has_cognito_authorizer and route.get("authorization_type") == "COGNITO":
393
+ error_msg = (
394
+ f"🚨 CONFIGURATION ERROR for route {route_path} ({method}):\n"
395
+ f" ❌ authorization_type is explicitly set to 'COGNITO' but no Cognito authorizer configured\n"
396
+ f" ❌ Cannot secure endpoint without authentication provider\n\n"
397
+ f"💡 SOLUTIONS:\n"
398
+ f" 1. Add Cognito configuration to enable authentication\n"
399
+ f" 2. Set authorization_type to 'NONE' for public access\n"
400
+ f" 3. Configure SSM auto-import for user_pool_arn\n"
401
+ f" 4. Remove explicit authorization_type to use default behavior"
402
+ )
403
+ raise ValueError(error_msg)
404
+
405
+ # Case 3: Cognito available + NONE requested + Explicit override = WARN
406
+ if has_cognito_authorizer and auth_type == "NONE" and explicit_override:
407
+ warning_msg = (
408
+ f"⚠️ PUBLIC ENDPOINT CONFIGURED: {route_path} ({method})\n"
409
+ f" 🔓 This endpoint is intentionally public (allow_public_override: true)\n"
410
+ f" 🔐 Cognito authentication is available but overridden\n"
411
+ f" 📊 Consider monitoring this endpoint for unexpected usage patterns\n"
412
+ f" 🔍 Review periodically: Should this endpoint be secured?"
413
+ )
414
+
415
+ # Print to console during deployment for visibility
416
+ print(warning_msg)
417
+
418
+ # Structured logging for monitoring and metrics
419
+ logger.warning(
420
+ "Public endpoint configured with Cognito available",
421
+ extra={
422
+ "route": route_path,
423
+ "method": method,
424
+ "security_override": True,
425
+ "cognito_available": True,
426
+ "authorization_type": "NONE",
427
+ "metric_name": "public_endpoint_with_cognito",
428
+ "security_decision": "intentional_public",
429
+ "recommendation": "review_periodically"
430
+ }
431
+ )
432
+
433
+ # Case 4: No Cognito + NONE = INFO (expected for public-only APIs)
434
+ if not has_cognito_authorizer and auth_type == "NONE":
435
+ logger.info(
436
+ f"Public endpoint configured (no Cognito available): {route_path} ({method})",
437
+ extra={
438
+ "route": route_path,
439
+ "method": method,
440
+ "authorization_type": "NONE",
441
+ "cognito_available": False,
442
+ "security_decision": "public_only_api"
443
+ }
444
+ )
445
+
350
446
  def _setup_lambda_integration(
351
447
  self, api_gateway, api_id, route, lambda_fn, authorizer, suffix
352
448
  ):
353
449
  """Setup Lambda integration for a route"""
450
+ import logging
451
+
354
452
  route_path = route["path"]
355
453
  # Secure by default: require Cognito authorization unless explicitly set to NONE
356
454
  authorization_type = route.get("authorization_type", "COGNITO")
357
455
 
358
- # If explicitly set to NONE, skip authorization
456
+ # If no Cognito authorizer available and default COGNITO, fall back to NONE
457
+ if not authorizer and authorization_type == "COGNITO" and "authorization_type" not in route:
458
+ authorization_type = "NONE"
459
+ logger = logging.getLogger(__name__)
460
+ logger.info(
461
+ f"No Cognito authorizer available for route {route_path} ({route.get('method', 'unknown')}), "
462
+ f"defaulting to public access (NONE authorization)"
463
+ )
464
+
465
+ # Validate authorization configuration for security
466
+ self._validate_authorization_configuration(route, authorizer is not None)
467
+
468
+ # If set to NONE (explicitly or by fallback), skip authorization
359
469
  if authorization_type == "NONE":
360
470
  authorizer = None
361
471
 
@@ -391,9 +501,23 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
391
501
  self, api_gateway, route, lambda_fn, authorizer, api_id, suffix
392
502
  ):
393
503
  """Setup fallback Lambda integration for routes without src"""
504
+ import logging
505
+
394
506
  route_path = route["path"]
395
507
  # Secure by default: require Cognito authorization unless explicitly set to NONE
396
508
  authorization_type = route.get("authorization_type", "COGNITO")
509
+
510
+ # If no Cognito authorizer available and default COGNITO, fall back to NONE
511
+ if not authorizer and authorization_type == "COGNITO" and "authorization_type" not in route:
512
+ authorization_type = "NONE"
513
+ logger = logging.getLogger(__name__)
514
+ logger.info(
515
+ f"No Cognito authorizer available for route {route_path} ({route.get('method', 'unknown')}), "
516
+ f"defaulting to public access (NONE authorization)"
517
+ )
518
+
519
+ # Validate authorization configuration for security
520
+ self._validate_authorization_configuration(route, authorizer is not None)
397
521
 
398
522
  resource = (
399
523
  api_gateway.root.resource_for_path(route_path)
@@ -54,6 +54,17 @@ class ApiGatewayIntegrationUtility:
54
54
  if not api_config:
55
55
  raise ValueError("API Gateway config is missing in Lambda function config")
56
56
 
57
+ # Validate authorization configuration for security
58
+ has_cognito_authorizer = (
59
+ self.authorizer is not None or
60
+ self._get_existing_authorizer_id_with_ssm_fallback(api_config, stack_config) is not None
61
+ )
62
+
63
+ # Apply enhanced authorization validation and fallback logic
64
+ api_config = self._validate_and_adjust_authorization_configuration(
65
+ api_config, has_cognito_authorizer
66
+ )
67
+
57
68
  # Get or create authorizer if needed (only for COGNITO_USER_POOLS authorization)
58
69
  if api_config.authorization_type != "NONE" and not self.authorizer:
59
70
  self.authorizer = self.get_or_create_authorizer(
@@ -68,7 +79,7 @@ class ApiGatewayIntegrationUtility:
68
79
  )
69
80
 
70
81
  # Add method to API Gateway
71
- resource = self.get_or_create_resource(api_gateway, api_config.routes)
82
+ resource = self.get_or_create_resource(api_gateway, api_config.routes, stack_config)
72
83
 
73
84
  # Handle existing authorizer ID using L1 constructs
74
85
  if self._get_existing_authorizer_id_with_ssm_fallback(api_config, stack_config):
@@ -602,12 +613,20 @@ class ApiGatewayIntegrationUtility:
602
613
  return self.authorizer
603
614
 
604
615
  def get_or_create_resource(
605
- self, api_gateway: apigateway.RestApi, route_path: str
616
+ self, api_gateway: apigateway.RestApi, route_path: str, stack_config=None
606
617
  ) -> apigateway.Resource:
607
- """Get or create API Gateway resource for the given route path"""
618
+ """Get or create API Gateway resource for the given route path with cross-stack support"""
608
619
  if not route_path or route_path == "/":
609
620
  return api_gateway.root
610
621
 
622
+ # Check for existing resource import configuration
623
+ if stack_config:
624
+ api_gateway_config = stack_config.dictionary.get("api_gateway", {})
625
+ existing_resources = api_gateway_config.get("existing_resources", {})
626
+
627
+ if existing_resources:
628
+ return self._create_resource_with_imports(api_gateway, route_path, existing_resources)
629
+
611
630
  # Use the built-in resource_for_path method which handles existing resources correctly
612
631
  try:
613
632
  # This method automatically creates the full path and reuses existing resources
@@ -658,6 +677,94 @@ class ApiGatewayIntegrationUtility:
658
677
 
659
678
  return current_resource
660
679
 
680
+ def _create_resource_with_imports(
681
+ self, api_gateway: apigateway.RestApi, route_path: str, existing_resources: dict
682
+ ) -> apigateway.Resource:
683
+ """Create resource path using existing resource imports to avoid conflicts"""
684
+ from aws_cdk import aws_apigateway as apigateway
685
+
686
+ # Remove leading slash and split path
687
+ path_parts = route_path.lstrip("/").split("/")
688
+ current_resource = api_gateway.root
689
+ current_path = ""
690
+
691
+ # Navigate through path parts, importing existing resources where configured
692
+ for i, part in enumerate(path_parts):
693
+ if not part: # Skip empty parts
694
+ continue
695
+
696
+ current_path = "/" + "/".join(path_parts[:i+1])
697
+
698
+ # Check if this path segment should be imported from existing resources
699
+ if current_path in existing_resources:
700
+ resource_config = existing_resources[current_path]
701
+ resource_id = resource_config.get("resource_id")
702
+
703
+ if resource_id:
704
+ logger.info(f"Importing existing resource for path {current_path} with ID: {resource_id}")
705
+
706
+ # Import the existing resource using L1 constructs
707
+ current_resource = self._import_existing_resource(
708
+ api_gateway, current_resource, part, resource_id, current_path
709
+ )
710
+ else:
711
+ # Create normally if no resource_id specified
712
+ current_resource = self._add_resource_safely(current_resource, part)
713
+ else:
714
+ # Create normally for non-imported paths
715
+ current_resource = self._add_resource_safely(current_resource, part)
716
+
717
+ return current_resource
718
+
719
+ def _import_existing_resource(
720
+ self, api_gateway: apigateway.RestApi, parent_resource: apigateway.Resource,
721
+ path_part: str, resource_id: str, full_path: str
722
+ ) -> apigateway.Resource:
723
+ """Import an existing API Gateway resource by ID"""
724
+ from aws_cdk import aws_apigateway as apigateway
725
+
726
+ try:
727
+ # Use CfnResource to reference existing resource
728
+ # This creates a reference without trying to create the resource
729
+ imported_resource = apigateway.Resource.from_resource_id(
730
+ self.scope,
731
+ f"imported-resource-{hash(full_path) % 10000}",
732
+ resource_id
733
+ )
734
+
735
+ logger.info(f"Successfully imported existing resource: {path_part} (ID: {resource_id})")
736
+ return imported_resource
737
+
738
+ except Exception as e:
739
+ logger.warning(f"Failed to import resource {path_part} with ID {resource_id}: {e}")
740
+ # Fallback to normal creation
741
+ return self._add_resource_safely(parent_resource, path_part)
742
+
743
+ def _add_resource_safely(
744
+ self, parent_resource: apigateway.Resource, path_part: str
745
+ ) -> apigateway.Resource:
746
+ """Add resource with conflict handling"""
747
+ try:
748
+ return parent_resource.add_resource(path_part)
749
+ except Exception as e:
750
+ if "AlreadyExists" in str(e) or "same parent already has this name" in str(e):
751
+ logger.warning(f"Resource {path_part} already exists, attempting to find existing resource")
752
+
753
+ # Try to find the existing resource in children
754
+ for child in parent_resource.node.children:
755
+ if (
756
+ hasattr(child, "path_part")
757
+ and getattr(child, "path_part", None) == path_part
758
+ ):
759
+ logger.info(f"Found existing resource: {path_part}")
760
+ return child
761
+
762
+ # If not found in children, re-raise the error
763
+ logger.error(f"Could not find or create resource: {path_part}")
764
+ raise e
765
+ else:
766
+ raise e
767
+
661
768
  def _get_existing_api_gateway_id_with_ssm_fallback(
662
769
  self, api_config: ApiGatewayConfigRouteConfig, stack_config
663
770
  ) -> Optional[str]:
@@ -1198,3 +1305,128 @@ class ApiGatewayIntegrationUtility:
1198
1305
  api_gateways[api_key]['integrations'].append(integration)
1199
1306
 
1200
1307
  return api_gateways
1308
+
1309
+ def _validate_and_adjust_authorization_configuration(
1310
+ self, api_config: ApiGatewayConfigRouteConfig, has_cognito_authorizer: bool
1311
+ ) -> ApiGatewayConfigRouteConfig:
1312
+ """
1313
+ Validate and adjust authorization configuration for security and clarity.
1314
+
1315
+ This method implements 'secure by default' with explicit overrides:
1316
+ - If Cognito is available and route wants NONE auth, requires explicit override
1317
+ - If Cognito is not available and route wants COGNITO auth, raises error
1318
+ - Provides verbose warnings for monitoring and security awareness
1319
+ - Returns a potentially modified api_config with adjusted authorization_type
1320
+
1321
+ Args:
1322
+ api_config (ApiGatewayConfigRouteConfig): Route configuration
1323
+ has_cognito_authorizer (bool): Whether a Cognito authorizer is configured
1324
+
1325
+ Returns:
1326
+ ApiGatewayConfigRouteConfig: Potentially modified configuration
1327
+
1328
+ Raises:
1329
+ ValueError: When there are security conflicts without explicit overrides
1330
+ """
1331
+ import logging
1332
+ from copy import deepcopy
1333
+
1334
+ # Create a copy to avoid modifying the original
1335
+ modified_config = deepcopy(api_config)
1336
+
1337
+ auth_type = getattr(api_config, 'authorization_type', 'COGNITO')
1338
+
1339
+ # Check for explicit override flag (need to check the original route dictionary)
1340
+ explicit_override = False
1341
+ if hasattr(api_config, 'dictionary') and api_config.dictionary:
1342
+ explicit_override = api_config.dictionary.get('allow_public_override', False)
1343
+
1344
+ route_path = getattr(api_config, 'routes', 'unknown')
1345
+ method = getattr(api_config, 'method', 'unknown')
1346
+
1347
+ logger = logging.getLogger(__name__)
1348
+
1349
+ # Case 1: Cognito available + NONE requested + No explicit override = ERROR
1350
+ if has_cognito_authorizer and auth_type == "NONE" and not explicit_override:
1351
+ error_msg = (
1352
+ f"🚨 SECURITY CONFLICT DETECTED for route {route_path} ({method}):\n"
1353
+ f" ❌ Cognito authorizer is configured (manual or auto-import)\n"
1354
+ f" ❌ authorization_type is set to 'NONE' (public access)\n"
1355
+ f" ❌ This creates a security risk - public endpoint with auth available\n\n"
1356
+ f"💡 SOLUTIONS:\n"
1357
+ f" 1. Remove Cognito configuration if you want public access\n"
1358
+ f" 2. Add 'allow_public_override': true to explicitly allow public access\n"
1359
+ f" 3. Remove 'authorization_type': 'NONE' to use secure Cognito auth\n\n"
1360
+ f"🔒 This prevents accidental public endpoints when authentication is available."
1361
+ )
1362
+ raise ValueError(error_msg)
1363
+
1364
+ # Case 2: No Cognito + COGNITO explicitly requested = ERROR
1365
+ # Only error if COGNITO was explicitly requested, not if it's the default
1366
+ original_auth_type = None
1367
+ if hasattr(api_config, 'dictionary') and api_config.dictionary:
1368
+ original_auth_type = api_config.dictionary.get('authorization_type')
1369
+
1370
+ if not has_cognito_authorizer and original_auth_type == "COGNITO":
1371
+ error_msg = (
1372
+ f"🚨 CONFIGURATION ERROR for route {route_path} ({method}):\n"
1373
+ f" ❌ authorization_type is explicitly set to 'COGNITO' but no Cognito authorizer configured\n"
1374
+ f" ❌ Cannot secure endpoint without authentication provider\n\n"
1375
+ f"💡 SOLUTIONS:\n"
1376
+ f" 1. Add Cognito configuration to enable authentication\n"
1377
+ f" 2. Set authorization_type to 'NONE' for public access\n"
1378
+ f" 3. Configure SSM auto-import for user_pool_arn\n"
1379
+ f" 4. Remove explicit authorization_type to use default behavior"
1380
+ )
1381
+ raise ValueError(error_msg)
1382
+
1383
+ # Case 3: Cognito available + NONE requested + Explicit override = WARN
1384
+ if has_cognito_authorizer and auth_type == "NONE" and explicit_override:
1385
+ warning_msg = (
1386
+ f"⚠️ PUBLIC ENDPOINT CONFIGURED: {route_path} ({method})\n"
1387
+ f" 🔓 This endpoint is intentionally public (allow_public_override: true)\n"
1388
+ f" 🔐 Cognito authentication is available but overridden\n"
1389
+ f" 📊 Consider monitoring this endpoint for unexpected usage patterns\n"
1390
+ f" 🔍 Review periodically: Should this endpoint be secured?"
1391
+ )
1392
+
1393
+ # Print to console during deployment for visibility
1394
+ print(warning_msg)
1395
+
1396
+ # Structured logging for monitoring and metrics
1397
+ logger.warning(
1398
+ "Public endpoint configured with Cognito available",
1399
+ extra={
1400
+ "route": route_path,
1401
+ "method": method,
1402
+ "security_override": True,
1403
+ "cognito_available": True,
1404
+ "authorization_type": "NONE",
1405
+ "metric_name": "public_endpoint_with_cognito",
1406
+ "security_decision": "intentional_public",
1407
+ "recommendation": "review_periodically"
1408
+ }
1409
+ )
1410
+
1411
+ # Case 4: No Cognito + default COGNITO = Fall back to NONE
1412
+ if not has_cognito_authorizer and auth_type == "COGNITO" and original_auth_type is None:
1413
+ modified_config.authorization_type = "NONE"
1414
+ logger.info(
1415
+ f"No Cognito authorizer available for route {route_path} ({method}), "
1416
+ f"defaulting to public access (NONE authorization)"
1417
+ )
1418
+
1419
+ # Case 5: No Cognito + NONE = INFO (expected for public-only APIs)
1420
+ if not has_cognito_authorizer and auth_type == "NONE":
1421
+ logger.info(
1422
+ f"Public endpoint configured (no Cognito available): {route_path} ({method})",
1423
+ extra={
1424
+ "route": route_path,
1425
+ "method": method,
1426
+ "authorization_type": "NONE",
1427
+ "cognito_available": False,
1428
+ "security_decision": "public_only_api"
1429
+ }
1430
+ )
1431
+
1432
+ return modified_config
@@ -0,0 +1 @@
1
+ __version__ = "0.7.25"
@@ -1 +0,0 @@
1
- __version__ = "0.7.22"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes