cdk-factory 0.15.10__py3-none-any.whl → 0.18.9__py3-none-any.whl

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 (63) hide show
  1. cdk_factory/configurations/base_config.py +23 -24
  2. cdk_factory/configurations/cdk_config.py +6 -4
  3. cdk_factory/configurations/deployment.py +12 -0
  4. cdk_factory/configurations/devops.py +1 -1
  5. cdk_factory/configurations/pipeline_stage.py +29 -5
  6. cdk_factory/configurations/resources/acm.py +85 -0
  7. cdk_factory/configurations/resources/auto_scaling.py +7 -5
  8. cdk_factory/configurations/resources/cloudfront.py +7 -2
  9. cdk_factory/configurations/resources/ecr.py +1 -1
  10. cdk_factory/configurations/resources/ecs_cluster.py +108 -0
  11. cdk_factory/configurations/resources/ecs_service.py +17 -2
  12. cdk_factory/configurations/resources/load_balancer.py +17 -4
  13. cdk_factory/configurations/resources/monitoring.py +8 -3
  14. cdk_factory/configurations/resources/rds.py +305 -19
  15. cdk_factory/configurations/resources/rum.py +7 -2
  16. cdk_factory/configurations/resources/s3.py +1 -1
  17. cdk_factory/configurations/resources/security_group_full_stack.py +7 -8
  18. cdk_factory/configurations/resources/vpc.py +19 -0
  19. cdk_factory/configurations/workload.py +32 -2
  20. cdk_factory/constructs/ecr/ecr_construct.py +9 -2
  21. cdk_factory/constructs/lambdas/policies/policy_docs.py +4 -4
  22. cdk_factory/interfaces/istack.py +6 -3
  23. cdk_factory/interfaces/networked_stack_mixin.py +75 -0
  24. cdk_factory/interfaces/standardized_ssm_mixin.py +657 -0
  25. cdk_factory/interfaces/vpc_provider_mixin.py +210 -0
  26. cdk_factory/lambdas/edge/ip_gate/handler.py +42 -40
  27. cdk_factory/pipeline/pipeline_factory.py +222 -27
  28. cdk_factory/stack/stack_factory.py +34 -0
  29. cdk_factory/stack_library/__init__.py +3 -2
  30. cdk_factory/stack_library/acm/__init__.py +6 -0
  31. cdk_factory/stack_library/acm/acm_stack.py +169 -0
  32. cdk_factory/stack_library/api_gateway/api_gateway_stack.py +84 -59
  33. cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +366 -408
  34. cdk_factory/stack_library/code_artifact/code_artifact_stack.py +2 -2
  35. cdk_factory/stack_library/cognito/cognito_stack.py +152 -92
  36. cdk_factory/stack_library/dynamodb/dynamodb_stack.py +19 -15
  37. cdk_factory/stack_library/ecr/ecr_stack.py +2 -2
  38. cdk_factory/stack_library/ecs/__init__.py +12 -0
  39. cdk_factory/stack_library/ecs/ecs_cluster_stack.py +316 -0
  40. cdk_factory/stack_library/ecs/ecs_service_stack.py +20 -39
  41. cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +2 -2
  42. cdk_factory/stack_library/load_balancer/load_balancer_stack.py +151 -118
  43. cdk_factory/stack_library/rds/rds_stack.py +85 -74
  44. cdk_factory/stack_library/route53/route53_stack.py +8 -3
  45. cdk_factory/stack_library/rum/rum_stack.py +108 -91
  46. cdk_factory/stack_library/security_group/security_group_full_stack.py +9 -22
  47. cdk_factory/stack_library/security_group/security_group_stack.py +11 -11
  48. cdk_factory/stack_library/stack_base.py +5 -0
  49. cdk_factory/stack_library/vpc/vpc_stack.py +272 -124
  50. cdk_factory/stack_library/websites/static_website_stack.py +1 -1
  51. cdk_factory/utilities/api_gateway_integration_utility.py +24 -16
  52. cdk_factory/utilities/environment_services.py +5 -5
  53. cdk_factory/utilities/json_loading_utility.py +12 -3
  54. cdk_factory/validation/config_validator.py +483 -0
  55. cdk_factory/version.py +1 -1
  56. cdk_factory/workload/workload_factory.py +1 -0
  57. {cdk_factory-0.15.10.dist-info → cdk_factory-0.18.9.dist-info}/METADATA +1 -1
  58. {cdk_factory-0.15.10.dist-info → cdk_factory-0.18.9.dist-info}/RECORD +61 -54
  59. cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -321
  60. cdk_factory/interfaces/ssm_parameter_mixin.py +0 -329
  61. {cdk_factory-0.15.10.dist-info → cdk_factory-0.18.9.dist-info}/WHEEL +0 -0
  62. {cdk_factory-0.15.10.dist-info → cdk_factory-0.18.9.dist-info}/entry_points.txt +0 -0
  63. {cdk_factory-0.15.10.dist-info → cdk_factory-0.18.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,210 @@
1
+ """
2
+ VPC Provider Mixin - Reusable VPC resolution functionality
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for license information.
5
+ """
6
+
7
+ from typing import Optional, List, Any
8
+ from aws_lambda_powertools import Logger
9
+ from aws_cdk import aws_ec2 as ec2, aws_ssm as ssm
10
+ from constructs import Construct
11
+
12
+ logger = Logger(__name__)
13
+
14
+
15
+ class VPCProviderMixin:
16
+ """
17
+ Mixin class that provides reusable VPC resolution functionality for stacks.
18
+
19
+ This mixin eliminates code duplication across stacks that need to resolve
20
+ VPC references, providing a standardized way to handle:
21
+ - SSM imported VPC parameters (works with enhanced StandardizedSsmMixin)
22
+ - Configuration-based VPC resolution
23
+ - Workload-level VPC fallback
24
+ - Error handling and validation
25
+
26
+ Note: This mixin does NOT handle SSM imports directly - it expects
27
+ the SSM values to be available via the enhanced StandardizedSsmMixin.
28
+ """
29
+
30
+ def _initialize_vpc_cache(self) -> None:
31
+ """Initialize the VPC cache attribute"""
32
+ if not hasattr(self, '_vpc'):
33
+ self._vpc: Optional[ec2.IVpc] = None
34
+
35
+ def resolve_vpc(
36
+ self,
37
+ config: Any,
38
+ deployment: Any,
39
+ workload: Any,
40
+ availability_zones: Optional[List[str]] = None
41
+ ) -> ec2.IVpc:
42
+ """
43
+ Resolve VPC from multiple sources with standardized priority order.
44
+
45
+ Priority order:
46
+ 1. SSM imported VPC ID (from config.ssm.imports)
47
+ 2. Config-level VPC ID
48
+ 3. Workload-level VPC ID
49
+ 4. Raise error if none found
50
+
51
+ Args:
52
+ config: The stack configuration
53
+ deployment: The deployment configuration
54
+ workload: The workload configuration
55
+ availability_zones: Optional AZ list for VPC attributes
56
+
57
+ Returns:
58
+ Resolved VPC reference
59
+
60
+ Raises:
61
+ ValueError: If no VPC configuration is found
62
+ """
63
+ if self._vpc:
64
+ return self._vpc
65
+
66
+ # Default availability zones if not provided - use region-appropriate defaults
67
+ if not availability_zones:
68
+ region = getattr(deployment, 'region', 'us-east-1')
69
+ availability_zones = self._get_default_azs_for_region(region)
70
+
71
+ # Check SSM imports directly from config (source of truth)
72
+ ssm_config = getattr(config, 'ssm', {})
73
+ ssm_imports = ssm_config.get('imports', {})
74
+
75
+ if ssm_imports and "vpc_id" in ssm_imports:
76
+ vpc_id = ssm_imports["vpc_id"]
77
+
78
+ # Get subnet IDs first to determine AZ count
79
+ subnet_ids = []
80
+ if "subnet_ids" in ssm_imports:
81
+ imported_subnets = ssm_imports["subnet_ids"]
82
+ if isinstance(imported_subnets, str):
83
+ subnet_ids = [s.strip() for s in imported_subnets.split(",") if s.strip()]
84
+ elif isinstance(imported_subnets, list):
85
+ subnet_ids = imported_subnets
86
+
87
+ # Adjust availability zones to match subnet count
88
+ if subnet_ids and availability_zones:
89
+ region = getattr(deployment, 'region', 'us-east-1')
90
+ region_code = region.split('-')[0] + '-' + region.split('-')[1]
91
+ availability_zones = [f"{region_code}{chr(97+i)}" for i in range(len(subnet_ids))]
92
+
93
+ return self._create_vpc_from_ssm(vpc_id, availability_zones, subnet_ids if subnet_ids else None)
94
+
95
+ # Check config-level VPC ID
96
+ if hasattr(config, 'vpc_id') and config.vpc_id:
97
+ return ec2.Vpc.from_lookup(self, f"{self.stack_name}-VPC", vpc_id=config.vpc_id)
98
+
99
+ # Check workload-level VPC ID
100
+ if hasattr(workload, 'vpc_id') and workload.vpc_id:
101
+ return ec2.Vpc.from_lookup(self, f"{self.stack_name}-VPC", vpc_id=workload.vpc_id)
102
+
103
+ # No VPC found - raise descriptive error
104
+ raise self._create_vpc_not_found_error(config, workload)
105
+
106
+ def _create_vpc_from_ssm(
107
+ self,
108
+ vpc_id: str,
109
+ availability_zones: List[str],
110
+ subnet_ids: Optional[List[str]] = None
111
+ ) -> ec2.IVpc:
112
+ """
113
+ Create VPC reference from SSM imported VPC ID.
114
+
115
+ Args:
116
+ vpc_id: The VPC ID from SSM (can be SSM path or actual VPC ID)
117
+ availability_zones: List of availability zones
118
+ subnet_ids: Optional list of subnet IDs from SSM
119
+
120
+ Returns:
121
+ VPC reference created from attributes
122
+ """
123
+ # Check if vpc_id is an SSM path (starts with /) or actual VPC ID
124
+ if vpc_id.startswith('/'):
125
+ # Create CDK token for VPC ID from SSM parameter
126
+ vpc_id_token = ssm.StringParameter.from_string_parameter_name(
127
+ self, f"{self.stack_name}-VPC-ID-Token", vpc_id
128
+ ).string_value
129
+ else:
130
+ # Use the VPC ID directly (for testing or direct configuration)
131
+ vpc_id_token = vpc_id
132
+
133
+ # Build VPC attributes
134
+ vpc_attrs = {
135
+ "vpc_id": vpc_id_token,
136
+ "availability_zones": availability_zones,
137
+ }
138
+
139
+ # If we have subnet_ids from SSM, add them to the attributes
140
+ if subnet_ids:
141
+ # Use the actual subnet IDs from SSM
142
+ vpc_attrs["public_subnet_ids"] = subnet_ids
143
+
144
+ # Use from_vpc_attributes() for SSM tokens with unique construct name
145
+ self._vpc = ec2.Vpc.from_vpc_attributes(self, f"{self.stack_name}-VPC", **vpc_attrs)
146
+ return self._vpc
147
+
148
+ def _create_vpc_not_found_error(self, config: Any, workload: Any) -> ValueError:
149
+ """
150
+ Create a descriptive error message for missing VPC configuration.
151
+
152
+ Args:
153
+ config: The stack configuration
154
+ workload: The workload configuration
155
+
156
+ Returns:
157
+ ValueError with descriptive message
158
+ """
159
+ config_name = getattr(config, 'name', 'unknown')
160
+ workload_name = getattr(workload, 'name', 'unknown')
161
+
162
+ return ValueError(
163
+ f"VPC is not defined in the configuration for {config_name}. "
164
+ f"You can provide it at the following locations:\n"
165
+ f" 1. As an SSM import: config.ssm_imports.vpc_id\n"
166
+ f" 2. At the config level: config.vpc_id\n"
167
+ f" 3. At the workload level: workload.vpc_id\n"
168
+ f"Current workload: {workload_name}"
169
+ )
170
+
171
+ def get_vpc_property(self, config: Any, deployment: Any, workload: Any) -> ec2.IVpc:
172
+ """
173
+ Standard VPC property implementation that can be used by stacks.
174
+
175
+ Args:
176
+ config: The stack configuration
177
+ deployment: The deployment configuration
178
+ workload: The workload configuration
179
+
180
+ Returns:
181
+ Resolved VPC reference
182
+ """
183
+ return self.resolve_vpc(config, deployment, workload)
184
+
185
+ def _get_default_azs_for_region(self, region: str) -> List[str]:
186
+ """
187
+ Get default availability zones for a given region.
188
+
189
+ Args:
190
+ region: AWS region name (e.g., 'us-east-1', 'us-west-2')
191
+
192
+ Returns:
193
+ List of availability zone names for the region
194
+ """
195
+ # Common AZ mappings for major AWS regions
196
+ region_az_map = {
197
+ 'us-east-1': ['us-east-1a', 'us-east-1b'],
198
+ 'us-east-2': ['us-east-2a', 'us-east-2b'],
199
+ 'us-west-1': ['us-west-1a', 'us-west-1b'],
200
+ 'us-west-2': ['us-west-2a', 'us-west-2b'],
201
+ 'eu-west-1': ['eu-west-1a', 'eu-west-1b'],
202
+ 'eu-west-2': ['eu-west-2a', 'eu-west-2b'],
203
+ 'eu-central-1': ['eu-central-1a', 'eu-central-1b'],
204
+ 'ap-southeast-1': ['ap-southeast-1a', 'ap-southeast-1b'],
205
+ 'ap-southeast-2': ['ap-southeast-2a', 'ap-southeast-2b'],
206
+ 'ap-northeast-1': ['ap-northeast-1a', 'ap-northeast-1b'],
207
+ }
208
+
209
+ # Return region-specific AZs if available, otherwise fall back to us-east-1
210
+ return region_az_map.get(region, ['us-east-1a', 'us-east-1b'])
@@ -11,11 +11,18 @@ import ipaddress
11
11
  import boto3
12
12
  from functools import lru_cache
13
13
 
14
+ # Simple logging for Lambda@Edge (since we can't use external logging libraries)
15
+ import logging
16
+
17
+ # Configure basic logging
18
+ logger = logging.getLogger()
19
+ logger.setLevel(logging.INFO)
20
+
14
21
  # SSM client - will be created in the region where the function executes
15
22
  ssm = None
16
23
 
17
24
  @lru_cache(maxsize=128)
18
- def get_ssm_parameter(parameter_name: str, region: str = 'us-east-1', default: str = None) -> str:
25
+ def get_ssm_parameter(parameter_name: str, region: str = None, default: str = None) -> str:
19
26
  """
20
27
  Fetch SSM parameter with caching.
21
28
  Lambda@Edge cannot use environment variables, so we fetch from SSM.
@@ -23,14 +30,18 @@ def get_ssm_parameter(parameter_name: str, region: str = 'us-east-1', default: s
23
30
  The sentinel value 'NONE' indicates an explicitly unset/disabled parameter.
24
31
 
25
32
  Args:
26
- parameter_name: Name of the SSM parameter
27
- region: AWS region (default us-east-1)
28
- default: Default value to return if parameter not found (optional)
33
+ parameter_name: SSM parameter name
34
+ region: AWS region (defaults to us-east-1 for Lambda@Edge compatibility)
35
+ default: Default value if parameter not found
29
36
 
30
37
  Returns:
31
38
  Parameter value, default value if parameter not found, or empty string if value is 'NONE'
32
39
  """
33
40
  global ssm
41
+ # Default to us-east-1 for Lambda@Edge compatibility if no region specified
42
+ if region is None:
43
+ region = 'us-east-1'
44
+
34
45
  if ssm is None:
35
46
  ssm = boto3.client('ssm', region_name=region)
36
47
 
@@ -40,20 +51,20 @@ def get_ssm_parameter(parameter_name: str, region: str = 'us-east-1', default: s
40
51
 
41
52
  # Treat 'NONE' sentinel as empty/unset
42
53
  if value == 'NONE':
43
- print(f"SSM parameter {parameter_name} is set to 'NONE' (explicitly disabled)")
54
+ logger.info(f"SSM parameter {parameter_name} is set to 'NONE' (explicitly disabled)")
44
55
  return ''
45
56
 
46
57
  return value
47
58
  except ssm.exceptions.ParameterNotFound:
48
59
  if default is not None:
49
- print(f"SSM parameter {parameter_name} not found, using default: {default}")
60
+ logger.info(f"SSM parameter {parameter_name} not found, using default: {default}")
50
61
  return default
51
- print(f"SSM parameter {parameter_name} not found and no default provided")
62
+ logger.error(f"SSM parameter {parameter_name} not found and no default provided")
52
63
  raise
53
64
  except Exception as e:
54
- print(f"Error fetching SSM parameter {parameter_name}: {str(e)}")
65
+ logger.error(f"Error fetching SSM parameter {parameter_name}: {str(e)}")
55
66
  if default is not None:
56
- print(f"Returning default value due to error: {default}")
67
+ logger.info(f"Returning default value due to error: {default}")
57
68
  return default
58
69
  raise
59
70
 
@@ -89,7 +100,7 @@ def is_ip_allowed(client_ip: str, allowed_cidrs: list) -> bool:
89
100
  try:
90
101
  client_ip_obj = ipaddress.ip_address(client_ip)
91
102
  except ValueError as e:
92
- print(f"Invalid client IP address: {e}")
103
+ logger.error(f"Invalid client IP address: {e}")
93
104
  return False
94
105
 
95
106
  # Check each CIDR individually, skipping invalid ones
@@ -100,7 +111,7 @@ def is_ip_allowed(client_ip: str, allowed_cidrs: list) -> bool:
100
111
  return True
101
112
  except ValueError as e:
102
113
  # Invalid CIDR, log and continue checking others
103
- print(f"Invalid CIDR '{cidr}': {e}")
114
+ logger.warning(f"Invalid CIDR '{cidr}': {e}")
104
115
  continue
105
116
 
106
117
  return False
@@ -130,24 +141,23 @@ def lambda_handler(event, context):
130
141
  env = runtime_config.get('environment', 'dev')
131
142
  function_base_name = runtime_config.get('function_name', 'ip-gate')
132
143
 
133
- print(f"Runtime config loaded: environment={env}, function_name={function_base_name}")
144
+ logger.info(f"Runtime config loaded: environment={env}, function_name={function_base_name}")
134
145
  except FileNotFoundError:
135
146
  # Fallback: extract from Lambda context (less reliable)
136
- print("Warning: runtime_config.json not found, falling back to function name parsing")
147
+ logger.warning("runtime_config.json not found, falling back to function name parsing")
137
148
  function_full_name = context.function_name
138
149
 
139
150
  # Parse environment from function name as fallback
140
151
  parts = function_full_name.split('-')
141
152
  common_envs = ['dev', 'prod', 'staging', 'test', 'qa', 'uat']
142
- env = 'dev'
143
-
153
+ env = 'dev' # Default fallback
144
154
  for part in parts:
145
155
  if part in common_envs:
146
156
  env = part
147
157
  break
148
158
 
149
159
  function_base_name = 'ip-gate'
150
- print(f"Fallback: environment={env}, function_name={function_base_name}")
160
+ logger.info(f"Fallback: environment={env}, function_name={function_base_name}")
151
161
 
152
162
  # Full function name for SSM paths
153
163
  # Lambda@Edge replicas prepend region (e.g., "us-east-1.tech-talk-dev-ip-gate")
@@ -156,46 +166,46 @@ def lambda_handler(event, context):
156
166
  if '.' in function_name and function_name.split('.')[0].startswith('us-'):
157
167
  # Strip region prefix (e.g., "us-east-1." -> "tech-talk-dev-ip-gate")
158
168
  function_name = '.'.join(function_name.split('.')[1:])
159
- print(f"Stripped region prefix from function name: {function_name}")
169
+ logger.info(f"Stripped region prefix from function name: {function_name}")
160
170
 
161
- print(f"Lambda function ARN: {context.invoked_function_arn}")
162
- print(f"Using function name for SSM lookups: {function_name}")
171
+ logger.info(f"Lambda function ARN: {context.invoked_function_arn}")
172
+ logger.info(f"Using function name for SSM lookups: {function_name}")
163
173
 
164
174
  try:
165
175
  # Fetch configuration from SSM Parameter Store
166
176
  # Auto-generated paths: /{env}/{function-name}/{key}
167
- gate_enabled = get_ssm_parameter(f'/{env}/{function_name}/gate-enabled', 'us-east-1')
177
+ gate_enabled = get_ssm_parameter(f'/{env}/{function_name}/gate-enabled', region='us-east-1')
168
178
 
169
179
  # If gating is disabled, allow all traffic
170
180
  # Empty string (from 'NONE' sentinel) is treated as disabled
171
181
  if not gate_enabled or gate_enabled.lower() not in ('true', '1', 'yes'):
172
- print(f"IP gating is disabled (GATE_ENABLED={gate_enabled or 'NONE'})")
182
+ logger.info(f"IP gating is disabled (GATE_ENABLED={gate_enabled or 'NONE'})")
173
183
  return request
174
184
 
175
185
  # Get allowed CIDRs and backup host
176
- allow_cidrs_str = get_ssm_parameter(f'/{env}/{function_name}/allow-cidrs', 'us-east-1')
177
- dns_alias = get_ssm_parameter(f'/{env}/{function_name}/dns-alias', 'us-east-1')
186
+ allow_cidrs_str = get_ssm_parameter(f'/{env}/{function_name}/allow-cidrs', region='us-east-1')
187
+ dns_alias = get_ssm_parameter(f'/{env}/{function_name}/dns-alias', region='us-east-1')
178
188
 
179
189
  # Parse allowed CIDRs (empty string results in empty list)
180
190
  allowed_cidrs = [cidr.strip() for cidr in allow_cidrs_str.split(',') if cidr.strip()]
181
191
 
182
192
  # Get client IP
183
193
  client_ip = get_client_ip(request)
184
- print(f"Client IP: {client_ip}")
194
+ logger.info(f"Client IP: {client_ip}")
185
195
 
186
196
  # Check if IP is allowed
187
197
  if is_ip_allowed(client_ip, allowed_cidrs):
188
- print(f"IP {client_ip} is allowed")
198
+ logger.info(f"IP {client_ip} is allowed")
189
199
  return request
190
200
 
191
201
  # IP not allowed - either redirect or proxy backup page
192
202
  # Check response mode from SSM (default: redirect for backward compatibility)
193
203
  response_mode_param = f"/{env}/{function_name}/response-mode"
194
- response_mode = get_ssm_parameter(response_mode_param, 'us-east-1', default='redirect')
204
+ response_mode = get_ssm_parameter(response_mode_param, region='us-east-1', default='redirect')
195
205
 
196
206
  if response_mode == 'proxy':
197
207
  # Proxy mode: Fetch and return backup content (keeps URL the same)
198
- print(f"IP {client_ip} is NOT allowed, proxying content from {dns_alias}")
208
+ logger.info(f"IP {client_ip} is NOT allowed, proxying content from {dns_alias}")
199
209
 
200
210
  try:
201
211
  import urllib3
@@ -230,17 +240,17 @@ def lambda_handler(event, context):
230
240
  'body': alias_response.data.decode('utf-8')
231
241
  }
232
242
 
233
- print(f"Successfully proxied backup page (status {alias_response.status})")
243
+ logger.info(f"Successfully proxied backup page (status {alias_response.status})")
234
244
  return response
235
245
 
236
246
  except Exception as proxy_error:
237
- print(f"Error proxying backup content: {str(proxy_error)}")
247
+ logger.error(f"Error proxying backup content: {str(proxy_error)}")
238
248
  # Fall back to redirect if proxy fails
239
- print(f"Falling back to redirect mode")
249
+ logger.info(f"Falling back to redirect mode")
240
250
  response_mode = 'redirect'
241
251
 
242
252
  # Redirect mode (default): HTTP 302 redirect to backup site
243
- print(f"IP {client_ip} is NOT allowed, redirecting to {dns_alias}")
253
+ logger.info(f"IP {client_ip} is NOT allowed, redirecting to {dns_alias}")
244
254
 
245
255
  response = {
246
256
  'status': '302',
@@ -249,14 +259,6 @@ def lambda_handler(event, context):
249
259
  'location': [{
250
260
  'key': 'Location',
251
261
  'value': f'https://{dns_alias}'
252
- }],
253
- 'cache-control': [{
254
- 'key': 'Cache-Control',
255
- 'value': 'no-cache, no-store, must-revalidate'
256
- }],
257
- 'x-ip-gate-mode': [{
258
- 'key': 'X-IP-Gate-Mode',
259
- 'value': 'redirect'
260
262
  }]
261
263
  }
262
264
  }
@@ -264,7 +266,7 @@ def lambda_handler(event, context):
264
266
  return response
265
267
 
266
268
  except Exception as e:
267
- print(f"Error in IP gating function: {str(e)}")
269
+ logger.error(f"Error in IP gating function: {str(e)}")
268
270
  # On error, allow the request to proceed (fail open)
269
271
  # Change this to fail closed if preferred
270
272
  return request