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,332 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CloudFront HTTPS enforcement compliance test.
|
|
3
|
+
|
|
4
|
+
Checks that CloudFront distributions enforce HTTPS for secure content delivery.
|
|
5
|
+
|
|
6
|
+
ISO 27001 Control: A.8.24 - Use of cryptography
|
|
7
|
+
Requirement: CloudFront distributions must use HTTPS to encrypt data in transit
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
11
|
+
>>> from complio.tests_library.network.cloudfront_https import CloudFrontHTTPSTest
|
|
12
|
+
>>>
|
|
13
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
14
|
+
>>> connector.connect()
|
|
15
|
+
>>>
|
|
16
|
+
>>> test = CloudFrontHTTPSTest(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 CloudFrontHTTPSTest(ComplianceTest):
|
|
35
|
+
"""Test for CloudFront HTTPS enforcement compliance.
|
|
36
|
+
|
|
37
|
+
Verifies that CloudFront distributions use HTTPS for secure content delivery:
|
|
38
|
+
- Viewer protocol policy should redirect HTTP to HTTPS or HTTPS-only
|
|
39
|
+
- Origin protocol policy should use HTTPS when possible
|
|
40
|
+
- Minimum TLS version should be TLS 1.2 or higher
|
|
41
|
+
- Should use modern security policy
|
|
42
|
+
|
|
43
|
+
Compliance Requirements:
|
|
44
|
+
- ViewerProtocolPolicy must be 'redirect-to-https' or 'https-only'
|
|
45
|
+
- MinimumProtocolVersion should be TLSv1.2_2021 or higher
|
|
46
|
+
- Origin should use HTTPS when communicating with backend
|
|
47
|
+
- SSL/TLS certificate should be valid
|
|
48
|
+
|
|
49
|
+
Scoring:
|
|
50
|
+
- 100% if all distributions enforce HTTPS properly
|
|
51
|
+
- Proportional score based on compliant/total ratio
|
|
52
|
+
- 100% if no distributions exist
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
>>> test = CloudFrontHTTPSTest(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 CloudFront HTTPS test.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
connector: AWS connector instance
|
|
66
|
+
"""
|
|
67
|
+
super().__init__(
|
|
68
|
+
test_id="cloudfront_https",
|
|
69
|
+
test_name="CloudFront HTTPS Enforcement Check",
|
|
70
|
+
description="Verify CloudFront distributions enforce HTTPS for secure content delivery",
|
|
71
|
+
control_id="A.8.24",
|
|
72
|
+
connector=connector,
|
|
73
|
+
scope="global", # CloudFront is a global service
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def execute(self) -> TestResult:
|
|
77
|
+
"""Execute CloudFront HTTPS compliance test.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
TestResult with findings for insecure distributions
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> test = CloudFrontHTTPSTest(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 CloudFront client (global service, no region needed)
|
|
98
|
+
cloudfront_client = self.connector.get_client("cloudfront")
|
|
99
|
+
|
|
100
|
+
# List all distributions
|
|
101
|
+
self.logger.info("listing_cloudfront_distributions")
|
|
102
|
+
distributions = []
|
|
103
|
+
|
|
104
|
+
paginator = cloudfront_client.get_paginator("list_distributions")
|
|
105
|
+
for page in paginator.paginate():
|
|
106
|
+
dist_list = page.get("DistributionList", {})
|
|
107
|
+
items = dist_list.get("Items", [])
|
|
108
|
+
distributions.extend(items)
|
|
109
|
+
|
|
110
|
+
if not distributions:
|
|
111
|
+
self.logger.info("no_cloudfront_distributions_found")
|
|
112
|
+
result.metadata["message"] = "No CloudFront distributions found"
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
self.logger.info("cloudfront_distributions_found", count=len(distributions))
|
|
116
|
+
|
|
117
|
+
# Check HTTPS configuration for each distribution
|
|
118
|
+
https_enforced_count = 0
|
|
119
|
+
|
|
120
|
+
for dist_summary in distributions:
|
|
121
|
+
dist_id = dist_summary["Id"]
|
|
122
|
+
dist_domain = dist_summary.get("DomainName", "")
|
|
123
|
+
dist_status = dist_summary.get("Status", "")
|
|
124
|
+
|
|
125
|
+
result.resources_scanned += 1
|
|
126
|
+
|
|
127
|
+
# Get full distribution configuration
|
|
128
|
+
dist_response = cloudfront_client.get_distribution(Id=dist_id)
|
|
129
|
+
dist_config = dist_response.get("Distribution", {}).get("DistributionConfig", {})
|
|
130
|
+
|
|
131
|
+
# Check viewer protocol policy in default cache behavior
|
|
132
|
+
default_cache_behavior = dist_config.get("DefaultCacheBehavior", {})
|
|
133
|
+
viewer_protocol_policy = default_cache_behavior.get("ViewerProtocolPolicy", "allow-all")
|
|
134
|
+
|
|
135
|
+
# Check minimum protocol version
|
|
136
|
+
viewer_certificate = dist_config.get("ViewerCertificate", {})
|
|
137
|
+
min_protocol_version = viewer_certificate.get("MinimumProtocolVersion", "SSLv3")
|
|
138
|
+
|
|
139
|
+
# Check origin protocol policy
|
|
140
|
+
origins = dist_config.get("Origins", {}).get("Items", [])
|
|
141
|
+
insecure_origins = []
|
|
142
|
+
for origin in origins:
|
|
143
|
+
custom_origin_config = origin.get("CustomOriginConfig", {})
|
|
144
|
+
if custom_origin_config:
|
|
145
|
+
origin_protocol_policy = custom_origin_config.get("OriginProtocolPolicy", "http-only")
|
|
146
|
+
if origin_protocol_policy == "http-only":
|
|
147
|
+
insecure_origins.append(origin.get("Id", "unknown"))
|
|
148
|
+
|
|
149
|
+
# Determine if configuration is secure
|
|
150
|
+
issues = []
|
|
151
|
+
severity = Severity.MEDIUM
|
|
152
|
+
|
|
153
|
+
# Check viewer protocol policy
|
|
154
|
+
if viewer_protocol_policy not in ["redirect-to-https", "https-only"]:
|
|
155
|
+
issues.append(f"Viewer protocol allows HTTP (policy: {viewer_protocol_policy})")
|
|
156
|
+
severity = Severity.HIGH
|
|
157
|
+
|
|
158
|
+
# Check minimum TLS version
|
|
159
|
+
if min_protocol_version not in ["TLSv1.2_2021", "TLSv1.2_2019", "TLSv1.2_2018", "TLSv1.1_2016"]:
|
|
160
|
+
if "TLSv1.2" not in min_protocol_version and "TLSv1.3" not in min_protocol_version:
|
|
161
|
+
issues.append(f"Outdated minimum TLS version: {min_protocol_version}")
|
|
162
|
+
severity = Severity.HIGH
|
|
163
|
+
|
|
164
|
+
# Check origin protocol
|
|
165
|
+
if insecure_origins:
|
|
166
|
+
issues.append(f"Origins using HTTP-only: {', '.join(insecure_origins)}")
|
|
167
|
+
severity = Severity.MEDIUM
|
|
168
|
+
|
|
169
|
+
# Create evidence
|
|
170
|
+
evidence = self.create_evidence(
|
|
171
|
+
resource_id=dist_id,
|
|
172
|
+
resource_type="cloudfront_distribution",
|
|
173
|
+
data={
|
|
174
|
+
"distribution_id": dist_id,
|
|
175
|
+
"domain_name": dist_domain,
|
|
176
|
+
"status": dist_status,
|
|
177
|
+
"viewer_protocol_policy": viewer_protocol_policy,
|
|
178
|
+
"minimum_protocol_version": min_protocol_version,
|
|
179
|
+
"insecure_origins": insecure_origins,
|
|
180
|
+
"has_issues": len(issues) > 0,
|
|
181
|
+
"issues": issues,
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
result.add_evidence(evidence)
|
|
185
|
+
|
|
186
|
+
if len(issues) == 0:
|
|
187
|
+
https_enforced_count += 1
|
|
188
|
+
self.logger.debug(
|
|
189
|
+
"cloudfront_https_enforced",
|
|
190
|
+
distribution_id=dist_id
|
|
191
|
+
)
|
|
192
|
+
else:
|
|
193
|
+
# Create finding for insecure distribution
|
|
194
|
+
finding = self.create_finding(
|
|
195
|
+
resource_id=dist_id,
|
|
196
|
+
resource_type="cloudfront_distribution",
|
|
197
|
+
severity=severity,
|
|
198
|
+
title="CloudFront distribution not properly enforcing HTTPS",
|
|
199
|
+
description=f"CloudFront distribution '{dist_id}' (domain: {dist_domain}) has HTTPS "
|
|
200
|
+
f"enforcement issues: {'; '.join(issues)}. CloudFront distributions should "
|
|
201
|
+
"enforce HTTPS to encrypt data in transit, use modern TLS versions (1.2+), "
|
|
202
|
+
"and communicate with origins securely. ISO 27001 A.8.24 requires "
|
|
203
|
+
"cryptographic controls for data in transit.",
|
|
204
|
+
remediation=(
|
|
205
|
+
f"Improve CloudFront distribution '{dist_id}' HTTPS configuration:\n\n"
|
|
206
|
+
"1. Enforce HTTPS for viewers:\n"
|
|
207
|
+
f"aws cloudfront get-distribution-config --id {dist_id} > dist-config.json\n"
|
|
208
|
+
"# Edit dist-config.json:\n"
|
|
209
|
+
"# Set DefaultCacheBehavior.ViewerProtocolPolicy to 'redirect-to-https' or 'https-only'\n"
|
|
210
|
+
f"aws cloudfront update-distribution \\\n"
|
|
211
|
+
f" --id {dist_id} \\\n"
|
|
212
|
+
" --distribution-config file://dist-config.json \\\n"
|
|
213
|
+
" --if-match <ETag-from-get-distribution-config>\n\n"
|
|
214
|
+
"2. Update minimum TLS version:\n"
|
|
215
|
+
"# In dist-config.json:\n"
|
|
216
|
+
"# Set ViewerCertificate.MinimumProtocolVersion to 'TLSv1.2_2021'\n\n"
|
|
217
|
+
"3. Use HTTPS for origin communication:\n"
|
|
218
|
+
"# In dist-config.json:\n"
|
|
219
|
+
"# For each CustomOriginConfig:\n"
|
|
220
|
+
"# Set OriginProtocolPolicy to 'https-only' or 'match-viewer'\n\n"
|
|
221
|
+
"Or use AWS Console:\n"
|
|
222
|
+
"1. Go to CloudFront console\n"
|
|
223
|
+
f"2. Select distribution '{dist_id}'\n"
|
|
224
|
+
"3. Go to 'Behaviors' tab\n"
|
|
225
|
+
"4. Edit default behavior:\n"
|
|
226
|
+
" - Viewer Protocol Policy: 'Redirect HTTP to HTTPS'\n"
|
|
227
|
+
"5. Go to 'General' tab → Edit:\n"
|
|
228
|
+
" - Custom SSL Certificate (if using custom domain)\n"
|
|
229
|
+
" - Security Policy: TLSv1.2_2021 (recommended)\n"
|
|
230
|
+
"6. Go to 'Origins' tab\n"
|
|
231
|
+
"7. Edit each origin:\n"
|
|
232
|
+
" - Protocol: HTTPS only\n"
|
|
233
|
+
" - Minimum Origin SSL Protocol: TLSv1.2\n\n"
|
|
234
|
+
"Recommended configurations:\n"
|
|
235
|
+
"Viewer Protocol Policy:\n"
|
|
236
|
+
"- redirect-to-https (recommended for public sites)\n"
|
|
237
|
+
"- https-only (for APIs or sensitive content)\n\n"
|
|
238
|
+
"Security Policy (Minimum TLS Version):\n"
|
|
239
|
+
"- TLSv1.2_2021 (recommended, supports TLS 1.2 and 1.3)\n"
|
|
240
|
+
"- TLSv1.2_2019\n"
|
|
241
|
+
"- TLSv1.2_2018\n\n"
|
|
242
|
+
"Origin Protocol Policy:\n"
|
|
243
|
+
"- https-only (most secure)\n"
|
|
244
|
+
"- match-viewer (flexible, but ensure viewers use HTTPS)\n\n"
|
|
245
|
+
"Additional security best practices:\n"
|
|
246
|
+
"- Use AWS Certificate Manager (ACM) for SSL/TLS certificates\n"
|
|
247
|
+
"- Enable automatic certificate renewal\n"
|
|
248
|
+
"- Configure custom SSL certificate for custom domains\n"
|
|
249
|
+
"- Enable AWS WAF for web application protection\n"
|
|
250
|
+
"- Enable access logging for audit trail\n"
|
|
251
|
+
"- Use Origin Access Identity (OAI) for S3 origins\n"
|
|
252
|
+
"- Enable field-level encryption for sensitive data\n"
|
|
253
|
+
"- Configure security headers (HSTS, CSP, etc.)\n"
|
|
254
|
+
"- Monitor CloudFront metrics in CloudWatch\n"
|
|
255
|
+
"- Use signed URLs/cookies for private content"
|
|
256
|
+
),
|
|
257
|
+
evidence=evidence
|
|
258
|
+
)
|
|
259
|
+
result.add_finding(finding)
|
|
260
|
+
|
|
261
|
+
self.logger.warning(
|
|
262
|
+
"cloudfront_https_not_enforced",
|
|
263
|
+
distribution_id=dist_id,
|
|
264
|
+
issues=issues
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Calculate compliance score
|
|
268
|
+
result.score = (https_enforced_count / len(distributions)) * 100
|
|
269
|
+
|
|
270
|
+
# Determine pass/fail
|
|
271
|
+
result.passed = https_enforced_count == len(distributions)
|
|
272
|
+
result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
|
|
273
|
+
|
|
274
|
+
# Add metadata
|
|
275
|
+
result.metadata = {
|
|
276
|
+
"total_distributions": len(distributions),
|
|
277
|
+
"https_enforced": https_enforced_count,
|
|
278
|
+
"insecure_distributions": len(distributions) - https_enforced_count,
|
|
279
|
+
"compliance_percentage": result.score,
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
self.logger.info(
|
|
283
|
+
"cloudfront_https_test_completed",
|
|
284
|
+
total_distributions=len(distributions),
|
|
285
|
+
https_enforced=https_enforced_count,
|
|
286
|
+
score=result.score,
|
|
287
|
+
passed=result.passed
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
except ClientError as e:
|
|
291
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
292
|
+
self.logger.error("cloudfront_https_test_error", error_code=error_code, error=str(e))
|
|
293
|
+
result.status = TestStatus.ERROR
|
|
294
|
+
result.passed = False
|
|
295
|
+
result.score = 0.0
|
|
296
|
+
result.error_message = f"AWS API Error: {error_code} - {str(e)}"
|
|
297
|
+
|
|
298
|
+
except Exception as e:
|
|
299
|
+
self.logger.error("cloudfront_https_test_error", error=str(e))
|
|
300
|
+
result.status = TestStatus.ERROR
|
|
301
|
+
result.passed = False
|
|
302
|
+
result.score = 0.0
|
|
303
|
+
result.error_message = str(e)
|
|
304
|
+
|
|
305
|
+
return result
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# ============================================================================
|
|
309
|
+
# CONVENIENCE FUNCTION
|
|
310
|
+
# ============================================================================
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def run_cloudfront_https_test(connector: AWSConnector) -> TestResult:
|
|
314
|
+
"""Run CloudFront HTTPS enforcement compliance test.
|
|
315
|
+
|
|
316
|
+
Convenience function for running the test.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
connector: AWS connector
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
TestResult
|
|
323
|
+
|
|
324
|
+
Example:
|
|
325
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
326
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
327
|
+
>>> connector.connect()
|
|
328
|
+
>>> result = run_cloudfront_https_test(connector)
|
|
329
|
+
>>> print(f"Score: {result.score}%")
|
|
330
|
+
"""
|
|
331
|
+
test = CloudFrontHTTPSTest(connector)
|
|
332
|
+
return test.execute()
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Direct Connect security compliance test.
|
|
3
|
+
|
|
4
|
+
Checks that Direct Connect connections use secure configurations.
|
|
5
|
+
|
|
6
|
+
ISO 27001 Control: A.8.22 - Network segregation
|
|
7
|
+
Requirement: Direct Connect connections should use encryption and proper configurations
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
11
|
+
>>> from complio.tests_library.network.direct_connect_security import DirectConnectSecurityTest
|
|
12
|
+
>>>
|
|
13
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
14
|
+
>>> connector.connect()
|
|
15
|
+
>>>
|
|
16
|
+
>>> test = DirectConnectSecurityTest(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 DirectConnectSecurityTest(ComplianceTest):
|
|
35
|
+
"""Test for AWS Direct Connect security compliance.
|
|
36
|
+
|
|
37
|
+
Verifies that Direct Connect connections use secure configurations:
|
|
38
|
+
- Virtual interfaces should use MACsec encryption when possible
|
|
39
|
+
- Connection state should be available (not down)
|
|
40
|
+
- Should have proper tagging for management
|
|
41
|
+
|
|
42
|
+
Compliance Requirements:
|
|
43
|
+
- Use MACsec encryption for Layer 2 security (when supported)
|
|
44
|
+
- Connections should be in available state
|
|
45
|
+
- Virtual interfaces properly configured
|
|
46
|
+
|
|
47
|
+
Scoring:
|
|
48
|
+
- 100% if all Direct Connect resources follow security best practices
|
|
49
|
+
- Proportional score based on compliant/total ratio
|
|
50
|
+
- 100% if no Direct Connect connections exist
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
>>> test = DirectConnectSecurityTest(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 Direct Connect security test.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
connector: AWS connector instance
|
|
64
|
+
"""
|
|
65
|
+
super().__init__(
|
|
66
|
+
test_id="direct_connect_security",
|
|
67
|
+
test_name="AWS Direct Connect Security Check",
|
|
68
|
+
description="Verify Direct Connect connections use secure configurations",
|
|
69
|
+
control_id="A.8.22",
|
|
70
|
+
connector=connector,
|
|
71
|
+
scope="regional",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def execute(self) -> TestResult:
|
|
75
|
+
"""Execute Direct Connect security compliance test.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
TestResult with findings for insecure Direct Connect configurations
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
>>> test = DirectConnectSecurityTest(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 Direct Connect client
|
|
96
|
+
dx_client = self.connector.get_client("directconnect")
|
|
97
|
+
|
|
98
|
+
# List all Direct Connect connections
|
|
99
|
+
self.logger.info("listing_direct_connect_connections")
|
|
100
|
+
connections_response = dx_client.describe_connections()
|
|
101
|
+
connections = connections_response.get("connections", [])
|
|
102
|
+
|
|
103
|
+
if not connections:
|
|
104
|
+
self.logger.info("no_direct_connect_connections_found")
|
|
105
|
+
result.metadata["message"] = "No Direct Connect connections found in region"
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
self.logger.info("direct_connect_connections_found", count=len(connections))
|
|
109
|
+
|
|
110
|
+
# Check security for each connection
|
|
111
|
+
secure_connection_count = 0
|
|
112
|
+
|
|
113
|
+
for connection in connections:
|
|
114
|
+
connection_id = connection.get("connectionId", "")
|
|
115
|
+
connection_name = connection.get("connectionName", "")
|
|
116
|
+
connection_state = connection.get("connectionState", "")
|
|
117
|
+
location = connection.get("location", "")
|
|
118
|
+
bandwidth = connection.get("bandwidth", "")
|
|
119
|
+
|
|
120
|
+
# Skip deleted connections
|
|
121
|
+
if connection_state in ["deleted", "deleting"]:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
result.resources_scanned += 1
|
|
125
|
+
|
|
126
|
+
# Check for security issues
|
|
127
|
+
issues = []
|
|
128
|
+
severity = Severity.MEDIUM
|
|
129
|
+
|
|
130
|
+
# Check connection state
|
|
131
|
+
if connection_state not in ["available", "requested", "pending"]:
|
|
132
|
+
issues.append(f"Connection not in healthy state: {connection_state}")
|
|
133
|
+
severity = Severity.HIGH
|
|
134
|
+
|
|
135
|
+
# Check if MACsec is supported and enabled
|
|
136
|
+
has_mac_sec_capability = connection.get("hasLogicalRedundancy", False)
|
|
137
|
+
mac_sec_keys = connection.get("macSecKeys", [])
|
|
138
|
+
|
|
139
|
+
# MACsec provides Layer 2 encryption
|
|
140
|
+
if has_mac_sec_capability and len(mac_sec_keys) == 0:
|
|
141
|
+
issues.append("MACsec capable but not configured (encryption not enabled)")
|
|
142
|
+
severity = Severity.MEDIUM
|
|
143
|
+
|
|
144
|
+
# Get associated virtual interfaces
|
|
145
|
+
vifs_response = dx_client.describe_virtual_interfaces(connectionId=connection_id)
|
|
146
|
+
virtual_interfaces = vifs_response.get("virtualInterfaces", [])
|
|
147
|
+
|
|
148
|
+
# Check virtual interface configurations
|
|
149
|
+
vif_issues = []
|
|
150
|
+
for vif in virtual_interfaces:
|
|
151
|
+
vif_state = vif.get("virtualInterfaceState", "")
|
|
152
|
+
if vif_state == "down":
|
|
153
|
+
vif_issues.append(f"Virtual interface {vif.get('virtualInterfaceId')} is down")
|
|
154
|
+
|
|
155
|
+
if vif_issues:
|
|
156
|
+
issues.extend(vif_issues)
|
|
157
|
+
severity = Severity.HIGH
|
|
158
|
+
|
|
159
|
+
# Create evidence
|
|
160
|
+
evidence = self.create_evidence(
|
|
161
|
+
resource_id=connection_id,
|
|
162
|
+
resource_type="direct_connect_connection",
|
|
163
|
+
data={
|
|
164
|
+
"connection_id": connection_id,
|
|
165
|
+
"connection_name": connection_name,
|
|
166
|
+
"connection_state": connection_state,
|
|
167
|
+
"location": location,
|
|
168
|
+
"bandwidth": bandwidth,
|
|
169
|
+
"has_macsec_capability": has_mac_sec_capability,
|
|
170
|
+
"macsec_configured": len(mac_sec_keys) > 0,
|
|
171
|
+
"virtual_interfaces_count": len(virtual_interfaces),
|
|
172
|
+
"has_issues": len(issues) > 0,
|
|
173
|
+
"issues": issues,
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
result.add_evidence(evidence)
|
|
177
|
+
|
|
178
|
+
if len(issues) == 0:
|
|
179
|
+
secure_connection_count += 1
|
|
180
|
+
self.logger.debug(
|
|
181
|
+
"direct_connect_secure",
|
|
182
|
+
connection_id=connection_id
|
|
183
|
+
)
|
|
184
|
+
else:
|
|
185
|
+
# Create finding for insecure Direct Connect connection
|
|
186
|
+
finding = self.create_finding(
|
|
187
|
+
resource_id=connection_id,
|
|
188
|
+
resource_type="direct_connect_connection",
|
|
189
|
+
severity=severity,
|
|
190
|
+
title="Direct Connect connection has security issues",
|
|
191
|
+
description=f"Direct Connect connection '{connection_name}' ({connection_id}) at location "
|
|
192
|
+
f"'{location}' has security issues: {'; '.join(issues)}. Direct Connect "
|
|
193
|
+
"connections should use MACsec encryption when available, maintain healthy "
|
|
194
|
+
"connection states, and have properly configured virtual interfaces. "
|
|
195
|
+
"ISO 27001 A.8.22 requires secure network connections and encryption.",
|
|
196
|
+
remediation=(
|
|
197
|
+
f"Improve Direct Connect connection '{connection_id}' security:\n\n"
|
|
198
|
+
"1. Enable MACsec encryption (if supported):\n"
|
|
199
|
+
"# First, associate a MACsec secret key\n"
|
|
200
|
+
"aws directconnect associate-mac-sec-key \\\n"
|
|
201
|
+
f" --connection-id {connection_id} \\\n"
|
|
202
|
+
" --secret-arn <SECRETS-MANAGER-ARN>\n\n"
|
|
203
|
+
"# Verify MACsec is active\n"
|
|
204
|
+
f"aws directconnect describe-connections \\\n"
|
|
205
|
+
f" --connection-id {connection_id}\n\n"
|
|
206
|
+
"2. Check connection health:\n"
|
|
207
|
+
f"aws directconnect describe-connections \\\n"
|
|
208
|
+
f" --connection-id {connection_id}\n\n"
|
|
209
|
+
"If connection is down, contact AWS Support:\n"
|
|
210
|
+
"- Check Letter of Authorization (LOA) status\n"
|
|
211
|
+
"- Verify physical connectivity at colocation\n"
|
|
212
|
+
"- Check BGP configuration\n\n"
|
|
213
|
+
"3. Configure virtual interfaces properly:\n"
|
|
214
|
+
"# List all virtual interfaces\n"
|
|
215
|
+
f"aws directconnect describe-virtual-interfaces \\\n"
|
|
216
|
+
f" --connection-id {connection_id}\n\n"
|
|
217
|
+
"# For down virtual interfaces, check:\n"
|
|
218
|
+
"- BGP peering status\n"
|
|
219
|
+
"- VLAN configuration\n"
|
|
220
|
+
"- Route propagation\n"
|
|
221
|
+
"- Security group rules\n\n"
|
|
222
|
+
"Or use AWS Console:\n"
|
|
223
|
+
"1. Go to Direct Connect console\n"
|
|
224
|
+
"2. Select Connections\n"
|
|
225
|
+
f"3. Choose connection '{connection_name}'\n"
|
|
226
|
+
"4. Enable MACsec:\n"
|
|
227
|
+
" - Select 'Actions' → 'Associate MACsec key'\n"
|
|
228
|
+
" - Choose secret from Secrets Manager\n"
|
|
229
|
+
" - Verify MACsec status shows 'enabled'\n"
|
|
230
|
+
"5. Check virtual interfaces:\n"
|
|
231
|
+
" - Go to 'Virtual interfaces' tab\n"
|
|
232
|
+
" - Verify each VIF is in 'available' state\n"
|
|
233
|
+
" - Check BGP session status\n\n"
|
|
234
|
+
"Security best practices:\n"
|
|
235
|
+
"- Enable MACsec for Layer 2 encryption (10 Gbps and above)\n"
|
|
236
|
+
"- Use redundant connections for high availability\n"
|
|
237
|
+
"- Implement BGP authentication (MD5)\n"
|
|
238
|
+
"- Use VPN over Direct Connect for additional encryption\n"
|
|
239
|
+
"- Configure proper route filtering and BGP communities\n"
|
|
240
|
+
"- Enable Connection Health monitoring\n"
|
|
241
|
+
"- Use AWS Transit Gateway with Direct Connect Gateway\n"
|
|
242
|
+
"- Implement least privilege routing\n"
|
|
243
|
+
"- Tag connections for cost allocation and management\n"
|
|
244
|
+
"- Monitor with CloudWatch metrics:\n"
|
|
245
|
+
" • ConnectionState\n"
|
|
246
|
+
" • ConnectionBpsEgress/Ingress\n"
|
|
247
|
+
" • ConnectionPpsEgress/Ingress\n"
|
|
248
|
+
" • ConnectionLightLevelTx/Rx\n\n"
|
|
249
|
+
"MACsec encryption requirements:\n"
|
|
250
|
+
"- Supported on 10 Gbps and 100 Gbps connections\n"
|
|
251
|
+
"- Requires MACsec capable device at customer end\n"
|
|
252
|
+
"- Uses AES-256 GCM encryption\n"
|
|
253
|
+
"- Store keys in AWS Secrets Manager\n"
|
|
254
|
+
"- Rotate keys regularly\n\n"
|
|
255
|
+
"High availability setup:\n"
|
|
256
|
+
"- Deploy connections in multiple locations\n"
|
|
257
|
+
"- Use LAG (Link Aggregation Groups) when possible\n"
|
|
258
|
+
"- Configure BFD (Bidirectional Forwarding Detection)\n"
|
|
259
|
+
"- Implement active-active or active-passive failover\n"
|
|
260
|
+
"- Test failover scenarios regularly\n\n"
|
|
261
|
+
"Additional security layers:\n"
|
|
262
|
+
"- Use Site-to-Site VPN as backup\n"
|
|
263
|
+
"- Implement IPsec over Direct Connect for end-to-end encryption\n"
|
|
264
|
+
"- Use AWS PrivateLink for service-level isolation\n"
|
|
265
|
+
"- Configure network ACLs and security groups\n"
|
|
266
|
+
"- Enable VPC Flow Logs for traffic analysis"
|
|
267
|
+
),
|
|
268
|
+
evidence=evidence
|
|
269
|
+
)
|
|
270
|
+
result.add_finding(finding)
|
|
271
|
+
|
|
272
|
+
self.logger.warning(
|
|
273
|
+
"direct_connect_insecure",
|
|
274
|
+
connection_id=connection_id,
|
|
275
|
+
issues=issues
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# Calculate compliance score
|
|
279
|
+
result.score = (secure_connection_count / len(connections)) * 100
|
|
280
|
+
|
|
281
|
+
# Determine pass/fail
|
|
282
|
+
result.passed = secure_connection_count == len(connections)
|
|
283
|
+
result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
|
|
284
|
+
|
|
285
|
+
# Add metadata
|
|
286
|
+
result.metadata = {
|
|
287
|
+
"total_connections": len(connections),
|
|
288
|
+
"secure_connections": secure_connection_count,
|
|
289
|
+
"insecure_connections": len(connections) - secure_connection_count,
|
|
290
|
+
"compliance_percentage": result.score,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
self.logger.info(
|
|
294
|
+
"direct_connect_security_test_completed",
|
|
295
|
+
total_connections=len(connections),
|
|
296
|
+
secure=secure_connection_count,
|
|
297
|
+
score=result.score,
|
|
298
|
+
passed=result.passed
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
except ClientError as e:
|
|
302
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
303
|
+
self.logger.error("direct_connect_security_test_error", error_code=error_code, error=str(e))
|
|
304
|
+
result.status = TestStatus.ERROR
|
|
305
|
+
result.passed = False
|
|
306
|
+
result.score = 0.0
|
|
307
|
+
result.error_message = f"AWS API Error: {error_code} - {str(e)}"
|
|
308
|
+
|
|
309
|
+
except Exception as e:
|
|
310
|
+
self.logger.error("direct_connect_security_test_error", error=str(e))
|
|
311
|
+
result.status = TestStatus.ERROR
|
|
312
|
+
result.passed = False
|
|
313
|
+
result.score = 0.0
|
|
314
|
+
result.error_message = str(e)
|
|
315
|
+
|
|
316
|
+
return result
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
# ============================================================================
|
|
320
|
+
# CONVENIENCE FUNCTION
|
|
321
|
+
# ============================================================================
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def run_direct_connect_security_test(connector: AWSConnector) -> TestResult:
|
|
325
|
+
"""Run AWS Direct Connect security compliance test.
|
|
326
|
+
|
|
327
|
+
Convenience function for running the test.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
connector: AWS connector
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
TestResult
|
|
334
|
+
|
|
335
|
+
Example:
|
|
336
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
337
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
338
|
+
>>> connector.connect()
|
|
339
|
+
>>> result = run_direct_connect_security_test(connector)
|
|
340
|
+
>>> print(f"Score: {result.score}%")
|
|
341
|
+
"""
|
|
342
|
+
test = DirectConnectSecurityTest(connector)
|
|
343
|
+
return test.execute()
|