cdk-factory 0.16.15__py3-none-any.whl → 0.20.0__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.
- cdk_factory/configurations/base_config.py +23 -24
- cdk_factory/configurations/cdk_config.py +1 -1
- cdk_factory/configurations/deployment.py +12 -0
- cdk_factory/configurations/devops.py +1 -1
- cdk_factory/configurations/resources/acm.py +9 -2
- cdk_factory/configurations/resources/auto_scaling.py +7 -5
- cdk_factory/configurations/resources/cloudfront.py +7 -2
- cdk_factory/configurations/resources/ecr.py +1 -1
- cdk_factory/configurations/resources/ecs_cluster.py +12 -5
- cdk_factory/configurations/resources/ecs_service.py +30 -3
- cdk_factory/configurations/resources/lambda_edge.py +18 -4
- cdk_factory/configurations/resources/load_balancer.py +8 -9
- cdk_factory/configurations/resources/monitoring.py +8 -3
- cdk_factory/configurations/resources/rds.py +8 -9
- cdk_factory/configurations/resources/route53.py +5 -0
- cdk_factory/configurations/resources/rum.py +7 -2
- cdk_factory/configurations/resources/s3.py +10 -2
- cdk_factory/configurations/resources/security_group_full_stack.py +7 -8
- cdk_factory/configurations/resources/vpc.py +19 -0
- cdk_factory/configurations/workload.py +32 -2
- cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +1 -1
- cdk_factory/constructs/ecr/ecr_construct.py +9 -2
- cdk_factory/constructs/lambdas/policies/policy_docs.py +4 -4
- cdk_factory/interfaces/istack.py +4 -4
- cdk_factory/interfaces/networked_stack_mixin.py +6 -6
- cdk_factory/interfaces/standardized_ssm_mixin.py +684 -0
- cdk_factory/interfaces/vpc_provider_mixin.py +64 -33
- cdk_factory/lambdas/edge/ip_gate/handler.py +42 -40
- cdk_factory/pipeline/pipeline_factory.py +3 -3
- cdk_factory/stack_library/__init__.py +3 -2
- cdk_factory/stack_library/acm/acm_stack.py +7 -17
- cdk_factory/stack_library/api_gateway/api_gateway_stack.py +84 -59
- cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +454 -537
- cdk_factory/stack_library/cloudfront/cloudfront_stack.py +76 -22
- cdk_factory/stack_library/code_artifact/code_artifact_stack.py +5 -27
- cdk_factory/stack_library/cognito/cognito_stack.py +152 -92
- cdk_factory/stack_library/dynamodb/dynamodb_stack.py +19 -15
- cdk_factory/stack_library/ecr/ecr_stack.py +2 -2
- cdk_factory/stack_library/ecs/__init__.py +1 -3
- cdk_factory/stack_library/ecs/ecs_cluster_stack.py +159 -75
- cdk_factory/stack_library/ecs/ecs_service_stack.py +59 -52
- cdk_factory/stack_library/lambda_edge/EDGE_LOG_RETENTION_TODO.md +226 -0
- cdk_factory/stack_library/lambda_edge/LAMBDA_EDGE_LOG_RETENTION_BLOG.md +215 -0
- cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +240 -83
- cdk_factory/stack_library/load_balancer/load_balancer_stack.py +139 -212
- cdk_factory/stack_library/rds/rds_stack.py +74 -98
- cdk_factory/stack_library/route53/route53_stack.py +246 -40
- cdk_factory/stack_library/rum/rum_stack.py +108 -91
- cdk_factory/stack_library/security_group/security_group_full_stack.py +10 -53
- cdk_factory/stack_library/security_group/security_group_stack.py +12 -19
- cdk_factory/stack_library/simple_queue_service/sqs_stack.py +1 -34
- cdk_factory/stack_library/stack_base.py +5 -0
- cdk_factory/stack_library/vpc/vpc_stack.py +171 -130
- cdk_factory/stack_library/websites/static_website_stack.py +7 -3
- cdk_factory/utilities/api_gateway_integration_utility.py +24 -16
- cdk_factory/utilities/environment_services.py +5 -5
- cdk_factory/utilities/json_loading_utility.py +1 -1
- cdk_factory/validation/config_validator.py +483 -0
- cdk_factory/version.py +1 -1
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/METADATA +1 -1
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/RECORD +64 -62
- cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -321
- cdk_factory/interfaces/ssm_parameter_mixin.py +0 -454
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/WHEEL +0 -0
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/entry_points.txt +0 -0
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,7 +6,7 @@ MIT License. See Project Root for license information.
|
|
|
6
6
|
|
|
7
7
|
from typing import Optional, List, Any
|
|
8
8
|
from aws_lambda_powertools import Logger
|
|
9
|
-
from aws_cdk import aws_ec2 as ec2
|
|
9
|
+
from aws_cdk import aws_ec2 as ec2, aws_ssm as ssm
|
|
10
10
|
from constructs import Construct
|
|
11
11
|
|
|
12
12
|
logger = Logger(__name__)
|
|
@@ -18,13 +18,13 @@ class VPCProviderMixin:
|
|
|
18
18
|
|
|
19
19
|
This mixin eliminates code duplication across stacks that need to resolve
|
|
20
20
|
VPC references, providing a standardized way to handle:
|
|
21
|
-
- SSM imported VPC parameters (works with enhanced
|
|
21
|
+
- SSM imported VPC parameters (works with enhanced StandardizedSsmMixin)
|
|
22
22
|
- Configuration-based VPC resolution
|
|
23
23
|
- Workload-level VPC fallback
|
|
24
24
|
- Error handling and validation
|
|
25
25
|
|
|
26
26
|
Note: This mixin does NOT handle SSM imports directly - it expects
|
|
27
|
-
the SSM values to be available via the enhanced
|
|
27
|
+
the SSM values to be available via the enhanced StandardizedSsmMixin.
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
30
|
def _initialize_vpc_cache(self) -> None:
|
|
@@ -43,7 +43,7 @@ class VPCProviderMixin:
|
|
|
43
43
|
Resolve VPC from multiple sources with standardized priority order.
|
|
44
44
|
|
|
45
45
|
Priority order:
|
|
46
|
-
1. SSM imported VPC ID (from
|
|
46
|
+
1. SSM imported VPC ID (from config.ssm.imports)
|
|
47
47
|
2. Config-level VPC ID
|
|
48
48
|
3. Workload-level VPC ID
|
|
49
49
|
4. Raise error if none found
|
|
@@ -63,19 +63,22 @@ class VPCProviderMixin:
|
|
|
63
63
|
if self._vpc:
|
|
64
64
|
return self._vpc
|
|
65
65
|
|
|
66
|
-
# Default availability zones if not provided
|
|
66
|
+
# Default availability zones if not provided - use region-appropriate defaults
|
|
67
67
|
if not availability_zones:
|
|
68
|
-
|
|
68
|
+
region = getattr(deployment, 'region', 'us-east-1')
|
|
69
|
+
availability_zones = self._get_default_azs_for_region(region)
|
|
69
70
|
|
|
70
|
-
# Check SSM
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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"]
|
|
74
77
|
|
|
75
78
|
# Get subnet IDs first to determine AZ count
|
|
76
79
|
subnet_ids = []
|
|
77
|
-
if
|
|
78
|
-
imported_subnets =
|
|
80
|
+
if "subnet_ids" in ssm_imports:
|
|
81
|
+
imported_subnets = ssm_imports["subnet_ids"]
|
|
79
82
|
if isinstance(imported_subnets, str):
|
|
80
83
|
subnet_ids = [s.strip() for s in imported_subnets.split(",") if s.strip()]
|
|
81
84
|
elif isinstance(imported_subnets, list):
|
|
@@ -83,9 +86,11 @@ class VPCProviderMixin:
|
|
|
83
86
|
|
|
84
87
|
# Adjust availability zones to match subnet count
|
|
85
88
|
if subnet_ids and availability_zones:
|
|
86
|
-
|
|
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))]
|
|
87
92
|
|
|
88
|
-
return self._create_vpc_from_ssm(vpc_id, availability_zones)
|
|
93
|
+
return self._create_vpc_from_ssm(vpc_id, availability_zones, subnet_ids if subnet_ids else None)
|
|
89
94
|
|
|
90
95
|
# Check config-level VPC ID
|
|
91
96
|
if hasattr(config, 'vpc_id') and config.vpc_id:
|
|
@@ -101,41 +106,40 @@ class VPCProviderMixin:
|
|
|
101
106
|
def _create_vpc_from_ssm(
|
|
102
107
|
self,
|
|
103
108
|
vpc_id: str,
|
|
104
|
-
availability_zones: List[str]
|
|
109
|
+
availability_zones: List[str],
|
|
110
|
+
subnet_ids: Optional[List[str]] = None
|
|
105
111
|
) -> ec2.IVpc:
|
|
106
112
|
"""
|
|
107
113
|
Create VPC reference from SSM imported VPC ID.
|
|
108
114
|
|
|
109
115
|
Args:
|
|
110
|
-
vpc_id: The VPC ID from SSM
|
|
116
|
+
vpc_id: The VPC ID from SSM (can be SSM path or actual VPC ID)
|
|
111
117
|
availability_zones: List of availability zones
|
|
118
|
+
subnet_ids: Optional list of subnet IDs from SSM
|
|
112
119
|
|
|
113
120
|
Returns:
|
|
114
121
|
VPC reference created from attributes
|
|
115
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
|
+
|
|
116
133
|
# Build VPC attributes
|
|
117
134
|
vpc_attrs = {
|
|
118
|
-
"vpc_id":
|
|
135
|
+
"vpc_id": vpc_id_token,
|
|
119
136
|
"availability_zones": availability_zones,
|
|
120
137
|
}
|
|
121
138
|
|
|
122
|
-
# If we have subnet_ids from SSM,
|
|
123
|
-
if
|
|
124
|
-
imported_subnets = self._ssm_imported_values["subnet_ids"]
|
|
125
|
-
if isinstance(imported_subnets, str):
|
|
126
|
-
# Split comma-separated subnet IDs
|
|
127
|
-
subnet_ids = [s.strip() for s in imported_subnets.split(",") if s.strip()]
|
|
128
|
-
elif isinstance(imported_subnets, list):
|
|
129
|
-
subnet_ids = imported_subnets
|
|
130
|
-
else:
|
|
131
|
-
subnet_ids = []
|
|
132
|
-
|
|
139
|
+
# If we have subnet_ids from SSM, add them to the attributes
|
|
140
|
+
if subnet_ids:
|
|
133
141
|
# Use the actual subnet IDs from SSM
|
|
134
|
-
|
|
135
|
-
vpc_attrs["public_subnet_ids"] = subnet_ids
|
|
136
|
-
else:
|
|
137
|
-
# Fallback to dummy subnets if no valid subnet IDs
|
|
138
|
-
vpc_attrs["public_subnet_ids"] = ["subnet-dummy1", "subnet-dummy2"]
|
|
142
|
+
vpc_attrs["public_subnet_ids"] = subnet_ids
|
|
139
143
|
|
|
140
144
|
# Use from_vpc_attributes() for SSM tokens with unique construct name
|
|
141
145
|
self._vpc = ec2.Vpc.from_vpc_attributes(self, f"{self.stack_name}-VPC", **vpc_attrs)
|
|
@@ -177,3 +181,30 @@ class VPCProviderMixin:
|
|
|
177
181
|
Resolved VPC reference
|
|
178
182
|
"""
|
|
179
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 =
|
|
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:
|
|
27
|
-
region: AWS region (
|
|
28
|
-
default: Default value
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
logger.info(f"SSM parameter {parameter_name} not found, using default: {default}")
|
|
50
61
|
return default
|
|
51
|
-
|
|
62
|
+
logger.error(f"SSM parameter {parameter_name} not found and no default provided")
|
|
52
63
|
raise
|
|
53
64
|
except Exception as e:
|
|
54
|
-
|
|
65
|
+
logger.error(f"Error fetching SSM parameter {parameter_name}: {str(e)}")
|
|
55
66
|
if default is not None:
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
169
|
+
logger.info(f"Stripped region prefix from function name: {function_name}")
|
|
160
170
|
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
247
|
+
logger.error(f"Error proxying backup content: {str(proxy_error)}")
|
|
238
248
|
# Fall back to redirect if proxy fails
|
|
239
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -118,7 +118,7 @@ class PipelineFactoryStack(IStack):
|
|
|
118
118
|
f"\t🚨 Deployment for Environment: {deployment.environment} "
|
|
119
119
|
f"is disabled."
|
|
120
120
|
)
|
|
121
|
-
if
|
|
121
|
+
if not pipeline_deployments:
|
|
122
122
|
print(f"\t⛔️ No Pipeline Deployments configured for {self.workload.name}.")
|
|
123
123
|
|
|
124
124
|
return len(pipeline_deployments)
|
|
@@ -216,7 +216,7 @@ class PipelineFactoryStack(IStack):
|
|
|
216
216
|
wave_name = stage.wave_name
|
|
217
217
|
|
|
218
218
|
# if we don't have any stacks we'll need to use the wave
|
|
219
|
-
if
|
|
219
|
+
if not stage.stacks:
|
|
220
220
|
wave_name = stage.name
|
|
221
221
|
|
|
222
222
|
if wave_name:
|
|
@@ -519,7 +519,7 @@ class PipelineFactoryStack(IStack):
|
|
|
519
519
|
f"\t\t ⚠️ Stack {stack_config.name} is disabled in stage: {stage_config.name}"
|
|
520
520
|
)
|
|
521
521
|
|
|
522
|
-
if
|
|
522
|
+
if not cf_stacks:
|
|
523
523
|
print(f"\t\t ⚠️ No stacks added to stage: {stage_config.name}")
|
|
524
524
|
print(f"\t\t ⚠️ Internal Stack Count: {len(stage_config.stacks)}")
|
|
525
525
|
|
|
@@ -12,9 +12,10 @@ paths = []
|
|
|
12
12
|
|
|
13
13
|
try:
|
|
14
14
|
paths = __path__
|
|
15
|
-
except
|
|
15
|
+
except (AttributeError, ImportError) as e:
|
|
16
|
+
# Fallback to using os.path if __path__ is not available
|
|
16
17
|
import os
|
|
17
|
-
|
|
18
|
+
|
|
18
19
|
paths.append(os.path.dirname(__file__))
|
|
19
20
|
|
|
20
21
|
|
|
@@ -16,7 +16,7 @@ from cdk_factory.configurations.deployment import DeploymentConfig
|
|
|
16
16
|
from cdk_factory.configurations.stack import StackConfig
|
|
17
17
|
from cdk_factory.configurations.resources.acm import AcmConfig
|
|
18
18
|
from cdk_factory.interfaces.istack import IStack
|
|
19
|
-
from cdk_factory.interfaces.
|
|
19
|
+
from cdk_factory.interfaces.standardized_ssm_mixin import StandardizedSsmMixin
|
|
20
20
|
from cdk_factory.stack.stack_module_registry import register_stack
|
|
21
21
|
from cdk_factory.workload.workload_factory import WorkloadConfig
|
|
22
22
|
|
|
@@ -25,7 +25,8 @@ logger = Logger(service="AcmStack")
|
|
|
25
25
|
|
|
26
26
|
@register_stack("acm_stack")
|
|
27
27
|
@register_stack("certificate_stack")
|
|
28
|
-
|
|
28
|
+
@register_stack("certificate_library_module")
|
|
29
|
+
class AcmStack(IStack, StandardizedSsmMixin):
|
|
29
30
|
"""
|
|
30
31
|
Reusable stack for AWS Certificate Manager.
|
|
31
32
|
Supports creating ACM certificates with DNS validation via Route53.
|
|
@@ -152,18 +153,7 @@ class AcmStack(IStack, EnhancedSsmParameterMixin):
|
|
|
152
153
|
|
|
153
154
|
def _add_outputs(self, cert_name: str) -> None:
|
|
154
155
|
"""Add CloudFormation outputs"""
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
description=f"Certificate ARN for {self.acm_config.domain_name}",
|
|
160
|
-
export_name=f"{cert_name}-arn",
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
cdk.CfnOutput(
|
|
164
|
-
self,
|
|
165
|
-
"DomainName",
|
|
166
|
-
value=self.acm_config.domain_name,
|
|
167
|
-
description="Primary domain name for the certificate",
|
|
168
|
-
export_name=f"{cert_name}-domain",
|
|
169
|
-
)
|
|
156
|
+
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
|