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,339 @@
|
|
|
1
|
+
"""
|
|
2
|
+
VPC Endpoints security compliance test.
|
|
3
|
+
|
|
4
|
+
Checks that VPC Endpoints use secure configurations and policies.
|
|
5
|
+
|
|
6
|
+
ISO 27001 Control: A.8.22 - Network segregation
|
|
7
|
+
Requirement: VPC Endpoints should use restrictive policies and private DNS
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
11
|
+
>>> from complio.tests_library.network.vpc_endpoints_security import VPCEndpointsSecurityTest
|
|
12
|
+
>>>
|
|
13
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
14
|
+
>>> connector.connect()
|
|
15
|
+
>>>
|
|
16
|
+
>>> test = VPCEndpointsSecurityTest(connector)
|
|
17
|
+
>>> result = test.run()
|
|
18
|
+
>>> print(f"Passed: {result.passed}, Score: {result.score}")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import Any, Dict
|
|
22
|
+
import json
|
|
23
|
+
|
|
24
|
+
from botocore.exceptions import ClientError
|
|
25
|
+
|
|
26
|
+
from complio.connectors.aws.client import AWSConnector
|
|
27
|
+
from complio.tests_library.base import (
|
|
28
|
+
ComplianceTest,
|
|
29
|
+
Severity,
|
|
30
|
+
TestResult,
|
|
31
|
+
TestStatus,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class VPCEndpointsSecurityTest(ComplianceTest):
|
|
36
|
+
"""Test for VPC Endpoints security compliance.
|
|
37
|
+
|
|
38
|
+
Verifies that VPC Endpoints use secure configurations:
|
|
39
|
+
- Interface endpoints should have Private DNS enabled
|
|
40
|
+
- Endpoint policies should not be overly permissive (not full access)
|
|
41
|
+
- Gateway endpoints (S3, DynamoDB) should use restrictive policies
|
|
42
|
+
|
|
43
|
+
Compliance Requirements:
|
|
44
|
+
- Interface endpoints should enable Private DNS
|
|
45
|
+
- Endpoint policies should follow least privilege
|
|
46
|
+
- Avoid "*" in policy statements
|
|
47
|
+
|
|
48
|
+
Scoring:
|
|
49
|
+
- 100% if all VPC Endpoints follow security best practices
|
|
50
|
+
- Proportional score based on compliant/total ratio
|
|
51
|
+
- 100% if no VPC Endpoints exist
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
>>> test = VPCEndpointsSecurityTest(connector)
|
|
55
|
+
>>> result = test.execute()
|
|
56
|
+
>>> for finding in result.findings:
|
|
57
|
+
... print(f"{finding.resource_id}: {finding.title}")
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, connector: AWSConnector) -> None:
|
|
61
|
+
"""Initialize VPC Endpoints security test.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
connector: AWS connector instance
|
|
65
|
+
"""
|
|
66
|
+
super().__init__(
|
|
67
|
+
test_id="vpc_endpoints_security",
|
|
68
|
+
test_name="VPC Endpoints Security Check",
|
|
69
|
+
description="Verify VPC Endpoints use secure configurations and policies",
|
|
70
|
+
control_id="A.8.22",
|
|
71
|
+
connector=connector,
|
|
72
|
+
scope="regional",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def execute(self) -> TestResult:
|
|
76
|
+
"""Execute VPC Endpoints security compliance test.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
TestResult with findings for insecure VPC Endpoints
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> test = VPCEndpointsSecurityTest(connector)
|
|
83
|
+
>>> result = test.execute()
|
|
84
|
+
>>> print(result.score)
|
|
85
|
+
100.0
|
|
86
|
+
"""
|
|
87
|
+
result = TestResult(
|
|
88
|
+
test_id=self.test_id,
|
|
89
|
+
test_name=self.test_name,
|
|
90
|
+
status=TestStatus.PASSED,
|
|
91
|
+
passed=True,
|
|
92
|
+
score=100.0,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
# Get EC2 client
|
|
97
|
+
ec2_client = self.connector.get_client("ec2")
|
|
98
|
+
|
|
99
|
+
# List all VPC Endpoints
|
|
100
|
+
self.logger.info("listing_vpc_endpoints")
|
|
101
|
+
endpoints_response = ec2_client.describe_vpc_endpoints()
|
|
102
|
+
vpc_endpoints = endpoints_response.get("VpcEndpoints", [])
|
|
103
|
+
|
|
104
|
+
if not vpc_endpoints:
|
|
105
|
+
self.logger.info("no_vpc_endpoints_found")
|
|
106
|
+
result.metadata["message"] = "No VPC Endpoints found in region"
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
self.logger.info("vpc_endpoints_found", count=len(vpc_endpoints))
|
|
110
|
+
|
|
111
|
+
# Check security for each VPC Endpoint
|
|
112
|
+
secure_endpoint_count = 0
|
|
113
|
+
|
|
114
|
+
for endpoint in vpc_endpoints:
|
|
115
|
+
endpoint_id = endpoint["VpcEndpointId"]
|
|
116
|
+
endpoint_type = endpoint.get("VpcEndpointType", "")
|
|
117
|
+
service_name = endpoint.get("ServiceName", "")
|
|
118
|
+
vpc_id = endpoint.get("VpcId", "")
|
|
119
|
+
state = endpoint.get("State", "")
|
|
120
|
+
|
|
121
|
+
# Skip endpoints that are being deleted
|
|
122
|
+
if state in ["deleted", "deleting", "failed"]:
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
result.resources_scanned += 1
|
|
126
|
+
|
|
127
|
+
# Determine security issues
|
|
128
|
+
issues = []
|
|
129
|
+
severity = Severity.MEDIUM
|
|
130
|
+
|
|
131
|
+
# Check for Interface endpoints
|
|
132
|
+
if endpoint_type == "Interface":
|
|
133
|
+
private_dns_enabled = endpoint.get("PrivateDnsEnabled", False)
|
|
134
|
+
if not private_dns_enabled:
|
|
135
|
+
issues.append("Private DNS not enabled for interface endpoint")
|
|
136
|
+
severity = Severity.MEDIUM
|
|
137
|
+
|
|
138
|
+
# Check endpoint policy
|
|
139
|
+
policy_document = endpoint.get("PolicyDocument")
|
|
140
|
+
if policy_document:
|
|
141
|
+
try:
|
|
142
|
+
policy = json.loads(policy_document)
|
|
143
|
+
# Check for overly permissive policies
|
|
144
|
+
if isinstance(policy, dict):
|
|
145
|
+
statements = policy.get("Statement", [])
|
|
146
|
+
for statement in statements:
|
|
147
|
+
if isinstance(statement, dict):
|
|
148
|
+
effect = statement.get("Effect", "")
|
|
149
|
+
action = statement.get("Action", "")
|
|
150
|
+
resource = statement.get("Resource", "")
|
|
151
|
+
principal = statement.get("Principal", "")
|
|
152
|
+
|
|
153
|
+
# Check for full access
|
|
154
|
+
if effect == "Allow" and action == "*" and (resource == "*" or resource == ["*"]):
|
|
155
|
+
issues.append("Endpoint policy allows all actions on all resources (too permissive)")
|
|
156
|
+
severity = Severity.HIGH
|
|
157
|
+
|
|
158
|
+
# Check for any principal
|
|
159
|
+
if effect == "Allow" and principal == "*":
|
|
160
|
+
issues.append("Endpoint policy allows any principal (too permissive)")
|
|
161
|
+
severity = Severity.HIGH
|
|
162
|
+
|
|
163
|
+
except json.JSONDecodeError:
|
|
164
|
+
issues.append("Endpoint policy is not valid JSON")
|
|
165
|
+
|
|
166
|
+
# Create evidence
|
|
167
|
+
evidence = self.create_evidence(
|
|
168
|
+
resource_id=endpoint_id,
|
|
169
|
+
resource_type="vpc_endpoint",
|
|
170
|
+
data={
|
|
171
|
+
"vpc_endpoint_id": endpoint_id,
|
|
172
|
+
"vpc_id": vpc_id,
|
|
173
|
+
"type": endpoint_type,
|
|
174
|
+
"service_name": service_name,
|
|
175
|
+
"state": state,
|
|
176
|
+
"private_dns_enabled": endpoint.get("PrivateDnsEnabled"),
|
|
177
|
+
"has_issues": len(issues) > 0,
|
|
178
|
+
"issues": issues,
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
result.add_evidence(evidence)
|
|
182
|
+
|
|
183
|
+
if len(issues) == 0:
|
|
184
|
+
secure_endpoint_count += 1
|
|
185
|
+
self.logger.debug(
|
|
186
|
+
"vpc_endpoint_secure",
|
|
187
|
+
endpoint_id=endpoint_id
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
# Create finding for insecure VPC Endpoint
|
|
191
|
+
finding = self.create_finding(
|
|
192
|
+
resource_id=endpoint_id,
|
|
193
|
+
resource_type="vpc_endpoint",
|
|
194
|
+
severity=severity,
|
|
195
|
+
title="VPC Endpoint has security configuration issues",
|
|
196
|
+
description=f"VPC Endpoint '{endpoint_id}' (service: {service_name}) in VPC '{vpc_id}' has "
|
|
197
|
+
f"security configuration issues: {'; '.join(issues)}. VPC Endpoints should enable "
|
|
198
|
+
"Private DNS for interface endpoints and use least privilege policies to control "
|
|
199
|
+
"access. ISO 27001 A.8.22 requires proper network segregation and access controls.",
|
|
200
|
+
remediation=(
|
|
201
|
+
f"Improve VPC Endpoint '{endpoint_id}' security:\n\n"
|
|
202
|
+
"1. Enable Private DNS (for interface endpoints):\n"
|
|
203
|
+
f"aws ec2 modify-vpc-endpoint \\\n"
|
|
204
|
+
f" --vpc-endpoint-id {endpoint_id} \\\n"
|
|
205
|
+
" --private-dns-enabled\n\n"
|
|
206
|
+
"2. Apply restrictive endpoint policy:\n"
|
|
207
|
+
"Create policy.json with least privilege access:\n"
|
|
208
|
+
"{\n"
|
|
209
|
+
' "Statement": [\n'
|
|
210
|
+
' {\n'
|
|
211
|
+
' "Effect": "Allow",\n'
|
|
212
|
+
' "Principal": {"AWS": "arn:aws:iam::ACCOUNT-ID:root"},\n'
|
|
213
|
+
' "Action": [\n'
|
|
214
|
+
' "s3:GetObject",\n'
|
|
215
|
+
' "s3:PutObject"\n'
|
|
216
|
+
' ],\n'
|
|
217
|
+
' "Resource": "arn:aws:s3:::my-bucket/*"\n'
|
|
218
|
+
' }\n'
|
|
219
|
+
' ]\n'
|
|
220
|
+
'}\n\n'
|
|
221
|
+
f"aws ec2 modify-vpc-endpoint \\\n"
|
|
222
|
+
f" --vpc-endpoint-id {endpoint_id} \\\n"
|
|
223
|
+
" --policy-document file://policy.json\n\n"
|
|
224
|
+
"Or use AWS Console:\n"
|
|
225
|
+
"1. Go to VPC console → Endpoints\n"
|
|
226
|
+
f"2. Select endpoint '{endpoint_id}'\n"
|
|
227
|
+
"3. Actions → Modify endpoint\n"
|
|
228
|
+
"4. For interface endpoints:\n"
|
|
229
|
+
" - Enable 'Enable Private DNS Name'\n"
|
|
230
|
+
"5. Edit policy:\n"
|
|
231
|
+
" - Click 'Custom' policy\n"
|
|
232
|
+
" - Define specific actions and resources\n"
|
|
233
|
+
" - Specify principal (avoid using '*')\n"
|
|
234
|
+
"6. Click 'Save'\n\n"
|
|
235
|
+
"Security best practices:\n"
|
|
236
|
+
"- Use interface endpoints for AWS services (more secure than internet)\n"
|
|
237
|
+
"- Enable Private DNS for seamless application integration\n"
|
|
238
|
+
"- Apply least privilege endpoint policies\n"
|
|
239
|
+
"- Use security groups to control access to interface endpoints\n"
|
|
240
|
+
"- Use VPC endpoint policies to restrict S3/DynamoDB access\n"
|
|
241
|
+
"- Monitor VPC endpoint usage with VPC Flow Logs\n"
|
|
242
|
+
"- Tag endpoints for easy management\n"
|
|
243
|
+
"- Regularly review and audit endpoint policies\n\n"
|
|
244
|
+
"Example restrictive policies:\n"
|
|
245
|
+
"S3 Gateway Endpoint (read-only for specific bucket):\n"
|
|
246
|
+
"{\n"
|
|
247
|
+
' "Statement": [{\n'
|
|
248
|
+
' "Effect": "Allow",\n'
|
|
249
|
+
' "Principal": "*",\n'
|
|
250
|
+
' "Action": "s3:GetObject",\n'
|
|
251
|
+
' "Resource": "arn:aws:s3:::my-bucket/*"\n'
|
|
252
|
+
' }]\n'
|
|
253
|
+
'}\n\n'
|
|
254
|
+
"DynamoDB Gateway Endpoint (specific table access):\n"
|
|
255
|
+
"{\n"
|
|
256
|
+
' "Statement": [{\n'
|
|
257
|
+
' "Effect": "Allow",\n'
|
|
258
|
+
' "Principal": "*",\n'
|
|
259
|
+
' "Action": ["dynamodb:GetItem", "dynamodb:PutItem"],\n'
|
|
260
|
+
' "Resource": "arn:aws:dynamodb:region:account:table/MyTable"\n'
|
|
261
|
+
' }]\n'
|
|
262
|
+
'}'
|
|
263
|
+
),
|
|
264
|
+
evidence=evidence
|
|
265
|
+
)
|
|
266
|
+
result.add_finding(finding)
|
|
267
|
+
|
|
268
|
+
self.logger.warning(
|
|
269
|
+
"vpc_endpoint_insecure",
|
|
270
|
+
endpoint_id=endpoint_id,
|
|
271
|
+
issues=issues
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Calculate compliance score
|
|
275
|
+
result.score = (secure_endpoint_count / len(vpc_endpoints)) * 100
|
|
276
|
+
|
|
277
|
+
# Determine pass/fail
|
|
278
|
+
result.passed = secure_endpoint_count == len(vpc_endpoints)
|
|
279
|
+
result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
|
|
280
|
+
|
|
281
|
+
# Add metadata
|
|
282
|
+
result.metadata = {
|
|
283
|
+
"total_vpc_endpoints": len(vpc_endpoints),
|
|
284
|
+
"secure_vpc_endpoints": secure_endpoint_count,
|
|
285
|
+
"insecure_vpc_endpoints": len(vpc_endpoints) - secure_endpoint_count,
|
|
286
|
+
"compliance_percentage": result.score,
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
self.logger.info(
|
|
290
|
+
"vpc_endpoints_security_test_completed",
|
|
291
|
+
total_vpc_endpoints=len(vpc_endpoints),
|
|
292
|
+
secure=secure_endpoint_count,
|
|
293
|
+
score=result.score,
|
|
294
|
+
passed=result.passed
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
except ClientError as e:
|
|
298
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
299
|
+
self.logger.error("vpc_endpoints_security_test_error", error_code=error_code, error=str(e))
|
|
300
|
+
result.status = TestStatus.ERROR
|
|
301
|
+
result.passed = False
|
|
302
|
+
result.score = 0.0
|
|
303
|
+
result.error_message = f"AWS API Error: {error_code} - {str(e)}"
|
|
304
|
+
|
|
305
|
+
except Exception as e:
|
|
306
|
+
self.logger.error("vpc_endpoints_security_test_error", error=str(e))
|
|
307
|
+
result.status = TestStatus.ERROR
|
|
308
|
+
result.passed = False
|
|
309
|
+
result.score = 0.0
|
|
310
|
+
result.error_message = str(e)
|
|
311
|
+
|
|
312
|
+
return result
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# ============================================================================
|
|
316
|
+
# CONVENIENCE FUNCTION
|
|
317
|
+
# ============================================================================
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def run_vpc_endpoints_security_test(connector: AWSConnector) -> TestResult:
|
|
321
|
+
"""Run VPC Endpoints security compliance test.
|
|
322
|
+
|
|
323
|
+
Convenience function for running the test.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
connector: AWS connector
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
TestResult
|
|
330
|
+
|
|
331
|
+
Example:
|
|
332
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
333
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
334
|
+
>>> connector.connect()
|
|
335
|
+
>>> result = run_vpc_endpoints_security_test(connector)
|
|
336
|
+
>>> print(f"Score: {result.score}%")
|
|
337
|
+
"""
|
|
338
|
+
test = VPCEndpointsSecurityTest(connector)
|
|
339
|
+
return test.execute()
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"""
|
|
2
|
+
VPN connection security compliance test.
|
|
3
|
+
|
|
4
|
+
Checks that all VPN connections use secure configurations.
|
|
5
|
+
|
|
6
|
+
ISO 27001 Control: A.8.22 - Network segregation
|
|
7
|
+
Requirement: VPN connections must use secure encryption and tunnel configurations
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
11
|
+
>>> from complio.tests_library.network.vpn_security import VPNSecurityTest
|
|
12
|
+
>>>
|
|
13
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
14
|
+
>>> connector.connect()
|
|
15
|
+
>>>
|
|
16
|
+
>>> test = VPNSecurityTest(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 VPNSecurityTest(ComplianceTest):
|
|
35
|
+
"""Test for VPN connection security compliance.
|
|
36
|
+
|
|
37
|
+
Verifies that all VPN connections use secure tunnel configurations
|
|
38
|
+
including proper encryption and integrity algorithms.
|
|
39
|
+
|
|
40
|
+
Compliance Requirements:
|
|
41
|
+
- All VPN tunnels must be UP (active)
|
|
42
|
+
- Use strong encryption (AES-256 recommended)
|
|
43
|
+
- Use strong integrity algorithms (SHA-256 or better)
|
|
44
|
+
- Use IKEv2 protocol when possible
|
|
45
|
+
|
|
46
|
+
Scoring:
|
|
47
|
+
- 100% if all VPN connections have secure configurations
|
|
48
|
+
- Proportional score based on compliant/total ratio
|
|
49
|
+
- 100% if no VPN connections exist
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
>>> test = VPNSecurityTest(connector)
|
|
53
|
+
>>> result = test.execute()
|
|
54
|
+
>>> for finding in result.findings:
|
|
55
|
+
... print(f"{finding.resource_id}: {finding.title}")
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, connector: AWSConnector) -> None:
|
|
59
|
+
"""Initialize VPN security test.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
connector: AWS connector instance
|
|
63
|
+
"""
|
|
64
|
+
super().__init__(
|
|
65
|
+
test_id="vpn_security",
|
|
66
|
+
test_name="VPN Connection Security Check",
|
|
67
|
+
description="Verify all VPN connections use secure tunnel configurations",
|
|
68
|
+
control_id="A.8.22",
|
|
69
|
+
connector=connector,
|
|
70
|
+
scope="regional",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def execute(self) -> TestResult:
|
|
74
|
+
"""Execute VPN connection security compliance test.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
TestResult with findings for insecure VPN configurations
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
>>> test = VPNSecurityTest(connector)
|
|
81
|
+
>>> result = test.execute()
|
|
82
|
+
>>> print(result.score)
|
|
83
|
+
100.0
|
|
84
|
+
"""
|
|
85
|
+
result = TestResult(
|
|
86
|
+
test_id=self.test_id,
|
|
87
|
+
test_name=self.test_name,
|
|
88
|
+
status=TestStatus.PASSED,
|
|
89
|
+
passed=True,
|
|
90
|
+
score=100.0,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
# Get EC2 client
|
|
95
|
+
ec2_client = self.connector.get_client("ec2")
|
|
96
|
+
|
|
97
|
+
# List all VPN connections
|
|
98
|
+
self.logger.info("listing_vpn_connections")
|
|
99
|
+
vpn_response = ec2_client.describe_vpn_connections()
|
|
100
|
+
vpn_connections = vpn_response.get("VpnConnections", [])
|
|
101
|
+
|
|
102
|
+
if not vpn_connections:
|
|
103
|
+
self.logger.info("no_vpn_connections_found")
|
|
104
|
+
result.metadata["message"] = "No VPN connections found in region"
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
self.logger.info("vpn_connections_found", count=len(vpn_connections))
|
|
108
|
+
|
|
109
|
+
# Check security for each VPN connection
|
|
110
|
+
secure_vpn_count = 0
|
|
111
|
+
|
|
112
|
+
for vpn_connection in vpn_connections:
|
|
113
|
+
vpn_id = vpn_connection["VpnConnectionId"]
|
|
114
|
+
vpn_state = vpn_connection.get("State", "")
|
|
115
|
+
result.resources_scanned += 1
|
|
116
|
+
|
|
117
|
+
# Skip deleted/deleting VPN connections
|
|
118
|
+
if vpn_state in ["deleted", "deleting"]:
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
# Check tunnel details
|
|
122
|
+
vgw_telemetry = vpn_connection.get("VgwTelemetry", [])
|
|
123
|
+
tunnel_options = vpn_connection.get("Options", {}).get("TunnelOptions", [])
|
|
124
|
+
|
|
125
|
+
# Initialize security check variables
|
|
126
|
+
all_tunnels_secure = True
|
|
127
|
+
security_issues = []
|
|
128
|
+
|
|
129
|
+
# Check each tunnel
|
|
130
|
+
for idx, telemetry in enumerate(vgw_telemetry):
|
|
131
|
+
tunnel_status = telemetry.get("Status", "DOWN")
|
|
132
|
+
|
|
133
|
+
# Check if tunnel is up
|
|
134
|
+
if tunnel_status != "UP":
|
|
135
|
+
all_tunnels_secure = False
|
|
136
|
+
security_issues.append(f"Tunnel {idx + 1} is {tunnel_status} (not UP)")
|
|
137
|
+
|
|
138
|
+
# Check tunnel options if available
|
|
139
|
+
for idx, tunnel_option in enumerate(tunnel_options):
|
|
140
|
+
phase1_encryption = tunnel_option.get("Phase1EncryptionAlgorithms", [])
|
|
141
|
+
phase1_integrity = tunnel_option.get("Phase1IntegrityAlgorithms", [])
|
|
142
|
+
phase2_encryption = tunnel_option.get("Phase2EncryptionAlgorithms", [])
|
|
143
|
+
phase2_integrity = tunnel_option.get("Phase2IntegrityAlgorithms", [])
|
|
144
|
+
ike_versions = tunnel_option.get("IKEVersions", [])
|
|
145
|
+
|
|
146
|
+
# Check for weak encryption (prefer AES-256)
|
|
147
|
+
weak_encryption_found = False
|
|
148
|
+
for enc_alg in phase1_encryption + phase2_encryption:
|
|
149
|
+
if isinstance(enc_alg, dict):
|
|
150
|
+
value = enc_alg.get("Value", "")
|
|
151
|
+
if value and "AES128" in value:
|
|
152
|
+
weak_encryption_found = True
|
|
153
|
+
|
|
154
|
+
if weak_encryption_found:
|
|
155
|
+
security_issues.append(f"Tunnel {idx + 1} uses AES-128 (AES-256 recommended)")
|
|
156
|
+
|
|
157
|
+
# Check for weak integrity (prefer SHA-256 or better)
|
|
158
|
+
weak_integrity_found = False
|
|
159
|
+
for int_alg in phase1_integrity + phase2_integrity:
|
|
160
|
+
if isinstance(int_alg, dict):
|
|
161
|
+
value = int_alg.get("Value", "")
|
|
162
|
+
if value and "SHA1" in value:
|
|
163
|
+
weak_integrity_found = True
|
|
164
|
+
|
|
165
|
+
if weak_integrity_found:
|
|
166
|
+
security_issues.append(f"Tunnel {idx + 1} uses SHA-1 (SHA-256+ recommended)")
|
|
167
|
+
|
|
168
|
+
# Check IKE version (IKEv2 preferred)
|
|
169
|
+
if ike_versions:
|
|
170
|
+
has_ikev2 = any(
|
|
171
|
+
v.get("Value") == "ikev2" if isinstance(v, dict) else False
|
|
172
|
+
for v in ike_versions
|
|
173
|
+
)
|
|
174
|
+
if not has_ikev2:
|
|
175
|
+
security_issues.append(f"Tunnel {idx + 1} does not use IKEv2")
|
|
176
|
+
|
|
177
|
+
# Create evidence
|
|
178
|
+
evidence = self.create_evidence(
|
|
179
|
+
resource_id=vpn_id,
|
|
180
|
+
resource_type="vpn_connection",
|
|
181
|
+
data={
|
|
182
|
+
"vpn_connection_id": vpn_id,
|
|
183
|
+
"state": vpn_state,
|
|
184
|
+
"type": vpn_connection.get("Type"),
|
|
185
|
+
"category": vpn_connection.get("Category"),
|
|
186
|
+
"tunnel_count": len(vgw_telemetry),
|
|
187
|
+
"tunnel_status": [t.get("Status") for t in vgw_telemetry],
|
|
188
|
+
"security_issues": security_issues,
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
result.add_evidence(evidence)
|
|
192
|
+
|
|
193
|
+
if all_tunnels_secure and not security_issues:
|
|
194
|
+
secure_vpn_count += 1
|
|
195
|
+
self.logger.debug(
|
|
196
|
+
"vpn_connection_secure",
|
|
197
|
+
vpn_id=vpn_id
|
|
198
|
+
)
|
|
199
|
+
else:
|
|
200
|
+
# Determine severity based on issues
|
|
201
|
+
severity = Severity.HIGH if "DOWN" in str(security_issues) else Severity.MEDIUM
|
|
202
|
+
|
|
203
|
+
# Create finding for insecure VPN connection
|
|
204
|
+
finding = self.create_finding(
|
|
205
|
+
resource_id=vpn_id,
|
|
206
|
+
resource_type="vpn_connection",
|
|
207
|
+
severity=severity,
|
|
208
|
+
title="VPN connection has security issues",
|
|
209
|
+
description=f"VPN connection '{vpn_id}' has security configuration issues: "
|
|
210
|
+
f"{', '.join(security_issues)}. "
|
|
211
|
+
"VPN connections should use strong encryption (AES-256), strong "
|
|
212
|
+
"integrity algorithms (SHA-256 or better), IKEv2 protocol, and "
|
|
213
|
+
"all tunnels should be in UP status for high availability. "
|
|
214
|
+
"ISO 27001 A.8.22 requires secure network segregation and "
|
|
215
|
+
"encryption of data in transit.",
|
|
216
|
+
remediation=(
|
|
217
|
+
f"Improve VPN connection '{vpn_id}' security:\n\n"
|
|
218
|
+
"1. Use strong encryption and integrity algorithms:\n"
|
|
219
|
+
" When creating/modifying VPN connection, specify tunnel options:\n"
|
|
220
|
+
" - Phase1EncryptionAlgorithms: AES256, AES256-GCM-16\n"
|
|
221
|
+
" - Phase1IntegrityAlgorithms: SHA2-256, SHA2-384, SHA2-512\n"
|
|
222
|
+
" - Phase2EncryptionAlgorithms: AES256, AES256-GCM-16\n"
|
|
223
|
+
" - Phase2IntegrityAlgorithms: SHA2-256, SHA2-384, SHA2-512\n"
|
|
224
|
+
" - IKEVersions: ikev2\n\n"
|
|
225
|
+
"2. Ensure both tunnels are UP:\n"
|
|
226
|
+
" - Check customer gateway configuration\n"
|
|
227
|
+
" - Verify routing and firewall rules\n"
|
|
228
|
+
" - Check VPN connection logs in CloudWatch\n\n"
|
|
229
|
+
"3. Create new VPN connection with secure settings:\n"
|
|
230
|
+
" aws ec2 create-vpn-connection \\\n"
|
|
231
|
+
" --type ipsec.1 \\\n"
|
|
232
|
+
" --customer-gateway-id <cgw-id> \\\n"
|
|
233
|
+
" --vpn-gateway-id <vgw-id> \\\n"
|
|
234
|
+
" --options TunnelOptions='[{\n"
|
|
235
|
+
' "Phase1EncryptionAlgorithms":[{"Value":"AES256"}],\n'
|
|
236
|
+
' "Phase1IntegrityAlgorithms":[{"Value":"SHA2-256"}],\n'
|
|
237
|
+
' "Phase2EncryptionAlgorithms":[{"Value":"AES256"}],\n'
|
|
238
|
+
' "Phase2IntegrityAlgorithms":[{"Value":"SHA2-256"}],\n'
|
|
239
|
+
' "IKEVersions":[{"Value":"ikev2"}]\n'
|
|
240
|
+
" }]'\n\n"
|
|
241
|
+
"Or use AWS Console:\n"
|
|
242
|
+
"1. Go to VPC console → Site-to-Site VPN Connections\n"
|
|
243
|
+
"2. Create new VPN connection or modify existing\n"
|
|
244
|
+
"3. Under 'Tunnel Options', configure:\n"
|
|
245
|
+
" - Phase 1 Encryption: AES-256\n"
|
|
246
|
+
" - Phase 1 Integrity: SHA2-256 or better\n"
|
|
247
|
+
" - Phase 2 Encryption: AES-256\n"
|
|
248
|
+
" - Phase 2 Integrity: SHA2-256 or better\n"
|
|
249
|
+
" - IKE Version: IKEv2\n"
|
|
250
|
+
"4. Update customer gateway configuration accordingly\n\n"
|
|
251
|
+
"Security best practices:\n"
|
|
252
|
+
"- Enable VPN CloudWatch logs for monitoring\n"
|
|
253
|
+
"- Use DPD (Dead Peer Detection) timeout\n"
|
|
254
|
+
"- Configure tunnel inside CIDR appropriately\n"
|
|
255
|
+
"- Regularly rotate pre-shared keys\n"
|
|
256
|
+
"- Monitor tunnel status with CloudWatch alarms"
|
|
257
|
+
),
|
|
258
|
+
evidence=evidence
|
|
259
|
+
)
|
|
260
|
+
result.add_finding(finding)
|
|
261
|
+
|
|
262
|
+
self.logger.warning(
|
|
263
|
+
"vpn_connection_insecure",
|
|
264
|
+
vpn_id=vpn_id,
|
|
265
|
+
issues=security_issues
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Calculate compliance score
|
|
269
|
+
result.score = (secure_vpn_count / len(vpn_connections)) * 100
|
|
270
|
+
|
|
271
|
+
# Determine pass/fail
|
|
272
|
+
result.passed = secure_vpn_count == len(vpn_connections)
|
|
273
|
+
result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
|
|
274
|
+
|
|
275
|
+
# Add metadata
|
|
276
|
+
result.metadata = {
|
|
277
|
+
"total_vpn_connections": len(vpn_connections),
|
|
278
|
+
"secure_vpn_connections": secure_vpn_count,
|
|
279
|
+
"insecure_vpn_connections": len(vpn_connections) - secure_vpn_count,
|
|
280
|
+
"compliance_percentage": result.score,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
self.logger.info(
|
|
284
|
+
"vpn_security_test_completed",
|
|
285
|
+
total_vpn_connections=len(vpn_connections),
|
|
286
|
+
secure=secure_vpn_count,
|
|
287
|
+
score=result.score,
|
|
288
|
+
passed=result.passed
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
except ClientError as e:
|
|
292
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
293
|
+
self.logger.error("vpn_security_test_error", error_code=error_code, error=str(e))
|
|
294
|
+
result.status = TestStatus.ERROR
|
|
295
|
+
result.passed = False
|
|
296
|
+
result.score = 0.0
|
|
297
|
+
result.error_message = f"AWS API Error: {error_code} - {str(e)}"
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
self.logger.error("vpn_security_test_error", error=str(e))
|
|
301
|
+
result.status = TestStatus.ERROR
|
|
302
|
+
result.passed = False
|
|
303
|
+
result.score = 0.0
|
|
304
|
+
result.error_message = str(e)
|
|
305
|
+
|
|
306
|
+
return result
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# ============================================================================
|
|
310
|
+
# CONVENIENCE FUNCTION
|
|
311
|
+
# ============================================================================
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def run_vpn_security_test(connector: AWSConnector) -> TestResult:
|
|
315
|
+
"""Run VPN connection security compliance test.
|
|
316
|
+
|
|
317
|
+
Convenience function for running the test.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
connector: AWS connector
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
TestResult
|
|
324
|
+
|
|
325
|
+
Example:
|
|
326
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
327
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
328
|
+
>>> connector.connect()
|
|
329
|
+
>>> result = run_vpn_security_test(connector)
|
|
330
|
+
>>> print(f"Score: {result.score}%")
|
|
331
|
+
"""
|
|
332
|
+
test = VPNSecurityTest(connector)
|
|
333
|
+
return test.execute()
|