complio 0.1.1__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.
- CHANGELOG.md +208 -0
- README.md +343 -0
- complio/__init__.py +48 -0
- complio/cli/__init__.py +0 -0
- complio/cli/banner.py +87 -0
- complio/cli/commands/__init__.py +0 -0
- complio/cli/commands/history.py +439 -0
- complio/cli/commands/scan.py +700 -0
- complio/cli/main.py +115 -0
- complio/cli/output.py +338 -0
- complio/config/__init__.py +17 -0
- complio/config/settings.py +333 -0
- complio/connectors/__init__.py +9 -0
- complio/connectors/aws/__init__.py +0 -0
- complio/connectors/aws/client.py +342 -0
- complio/connectors/base.py +135 -0
- complio/core/__init__.py +10 -0
- complio/core/registry.py +228 -0
- complio/core/runner.py +351 -0
- complio/py.typed +0 -0
- complio/reporters/__init__.py +7 -0
- complio/reporters/generator.py +417 -0
- complio/tests_library/__init__.py +0 -0
- complio/tests_library/base.py +492 -0
- complio/tests_library/identity/__init__.py +0 -0
- complio/tests_library/identity/access_key_rotation.py +302 -0
- complio/tests_library/identity/mfa_enforcement.py +327 -0
- complio/tests_library/identity/root_account_protection.py +470 -0
- complio/tests_library/infrastructure/__init__.py +0 -0
- complio/tests_library/infrastructure/cloudtrail_encryption.py +286 -0
- complio/tests_library/infrastructure/cloudtrail_log_validation.py +274 -0
- complio/tests_library/infrastructure/cloudtrail_logging.py +400 -0
- complio/tests_library/infrastructure/ebs_encryption.py +244 -0
- complio/tests_library/infrastructure/ec2_security_groups.py +321 -0
- complio/tests_library/infrastructure/iam_password_policy.py +460 -0
- complio/tests_library/infrastructure/nacl_security.py +356 -0
- complio/tests_library/infrastructure/rds_encryption.py +252 -0
- complio/tests_library/infrastructure/s3_encryption.py +301 -0
- complio/tests_library/infrastructure/s3_public_access.py +369 -0
- complio/tests_library/infrastructure/secrets_manager_encryption.py +248 -0
- complio/tests_library/infrastructure/vpc_flow_logs.py +287 -0
- complio/tests_library/logging/__init__.py +0 -0
- complio/tests_library/logging/cloudwatch_alarms.py +354 -0
- complio/tests_library/logging/cloudwatch_logs_encryption.py +281 -0
- complio/tests_library/logging/cloudwatch_retention.py +252 -0
- complio/tests_library/logging/config_enabled.py +393 -0
- complio/tests_library/logging/eventbridge_rules.py +460 -0
- complio/tests_library/logging/guardduty_enabled.py +436 -0
- complio/tests_library/logging/security_hub_enabled.py +416 -0
- complio/tests_library/logging/sns_encryption.py +273 -0
- complio/tests_library/network/__init__.py +0 -0
- complio/tests_library/network/alb_nlb_security.py +421 -0
- complio/tests_library/network/api_gateway_security.py +452 -0
- complio/tests_library/network/cloudfront_https.py +332 -0
- complio/tests_library/network/direct_connect_security.py +343 -0
- complio/tests_library/network/nacl_configuration.py +367 -0
- complio/tests_library/network/network_firewall.py +355 -0
- complio/tests_library/network/transit_gateway_security.py +318 -0
- complio/tests_library/network/vpc_endpoints_security.py +339 -0
- complio/tests_library/network/vpn_security.py +333 -0
- complio/tests_library/network/waf_configuration.py +428 -0
- complio/tests_library/security/__init__.py +0 -0
- complio/tests_library/security/kms_key_rotation.py +314 -0
- complio/tests_library/storage/__init__.py +0 -0
- complio/tests_library/storage/backup_encryption.py +288 -0
- complio/tests_library/storage/dynamodb_encryption.py +280 -0
- complio/tests_library/storage/efs_encryption.py +257 -0
- complio/tests_library/storage/elasticache_encryption.py +370 -0
- complio/tests_library/storage/redshift_encryption.py +252 -0
- complio/tests_library/storage/s3_versioning.py +264 -0
- complio/utils/__init__.py +26 -0
- complio/utils/errors.py +179 -0
- complio/utils/exceptions.py +151 -0
- complio/utils/history.py +243 -0
- complio/utils/logger.py +391 -0
- complio-0.1.1.dist-info/METADATA +385 -0
- complio-0.1.1.dist-info/RECORD +79 -0
- complio-0.1.1.dist-info/WHEEL +4 -0
- complio-0.1.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Network Firewall compliance test.
|
|
3
|
+
|
|
4
|
+
Checks that Network Firewalls are deployed and properly configured.
|
|
5
|
+
|
|
6
|
+
ISO 27001 Control: A.8.20 - Networks security
|
|
7
|
+
Requirement: VPCs handling sensitive workloads should use Network Firewall
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
11
|
+
>>> from complio.tests_library.network.network_firewall import NetworkFirewallTest
|
|
12
|
+
>>>
|
|
13
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
14
|
+
>>> connector.connect()
|
|
15
|
+
>>>
|
|
16
|
+
>>> test = NetworkFirewallTest(connector)
|
|
17
|
+
>>> result = test.run()
|
|
18
|
+
>>> print(f"Passed: {result.passed}, Score: {result.score}")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import Any, Dict
|
|
22
|
+
|
|
23
|
+
from botocore.exceptions import ClientError
|
|
24
|
+
|
|
25
|
+
from complio.connectors.aws.client import AWSConnector
|
|
26
|
+
from complio.tests_library.base import (
|
|
27
|
+
ComplianceTest,
|
|
28
|
+
Severity,
|
|
29
|
+
TestResult,
|
|
30
|
+
TestStatus,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class NetworkFirewallTest(ComplianceTest):
|
|
35
|
+
"""Test for AWS Network Firewall compliance.
|
|
36
|
+
|
|
37
|
+
Verifies that Network Firewalls are deployed for VPC protection:
|
|
38
|
+
- Firewalls should be in READY state
|
|
39
|
+
- Should have logging configured
|
|
40
|
+
- Should have stateful and/or stateless rules configured
|
|
41
|
+
|
|
42
|
+
Compliance Requirements:
|
|
43
|
+
- Network Firewalls deployed for production VPCs
|
|
44
|
+
- Logging enabled (flow logs and/or alert logs)
|
|
45
|
+
- Active firewall rules configured
|
|
46
|
+
|
|
47
|
+
Scoring:
|
|
48
|
+
- Based on deployment and configuration status
|
|
49
|
+
- 100% if Network Firewalls are properly configured
|
|
50
|
+
- Note: This is an informational test about firewall presence
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
>>> test = NetworkFirewallTest(connector)
|
|
54
|
+
>>> result = test.execute()
|
|
55
|
+
>>> for finding in result.findings:
|
|
56
|
+
... print(f"{finding.resource_id}: {finding.title}")
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, connector: AWSConnector) -> None:
|
|
60
|
+
"""Initialize Network Firewall test.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
connector: AWS connector instance
|
|
64
|
+
"""
|
|
65
|
+
super().__init__(
|
|
66
|
+
test_id="network_firewall",
|
|
67
|
+
test_name="AWS Network Firewall Check",
|
|
68
|
+
description="Verify Network Firewalls are deployed and properly configured",
|
|
69
|
+
control_id="A.8.20",
|
|
70
|
+
connector=connector,
|
|
71
|
+
scope="regional",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def execute(self) -> TestResult:
|
|
75
|
+
"""Execute Network Firewall compliance test.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
TestResult with findings for missing or misconfigured firewalls
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
>>> test = NetworkFirewallTest(connector)
|
|
82
|
+
>>> result = test.execute()
|
|
83
|
+
>>> print(result.score)
|
|
84
|
+
100.0
|
|
85
|
+
"""
|
|
86
|
+
result = TestResult(
|
|
87
|
+
test_id=self.test_id,
|
|
88
|
+
test_name=self.test_name,
|
|
89
|
+
status=TestStatus.PASSED,
|
|
90
|
+
passed=True,
|
|
91
|
+
score=100.0,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
# Get Network Firewall client
|
|
96
|
+
network_firewall_client = self.connector.get_client("network-firewall")
|
|
97
|
+
|
|
98
|
+
# List all Network Firewalls
|
|
99
|
+
self.logger.info("listing_network_firewalls")
|
|
100
|
+
firewalls_response = network_firewall_client.list_firewalls()
|
|
101
|
+
firewalls = firewalls_response.get("Firewalls", [])
|
|
102
|
+
|
|
103
|
+
if not firewalls:
|
|
104
|
+
self.logger.info("no_network_firewalls_found")
|
|
105
|
+
result.metadata["message"] = "No Network Firewalls found in region (consider deploying for production VPCs)"
|
|
106
|
+
result.metadata["recommendation"] = "Deploy Network Firewall for VPCs handling sensitive workloads"
|
|
107
|
+
# Not a failure, just informational
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
self.logger.info("network_firewalls_found", count=len(firewalls))
|
|
111
|
+
|
|
112
|
+
# Check configuration for each firewall
|
|
113
|
+
properly_configured_count = 0
|
|
114
|
+
|
|
115
|
+
for firewall_summary in firewalls:
|
|
116
|
+
firewall_name = firewall_summary.get("FirewallName", "")
|
|
117
|
+
firewall_arn = firewall_summary.get("FirewallArn", "")
|
|
118
|
+
|
|
119
|
+
result.resources_scanned += 1
|
|
120
|
+
|
|
121
|
+
# Get detailed firewall configuration
|
|
122
|
+
firewall_response = network_firewall_client.describe_firewall(
|
|
123
|
+
FirewallName=firewall_name
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
firewall = firewall_response.get("Firewall", {})
|
|
127
|
+
firewall_status = firewall_response.get("FirewallStatus", {})
|
|
128
|
+
|
|
129
|
+
status = firewall_status.get("Status", "")
|
|
130
|
+
vpc_id = firewall.get("VpcId", "")
|
|
131
|
+
firewall_policy_arn = firewall.get("FirewallPolicyArn", "")
|
|
132
|
+
|
|
133
|
+
# Get logging configuration
|
|
134
|
+
logging_response = network_firewall_client.describe_logging_configuration(
|
|
135
|
+
FirewallName=firewall_name
|
|
136
|
+
)
|
|
137
|
+
logging_config = logging_response.get("LoggingConfiguration", {})
|
|
138
|
+
log_destination_configs = logging_config.get("LogDestinationConfigs", [])
|
|
139
|
+
|
|
140
|
+
# Determine security issues
|
|
141
|
+
issues = []
|
|
142
|
+
severity = Severity.MEDIUM
|
|
143
|
+
|
|
144
|
+
# Check firewall status
|
|
145
|
+
if status != "READY":
|
|
146
|
+
issues.append(f"Firewall not in READY state (current: {status})")
|
|
147
|
+
severity = Severity.HIGH
|
|
148
|
+
|
|
149
|
+
# Check logging configuration
|
|
150
|
+
if not log_destination_configs:
|
|
151
|
+
issues.append("No logging configured (no alert or flow logs)")
|
|
152
|
+
severity = Severity.MEDIUM
|
|
153
|
+
|
|
154
|
+
# Check if firewall policy exists
|
|
155
|
+
if not firewall_policy_arn:
|
|
156
|
+
issues.append("No firewall policy configured")
|
|
157
|
+
severity = Severity.HIGH
|
|
158
|
+
|
|
159
|
+
# Create evidence
|
|
160
|
+
evidence = self.create_evidence(
|
|
161
|
+
resource_id=firewall_arn,
|
|
162
|
+
resource_type="network_firewall",
|
|
163
|
+
data={
|
|
164
|
+
"firewall_name": firewall_name,
|
|
165
|
+
"firewall_arn": firewall_arn,
|
|
166
|
+
"vpc_id": vpc_id,
|
|
167
|
+
"status": status,
|
|
168
|
+
"firewall_policy_arn": firewall_policy_arn,
|
|
169
|
+
"logging_configured": len(log_destination_configs) > 0,
|
|
170
|
+
"log_destinations_count": len(log_destination_configs),
|
|
171
|
+
"has_issues": len(issues) > 0,
|
|
172
|
+
"issues": issues,
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
result.add_evidence(evidence)
|
|
176
|
+
|
|
177
|
+
if len(issues) == 0:
|
|
178
|
+
properly_configured_count += 1
|
|
179
|
+
self.logger.debug(
|
|
180
|
+
"network_firewall_configured",
|
|
181
|
+
firewall_name=firewall_name
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
# Create finding for misconfigured firewall
|
|
185
|
+
finding = self.create_finding(
|
|
186
|
+
resource_id=firewall_arn,
|
|
187
|
+
resource_type="network_firewall",
|
|
188
|
+
severity=severity,
|
|
189
|
+
title="Network Firewall has configuration issues",
|
|
190
|
+
description=f"Network Firewall '{firewall_name}' in VPC '{vpc_id}' has configuration issues: "
|
|
191
|
+
f"{'; '.join(issues)}. Network Firewalls should be in READY state, have active "
|
|
192
|
+
"firewall policies, and enable logging for security monitoring. ISO 27001 A.8.20 "
|
|
193
|
+
"requires proper network security controls.",
|
|
194
|
+
remediation=(
|
|
195
|
+
f"Improve Network Firewall '{firewall_name}' configuration:\n\n"
|
|
196
|
+
"1. Ensure firewall is in READY state:\n"
|
|
197
|
+
f"aws network-firewall describe-firewall \\\n"
|
|
198
|
+
f" --firewall-name {firewall_name}\n\n"
|
|
199
|
+
"2. Enable logging (alert and flow logs):\n"
|
|
200
|
+
f"aws network-firewall update-logging-configuration \\\n"
|
|
201
|
+
f" --firewall-name {firewall_name} \\\n"
|
|
202
|
+
" --logging-configuration '{\n"
|
|
203
|
+
' "LogDestinationConfigs": [\n'
|
|
204
|
+
' {\n'
|
|
205
|
+
' "LogType": "ALERT",\n'
|
|
206
|
+
' "LogDestinationType": "CloudWatchLogs",\n'
|
|
207
|
+
' "LogDestination": {\n'
|
|
208
|
+
' "logGroup": "/aws/network-firewall/alerts"\n'
|
|
209
|
+
' }\n'
|
|
210
|
+
' },\n'
|
|
211
|
+
' {\n'
|
|
212
|
+
' "LogType": "FLOW",\n'
|
|
213
|
+
' "LogDestinationType": "S3",\n'
|
|
214
|
+
' "LogDestination": {\n'
|
|
215
|
+
' "bucketName": "my-firewall-logs",\n'
|
|
216
|
+
' "prefix": "network-firewall/flow"\n'
|
|
217
|
+
' }\n'
|
|
218
|
+
' }\n'
|
|
219
|
+
' ]\n'
|
|
220
|
+
" }'\n\n"
|
|
221
|
+
"3. Create and attach firewall policy with rules:\n"
|
|
222
|
+
"# Create stateful rule group\n"
|
|
223
|
+
"aws network-firewall create-rule-group \\\n"
|
|
224
|
+
" --rule-group-name block-malicious-domains \\\n"
|
|
225
|
+
" --type STATEFUL \\\n"
|
|
226
|
+
" --capacity 100 \\\n"
|
|
227
|
+
" --rule-group file://stateful-rules.json\n\n"
|
|
228
|
+
"# Create firewall policy\n"
|
|
229
|
+
"aws network-firewall create-firewall-policy \\\n"
|
|
230
|
+
" --firewall-policy-name my-firewall-policy \\\n"
|
|
231
|
+
" --firewall-policy file://policy.json\n\n"
|
|
232
|
+
"# Associate policy with firewall\n"
|
|
233
|
+
f"aws network-firewall associate-firewall-policy \\\n"
|
|
234
|
+
f" --firewall-name {firewall_name} \\\n"
|
|
235
|
+
" --firewall-policy-arn <POLICY-ARN>\n\n"
|
|
236
|
+
"Or use AWS Console:\n"
|
|
237
|
+
"1. Go to VPC console → Network Firewall → Firewalls\n"
|
|
238
|
+
f"2. Select firewall '{firewall_name}'\n"
|
|
239
|
+
"3. Verify status is READY\n"
|
|
240
|
+
"4. Edit firewall:\n"
|
|
241
|
+
" - Associate firewall policy\n"
|
|
242
|
+
" - Enable logging:\n"
|
|
243
|
+
" • Alert logs → CloudWatch Logs\n"
|
|
244
|
+
" • Flow logs → S3 or CloudWatch\n"
|
|
245
|
+
"5. Create firewall policy:\n"
|
|
246
|
+
" - Add stateful rule groups (domain lists, IPS rules)\n"
|
|
247
|
+
" - Add stateless rule groups (basic filtering)\n"
|
|
248
|
+
" - Set default actions\n\n"
|
|
249
|
+
"Security best practices:\n"
|
|
250
|
+
"- Deploy Network Firewall in dedicated inspection subnets\n"
|
|
251
|
+
"- Use stateful inspection for deep packet inspection\n"
|
|
252
|
+
"- Enable IPS/IDS with AWS managed rule groups\n"
|
|
253
|
+
"- Block known malicious domains and IPs\n"
|
|
254
|
+
"- Log all traffic for security analysis\n"
|
|
255
|
+
"- Use separate firewall policies for different zones\n"
|
|
256
|
+
"- Regularly update rule groups\n"
|
|
257
|
+
"- Monitor firewall metrics in CloudWatch\n"
|
|
258
|
+
"- Integrate with AWS Firewall Manager for centralized management\n"
|
|
259
|
+
"- Use with Transit Gateway for centralized inspection\n\n"
|
|
260
|
+
"Example stateful rules:\n"
|
|
261
|
+
"- Block malicious domains (using domain list)\n"
|
|
262
|
+
"- Block known malware signatures (using Suricata rules)\n"
|
|
263
|
+
"- Allow only specific protocols and ports\n"
|
|
264
|
+
"- Log and alert on suspicious patterns\n\n"
|
|
265
|
+
"Example deployment architectures:\n"
|
|
266
|
+
"1. Inspection VPC with Transit Gateway\n"
|
|
267
|
+
"2. Distributed firewalls in each VPC\n"
|
|
268
|
+
"3. Centralized egress filtering\n"
|
|
269
|
+
"4. East-West traffic inspection"
|
|
270
|
+
),
|
|
271
|
+
evidence=evidence
|
|
272
|
+
)
|
|
273
|
+
result.add_finding(finding)
|
|
274
|
+
|
|
275
|
+
self.logger.warning(
|
|
276
|
+
"network_firewall_misconfigured",
|
|
277
|
+
firewall_name=firewall_name,
|
|
278
|
+
issues=issues
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Calculate compliance score
|
|
282
|
+
if len(firewalls) > 0:
|
|
283
|
+
result.score = (properly_configured_count / len(firewalls)) * 100
|
|
284
|
+
result.passed = properly_configured_count == len(firewalls)
|
|
285
|
+
else:
|
|
286
|
+
result.score = 100.0
|
|
287
|
+
result.passed = True
|
|
288
|
+
|
|
289
|
+
result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
|
|
290
|
+
|
|
291
|
+
# Add metadata
|
|
292
|
+
result.metadata = {
|
|
293
|
+
"total_firewalls": len(firewalls),
|
|
294
|
+
"properly_configured": properly_configured_count,
|
|
295
|
+
"misconfigured": len(firewalls) - properly_configured_count,
|
|
296
|
+
"compliance_percentage": result.score,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
self.logger.info(
|
|
300
|
+
"network_firewall_test_completed",
|
|
301
|
+
total_firewalls=len(firewalls),
|
|
302
|
+
properly_configured=properly_configured_count,
|
|
303
|
+
score=result.score,
|
|
304
|
+
passed=result.passed
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
except ClientError as e:
|
|
308
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
309
|
+
# Network Firewall may not be available in all regions
|
|
310
|
+
if error_code in ["UnknownOperationException", "InvalidAction"]:
|
|
311
|
+
self.logger.info("network_firewall_not_available_in_region")
|
|
312
|
+
result.metadata["message"] = "AWS Network Firewall not available in this region"
|
|
313
|
+
return result
|
|
314
|
+
|
|
315
|
+
self.logger.error("network_firewall_test_error", error_code=error_code, error=str(e))
|
|
316
|
+
result.status = TestStatus.ERROR
|
|
317
|
+
result.passed = False
|
|
318
|
+
result.score = 0.0
|
|
319
|
+
result.error_message = f"AWS API Error: {error_code} - {str(e)}"
|
|
320
|
+
|
|
321
|
+
except Exception as e:
|
|
322
|
+
self.logger.error("network_firewall_test_error", error=str(e))
|
|
323
|
+
result.status = TestStatus.ERROR
|
|
324
|
+
result.passed = False
|
|
325
|
+
result.score = 0.0
|
|
326
|
+
result.error_message = str(e)
|
|
327
|
+
|
|
328
|
+
return result
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
# ============================================================================
|
|
332
|
+
# CONVENIENCE FUNCTION
|
|
333
|
+
# ============================================================================
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def run_network_firewall_test(connector: AWSConnector) -> TestResult:
|
|
337
|
+
"""Run AWS Network Firewall compliance test.
|
|
338
|
+
|
|
339
|
+
Convenience function for running the test.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
connector: AWS connector
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
TestResult
|
|
346
|
+
|
|
347
|
+
Example:
|
|
348
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
349
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
350
|
+
>>> connector.connect()
|
|
351
|
+
>>> result = run_network_firewall_test(connector)
|
|
352
|
+
>>> print(f"Score: {result.score}%")
|
|
353
|
+
"""
|
|
354
|
+
test = NetworkFirewallTest(connector)
|
|
355
|
+
return test.execute()
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Transit Gateway security compliance test.
|
|
3
|
+
|
|
4
|
+
Checks that Transit Gateways use secure configurations.
|
|
5
|
+
|
|
6
|
+
ISO 27001 Control: A.8.22 - Network segregation
|
|
7
|
+
Requirement: Transit Gateways must have proper route tables and security controls
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
11
|
+
>>> from complio.tests_library.network.transit_gateway_security import TransitGatewaySecurityTest
|
|
12
|
+
>>>
|
|
13
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
14
|
+
>>> connector.connect()
|
|
15
|
+
>>>
|
|
16
|
+
>>> test = TransitGatewaySecurityTest(connector)
|
|
17
|
+
>>> result = test.run()
|
|
18
|
+
>>> print(f"Passed: {result.passed}, Score: {result.score}")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import Any, Dict
|
|
22
|
+
|
|
23
|
+
from botocore.exceptions import ClientError
|
|
24
|
+
|
|
25
|
+
from complio.connectors.aws.client import AWSConnector
|
|
26
|
+
from complio.tests_library.base import (
|
|
27
|
+
ComplianceTest,
|
|
28
|
+
Severity,
|
|
29
|
+
TestResult,
|
|
30
|
+
TestStatus,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TransitGatewaySecurityTest(ComplianceTest):
|
|
35
|
+
"""Test for Transit Gateway security compliance.
|
|
36
|
+
|
|
37
|
+
Verifies that Transit Gateways use secure configurations:
|
|
38
|
+
- Default route table association should be disabled (explicit control)
|
|
39
|
+
- Default route table propagation should be disabled
|
|
40
|
+
- Auto-accept shared attachments should be disabled
|
|
41
|
+
- DNS support and VPN ECMP support configured appropriately
|
|
42
|
+
|
|
43
|
+
Compliance Requirements:
|
|
44
|
+
- DefaultRouteTableAssociation should be 'disable'
|
|
45
|
+
- DefaultRouteTablePropagation should be 'disable'
|
|
46
|
+
- AutoAcceptSharedAttachments should be 'disable'
|
|
47
|
+
- Proper isolation between VPC attachments
|
|
48
|
+
|
|
49
|
+
Scoring:
|
|
50
|
+
- 100% if all Transit Gateways follow security best practices
|
|
51
|
+
- Proportional score based on compliant/total ratio
|
|
52
|
+
- 100% if no Transit Gateways exist
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
>>> test = TransitGatewaySecurityTest(connector)
|
|
56
|
+
>>> result = test.execute()
|
|
57
|
+
>>> for finding in result.findings:
|
|
58
|
+
... print(f"{finding.resource_id}: {finding.title}")
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, connector: AWSConnector) -> None:
|
|
62
|
+
"""Initialize Transit Gateway security test.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
connector: AWS connector instance
|
|
66
|
+
"""
|
|
67
|
+
super().__init__(
|
|
68
|
+
test_id="transit_gateway_security",
|
|
69
|
+
test_name="Transit Gateway Security Check",
|
|
70
|
+
description="Verify Transit Gateways use secure configurations",
|
|
71
|
+
control_id="A.8.22",
|
|
72
|
+
connector=connector,
|
|
73
|
+
scope="regional",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def execute(self) -> TestResult:
|
|
77
|
+
"""Execute Transit Gateway security compliance test.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
TestResult with findings for insecure Transit Gateways
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> test = TransitGatewaySecurityTest(connector)
|
|
84
|
+
>>> result = test.execute()
|
|
85
|
+
>>> print(result.score)
|
|
86
|
+
100.0
|
|
87
|
+
"""
|
|
88
|
+
result = TestResult(
|
|
89
|
+
test_id=self.test_id,
|
|
90
|
+
test_name=self.test_name,
|
|
91
|
+
status=TestStatus.PASSED,
|
|
92
|
+
passed=True,
|
|
93
|
+
score=100.0,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
# Get EC2 client
|
|
98
|
+
ec2_client = self.connector.get_client("ec2")
|
|
99
|
+
|
|
100
|
+
# List all Transit Gateways
|
|
101
|
+
self.logger.info("listing_transit_gateways")
|
|
102
|
+
tgw_response = ec2_client.describe_transit_gateways()
|
|
103
|
+
transit_gateways = tgw_response.get("TransitGateways", [])
|
|
104
|
+
|
|
105
|
+
if not transit_gateways:
|
|
106
|
+
self.logger.info("no_transit_gateways_found")
|
|
107
|
+
result.metadata["message"] = "No Transit Gateways found in region"
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
self.logger.info("transit_gateways_found", count=len(transit_gateways))
|
|
111
|
+
|
|
112
|
+
# Check security for each Transit Gateway
|
|
113
|
+
secure_tgw_count = 0
|
|
114
|
+
|
|
115
|
+
for tgw in transit_gateways:
|
|
116
|
+
tgw_id = tgw["TransitGatewayId"]
|
|
117
|
+
tgw_state = tgw.get("State", "")
|
|
118
|
+
tgw_arn = tgw.get("TransitGatewayArn", "")
|
|
119
|
+
|
|
120
|
+
# Skip deleted/deleting transit gateways
|
|
121
|
+
if tgw_state in ["deleted", "deleting"]:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
result.resources_scanned += 1
|
|
125
|
+
|
|
126
|
+
# Get Transit Gateway options
|
|
127
|
+
options = tgw.get("Options", {})
|
|
128
|
+
default_route_table_association = options.get("DefaultRouteTableAssociation", "enable")
|
|
129
|
+
default_route_table_propagation = options.get("DefaultRouteTablePropagation", "enable")
|
|
130
|
+
auto_accept_shared_attachments = options.get("AutoAcceptSharedAttachments", "enable")
|
|
131
|
+
dns_support = options.get("DnsSupport", "enable")
|
|
132
|
+
|
|
133
|
+
# Determine security issues
|
|
134
|
+
issues = []
|
|
135
|
+
severity = Severity.MEDIUM
|
|
136
|
+
|
|
137
|
+
# Check default route table association (should be disabled for security)
|
|
138
|
+
if default_route_table_association == "enable":
|
|
139
|
+
issues.append("Default route table association enabled (should be disabled for explicit control)")
|
|
140
|
+
severity = Severity.MEDIUM
|
|
141
|
+
|
|
142
|
+
# Check default route table propagation
|
|
143
|
+
if default_route_table_propagation == "enable":
|
|
144
|
+
issues.append("Default route table propagation enabled (should be disabled for explicit control)")
|
|
145
|
+
severity = Severity.MEDIUM
|
|
146
|
+
|
|
147
|
+
# Check auto-accept shared attachments (security risk)
|
|
148
|
+
if auto_accept_shared_attachments == "enable":
|
|
149
|
+
issues.append("Auto-accept shared attachments enabled (security risk)")
|
|
150
|
+
severity = Severity.HIGH
|
|
151
|
+
|
|
152
|
+
# Create evidence
|
|
153
|
+
evidence = self.create_evidence(
|
|
154
|
+
resource_id=tgw_id,
|
|
155
|
+
resource_type="transit_gateway",
|
|
156
|
+
data={
|
|
157
|
+
"transit_gateway_id": tgw_id,
|
|
158
|
+
"transit_gateway_arn": tgw_arn,
|
|
159
|
+
"state": tgw_state,
|
|
160
|
+
"default_route_table_association": default_route_table_association,
|
|
161
|
+
"default_route_table_propagation": default_route_table_propagation,
|
|
162
|
+
"auto_accept_shared_attachments": auto_accept_shared_attachments,
|
|
163
|
+
"dns_support": dns_support,
|
|
164
|
+
"has_issues": len(issues) > 0,
|
|
165
|
+
"issues": issues,
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
result.add_evidence(evidence)
|
|
169
|
+
|
|
170
|
+
if len(issues) == 0:
|
|
171
|
+
secure_tgw_count += 1
|
|
172
|
+
self.logger.debug(
|
|
173
|
+
"transit_gateway_secure",
|
|
174
|
+
tgw_id=tgw_id
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
# Create finding for insecure Transit Gateway
|
|
178
|
+
finding = self.create_finding(
|
|
179
|
+
resource_id=tgw_id,
|
|
180
|
+
resource_type="transit_gateway",
|
|
181
|
+
severity=severity,
|
|
182
|
+
title="Transit Gateway has security configuration issues",
|
|
183
|
+
description=f"Transit Gateway '{tgw_id}' has security configuration issues: "
|
|
184
|
+
f"{'; '.join(issues)}. Transit Gateways should use explicit route table "
|
|
185
|
+
"associations and propagations for better security control, and should not "
|
|
186
|
+
"auto-accept shared attachments to prevent unauthorized network access. "
|
|
187
|
+
"ISO 27001 A.8.22 requires proper network segregation and security controls.",
|
|
188
|
+
remediation=(
|
|
189
|
+
f"Improve Transit Gateway '{tgw_id}' security configuration:\n\n"
|
|
190
|
+
"1. Disable default route table association:\n"
|
|
191
|
+
f"aws ec2 modify-transit-gateway \\\n"
|
|
192
|
+
f" --transit-gateway-id {tgw_id} \\\n"
|
|
193
|
+
" --options DefaultRouteTableAssociation=disable\n\n"
|
|
194
|
+
"2. Disable default route table propagation:\n"
|
|
195
|
+
f"aws ec2 modify-transit-gateway \\\n"
|
|
196
|
+
f" --transit-gateway-id {tgw_id} \\\n"
|
|
197
|
+
" --options DefaultRouteTablePropagation=disable\n\n"
|
|
198
|
+
"3. Disable auto-accept shared attachments:\n"
|
|
199
|
+
f"aws ec2 modify-transit-gateway \\\n"
|
|
200
|
+
f" --transit-gateway-id {tgw_id} \\\n"
|
|
201
|
+
" --options AutoAcceptSharedAttachments=disable\n\n"
|
|
202
|
+
"4. Explicitly manage route tables:\n"
|
|
203
|
+
"# Create dedicated route tables for different environments\n"
|
|
204
|
+
f"aws ec2 create-transit-gateway-route-table \\\n"
|
|
205
|
+
f" --transit-gateway-id {tgw_id} \\\n"
|
|
206
|
+
" --tag-specifications 'ResourceType=transit-gateway-route-table,Tags=[{Key=Name,Value=Production}]'\n\n"
|
|
207
|
+
"# Associate VPC attachments explicitly\n"
|
|
208
|
+
"aws ec2 associate-transit-gateway-route-table \\\n"
|
|
209
|
+
" --transit-gateway-route-table-id <TGW-RT-ID> \\\n"
|
|
210
|
+
" --transit-gateway-attachment-id <TGW-ATTACHMENT-ID>\n\n"
|
|
211
|
+
"Or use AWS Console:\n"
|
|
212
|
+
"1. Go to VPC console → Transit Gateways\n"
|
|
213
|
+
f"2. Select Transit Gateway '{tgw_id}'\n"
|
|
214
|
+
"3. Actions → Modify Transit Gateway\n"
|
|
215
|
+
"4. Disable:\n"
|
|
216
|
+
" - Default route table association\n"
|
|
217
|
+
" - Default route table propagation\n"
|
|
218
|
+
" - Auto accept shared attachments\n"
|
|
219
|
+
"5. Click 'Modify'\n"
|
|
220
|
+
"6. Go to 'Route Tables' tab\n"
|
|
221
|
+
"7. Create dedicated route tables for:\n"
|
|
222
|
+
" - Production VPCs\n"
|
|
223
|
+
" - Development VPCs\n"
|
|
224
|
+
" - Shared services VPCs\n"
|
|
225
|
+
"8. Manually associate each VPC attachment to appropriate route table\n\n"
|
|
226
|
+
"Security best practices:\n"
|
|
227
|
+
"- Use separate route tables for different security zones\n"
|
|
228
|
+
"- Implement hub-and-spoke topology for centralized control\n"
|
|
229
|
+
"- Use blackhole routes to block unwanted traffic\n"
|
|
230
|
+
"- Enable VPC Flow Logs on attached VPCs\n"
|
|
231
|
+
"- Monitor Transit Gateway metrics in CloudWatch\n"
|
|
232
|
+
"- Use AWS Resource Access Manager (RAM) for controlled sharing\n"
|
|
233
|
+
"- Implement least privilege routing (only allow necessary routes)\n"
|
|
234
|
+
"- Tag route tables and attachments for easy management\n"
|
|
235
|
+
"- Regularly audit route table configurations\n"
|
|
236
|
+
"- Use AWS Network Firewall with Transit Gateway for inspection\n\n"
|
|
237
|
+
"Network isolation strategies:\n"
|
|
238
|
+
"- Production-to-production: Allow\n"
|
|
239
|
+
"- Production-to-dev: Deny (use separate route tables)\n"
|
|
240
|
+
"- Shared-services-to-all: Allow (DNS, AD, etc.)\n"
|
|
241
|
+
"- Internet-egress: Centralize through inspection VPC"
|
|
242
|
+
),
|
|
243
|
+
evidence=evidence
|
|
244
|
+
)
|
|
245
|
+
result.add_finding(finding)
|
|
246
|
+
|
|
247
|
+
self.logger.warning(
|
|
248
|
+
"transit_gateway_insecure",
|
|
249
|
+
tgw_id=tgw_id,
|
|
250
|
+
issues=issues
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# Calculate compliance score
|
|
254
|
+
result.score = (secure_tgw_count / len(transit_gateways)) * 100
|
|
255
|
+
|
|
256
|
+
# Determine pass/fail
|
|
257
|
+
result.passed = secure_tgw_count == len(transit_gateways)
|
|
258
|
+
result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
|
|
259
|
+
|
|
260
|
+
# Add metadata
|
|
261
|
+
result.metadata = {
|
|
262
|
+
"total_transit_gateways": len(transit_gateways),
|
|
263
|
+
"secure_transit_gateways": secure_tgw_count,
|
|
264
|
+
"insecure_transit_gateways": len(transit_gateways) - secure_tgw_count,
|
|
265
|
+
"compliance_percentage": result.score,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
self.logger.info(
|
|
269
|
+
"transit_gateway_security_test_completed",
|
|
270
|
+
total_transit_gateways=len(transit_gateways),
|
|
271
|
+
secure=secure_tgw_count,
|
|
272
|
+
score=result.score,
|
|
273
|
+
passed=result.passed
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
except ClientError as e:
|
|
277
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
278
|
+
self.logger.error("transit_gateway_security_test_error", error_code=error_code, error=str(e))
|
|
279
|
+
result.status = TestStatus.ERROR
|
|
280
|
+
result.passed = False
|
|
281
|
+
result.score = 0.0
|
|
282
|
+
result.error_message = f"AWS API Error: {error_code} - {str(e)}"
|
|
283
|
+
|
|
284
|
+
except Exception as e:
|
|
285
|
+
self.logger.error("transit_gateway_security_test_error", error=str(e))
|
|
286
|
+
result.status = TestStatus.ERROR
|
|
287
|
+
result.passed = False
|
|
288
|
+
result.score = 0.0
|
|
289
|
+
result.error_message = str(e)
|
|
290
|
+
|
|
291
|
+
return result
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# ============================================================================
|
|
295
|
+
# CONVENIENCE FUNCTION
|
|
296
|
+
# ============================================================================
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def run_transit_gateway_security_test(connector: AWSConnector) -> TestResult:
|
|
300
|
+
"""Run Transit Gateway security compliance test.
|
|
301
|
+
|
|
302
|
+
Convenience function for running the test.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
connector: AWS connector
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
TestResult
|
|
309
|
+
|
|
310
|
+
Example:
|
|
311
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
312
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
313
|
+
>>> connector.connect()
|
|
314
|
+
>>> result = run_transit_gateway_security_test(connector)
|
|
315
|
+
>>> print(f"Score: {result.score}%")
|
|
316
|
+
"""
|
|
317
|
+
test = TransitGatewaySecurityTest(connector)
|
|
318
|
+
return test.execute()
|