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,416 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Security Hub enabled compliance test.
|
|
3
|
+
|
|
4
|
+
Checks that AWS Security Hub is enabled for centralized security monitoring.
|
|
5
|
+
|
|
6
|
+
ISO 27001 Control: A.8.16 - Monitoring activities
|
|
7
|
+
Requirement: Security Hub should be enabled for aggregated security findings
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
11
|
+
>>> from complio.tests_library.logging.security_hub_enabled import SecurityHubEnabledTest
|
|
12
|
+
>>>
|
|
13
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
14
|
+
>>> connector.connect()
|
|
15
|
+
>>>
|
|
16
|
+
>>> test = SecurityHubEnabledTest(connector)
|
|
17
|
+
>>> result = test.run()
|
|
18
|
+
>>> print(f"Passed: {result.passed}, Score: {result.score}")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import Any, Dict, List
|
|
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 SecurityHubEnabledTest(ComplianceTest):
|
|
35
|
+
"""Test for AWS Security Hub enabled compliance.
|
|
36
|
+
|
|
37
|
+
Verifies that AWS Security Hub is properly enabled:
|
|
38
|
+
- Security Hub should be enabled
|
|
39
|
+
- Security standards should be enabled (CIS, AWS Foundational Security)
|
|
40
|
+
- Product integrations should be enabled (GuardDuty, Config, etc.)
|
|
41
|
+
- Findings should be aggregated and actionable
|
|
42
|
+
|
|
43
|
+
Compliance Requirements:
|
|
44
|
+
- Security Hub enabled in region
|
|
45
|
+
- At least one security standard enabled
|
|
46
|
+
- Product integrations enabled
|
|
47
|
+
- Findings aggregation configured
|
|
48
|
+
|
|
49
|
+
Scoring:
|
|
50
|
+
- 100% if Security Hub fully enabled with standards
|
|
51
|
+
- Partial score for basic enablement
|
|
52
|
+
- 0% if Security Hub not enabled
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
>>> test = SecurityHubEnabledTest(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 Security Hub enabled test.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
connector: AWS connector instance
|
|
66
|
+
"""
|
|
67
|
+
super().__init__(
|
|
68
|
+
test_id="security_hub_enabled",
|
|
69
|
+
test_name="AWS Security Hub Enabled Check",
|
|
70
|
+
description="Verify AWS Security Hub is enabled for centralized security monitoring",
|
|
71
|
+
control_id="A.8.16",
|
|
72
|
+
connector=connector,
|
|
73
|
+
scope="regional",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def execute(self) -> TestResult:
|
|
77
|
+
"""Execute Security Hub enabled compliance test.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
TestResult with findings if Security Hub is not properly enabled
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> test = SecurityHubEnabledTest(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 Security Hub client
|
|
98
|
+
securityhub_client = self.connector.get_client("securityhub")
|
|
99
|
+
|
|
100
|
+
# Check if Security Hub is enabled
|
|
101
|
+
self.logger.info("checking_security_hub_status")
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
hub_response = securityhub_client.describe_hub()
|
|
105
|
+
hub_arn = hub_response.get("HubArn", "")
|
|
106
|
+
subscribed_at = hub_response.get("SubscribedAt", "")
|
|
107
|
+
auto_enable_controls = hub_response.get("AutoEnableControls", False)
|
|
108
|
+
|
|
109
|
+
security_hub_enabled = True
|
|
110
|
+
result.resources_scanned += 1
|
|
111
|
+
|
|
112
|
+
except ClientError as e:
|
|
113
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
114
|
+
if error_code == "InvalidAccessException":
|
|
115
|
+
security_hub_enabled = False
|
|
116
|
+
self.logger.warning("security_hub_not_enabled")
|
|
117
|
+
else:
|
|
118
|
+
raise
|
|
119
|
+
|
|
120
|
+
if not security_hub_enabled:
|
|
121
|
+
# Create finding for Security Hub not enabled
|
|
122
|
+
finding = self.create_finding(
|
|
123
|
+
resource_id="security-hub",
|
|
124
|
+
resource_type="security_hub",
|
|
125
|
+
severity=Severity.HIGH,
|
|
126
|
+
title="AWS Security Hub not enabled",
|
|
127
|
+
description="AWS Security Hub is not enabled in this region. Security Hub provides a "
|
|
128
|
+
"comprehensive view of security alerts and compliance status across AWS accounts. "
|
|
129
|
+
"It aggregates findings from GuardDuty, Inspector, Macie, IAM Access Analyzer, "
|
|
130
|
+
"AWS Firewall Manager, and third-party products. Without Security Hub, security "
|
|
131
|
+
"findings are dispersed and difficult to manage. ISO 27001 A.8.16 requires "
|
|
132
|
+
"centralized monitoring of security events.",
|
|
133
|
+
remediation=(
|
|
134
|
+
"Enable AWS Security Hub in this region:\n\n"
|
|
135
|
+
"1. Enable Security Hub:\n"
|
|
136
|
+
"aws securityhub enable-security-hub\n\n"
|
|
137
|
+
"2. Enable security standards:\n"
|
|
138
|
+
"# AWS Foundational Security Best Practices\n"
|
|
139
|
+
"aws securityhub batch-enable-standards \\\n"
|
|
140
|
+
" --standards-subscription-requests \\\n"
|
|
141
|
+
" StandardsArn=arn:aws:securityhub:REGION::standards/aws-foundational-security-best-practices/v/1.0.0\n\n"
|
|
142
|
+
"# CIS AWS Foundations Benchmark\n"
|
|
143
|
+
"aws securityhub batch-enable-standards \\\n"
|
|
144
|
+
" --standards-subscription-requests \\\n"
|
|
145
|
+
" StandardsArn=arn:aws:securityhub:REGION::standards/cis-aws-foundations-benchmark/v/1.2.0\n\n"
|
|
146
|
+
"# PCI-DSS (if applicable)\n"
|
|
147
|
+
"aws securityhub batch-enable-standards \\\n"
|
|
148
|
+
" --standards-subscription-requests \\\n"
|
|
149
|
+
" StandardsArn=arn:aws:securityhub:REGION::standards/pci-dss/v/3.2.1\n\n"
|
|
150
|
+
"3. Enable product integrations:\n"
|
|
151
|
+
"# GuardDuty\n"
|
|
152
|
+
"aws securityhub enable-import-findings-for-product \\\n"
|
|
153
|
+
" --product-arn arn:aws:securityhub:REGION::product/aws/guardduty\n\n"
|
|
154
|
+
"# AWS Config\n"
|
|
155
|
+
"aws securityhub enable-import-findings-for-product \\\n"
|
|
156
|
+
" --product-arn arn:aws:securityhub:REGION::product/aws/config\n\n"
|
|
157
|
+
"# IAM Access Analyzer\n"
|
|
158
|
+
"aws securityhub enable-import-findings-for-product \\\n"
|
|
159
|
+
" --product-arn arn:aws:securityhub:REGION::product/aws/access-analyzer\n\n"
|
|
160
|
+
"4. Configure findings aggregation (for multi-region):\n"
|
|
161
|
+
"aws securityhub create-finding-aggregator \\\n"
|
|
162
|
+
" --region-linking-mode ALL_REGIONS\n\n"
|
|
163
|
+
"Or use AWS Console:\n"
|
|
164
|
+
"1. Go to AWS Security Hub console\n"
|
|
165
|
+
"2. Click 'Enable Security Hub'\n"
|
|
166
|
+
"3. Select security standards to enable:\n"
|
|
167
|
+
" - AWS Foundational Security Best Practices (recommended)\n"
|
|
168
|
+
" - CIS AWS Foundations Benchmark\n"
|
|
169
|
+
" - PCI-DSS (if applicable)\n"
|
|
170
|
+
"4. Enable product integrations:\n"
|
|
171
|
+
" - Go to Integrations\n"
|
|
172
|
+
" - Enable AWS services (GuardDuty, Config, IAM Access Analyzer)\n"
|
|
173
|
+
" - Enable third-party products if needed\n"
|
|
174
|
+
"5. Configure settings:\n"
|
|
175
|
+
" - Enable auto-enable for new controls\n"
|
|
176
|
+
" - Set up finding aggregation for multi-region\n\n"
|
|
177
|
+
"Best practices:\n"
|
|
178
|
+
"- Enable Security Hub in all regions\n"
|
|
179
|
+
"- Use AWS Organizations for multi-account setup\n"
|
|
180
|
+
"- Enable delegated administrator account\n"
|
|
181
|
+
"- Configure automated remediation with EventBridge + Lambda\n"
|
|
182
|
+
"- Set up custom insights for your environment\n"
|
|
183
|
+
"- Integrate with SIEM tools (Splunk, etc.)\n"
|
|
184
|
+
"- Review findings daily/weekly\n"
|
|
185
|
+
"- Suppress accepted risks with proper documentation\n"
|
|
186
|
+
"- Use Security Hub automation rules\n"
|
|
187
|
+
"- Export findings to S3 for long-term analysis\n\n"
|
|
188
|
+
"Security standards comparison:\n"
|
|
189
|
+
"AWS Foundational Security Best Practices:\n"
|
|
190
|
+
"- 200+ automated checks\n"
|
|
191
|
+
"- AWS best practices\n"
|
|
192
|
+
"- Continuously updated by AWS\n"
|
|
193
|
+
"- Recommended for all accounts\n\n"
|
|
194
|
+
"CIS AWS Foundations Benchmark:\n"
|
|
195
|
+
"- Industry standard\n"
|
|
196
|
+
"- Required for compliance certifications\n"
|
|
197
|
+
"- Periodic updates\n"
|
|
198
|
+
"- Focus on foundational security\n\n"
|
|
199
|
+
"PCI-DSS:\n"
|
|
200
|
+
"- Required for payment card processing\n"
|
|
201
|
+
"- Specific compliance requirements\n"
|
|
202
|
+
"- More restrictive controls\n\n"
|
|
203
|
+
"Key integrations:\n"
|
|
204
|
+
"- GuardDuty: Threat detection\n"
|
|
205
|
+
"- Inspector: Vulnerability scanning\n"
|
|
206
|
+
"- Macie: Data security\n"
|
|
207
|
+
"- Config: Configuration compliance\n"
|
|
208
|
+
"- IAM Access Analyzer: Permissions analysis\n"
|
|
209
|
+
"- Firewall Manager: Network security\n"
|
|
210
|
+
"- Systems Manager: Patch compliance\n\n"
|
|
211
|
+
"Cost considerations:\n"
|
|
212
|
+
"- Free 30-day trial\n"
|
|
213
|
+
"- Pricing:\n"
|
|
214
|
+
" • Security checks: $0.0010 per check\n"
|
|
215
|
+
" • Finding ingestion: $0.00003 per finding\n"
|
|
216
|
+
"- Typical cost: $20-50 per account per month\n"
|
|
217
|
+
"- Consolidated billing with Organizations"
|
|
218
|
+
),
|
|
219
|
+
evidence=None
|
|
220
|
+
)
|
|
221
|
+
result.add_finding(finding)
|
|
222
|
+
result.score = 0.0
|
|
223
|
+
result.passed = False
|
|
224
|
+
result.status = TestStatus.FAILED
|
|
225
|
+
result.metadata = {
|
|
226
|
+
"security_hub_enabled": False,
|
|
227
|
+
"message": "Security Hub not enabled in region"
|
|
228
|
+
}
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
# Security Hub is enabled, check configuration
|
|
232
|
+
issues = []
|
|
233
|
+
severity = Severity.MEDIUM
|
|
234
|
+
score_points = 0 # Out of 100
|
|
235
|
+
|
|
236
|
+
# Hub is enabled (40 points)
|
|
237
|
+
score_points += 40
|
|
238
|
+
|
|
239
|
+
# Check enabled standards (30 points)
|
|
240
|
+
self.logger.info("checking_enabled_standards")
|
|
241
|
+
standards_response = securityhub_client.get_enabled_standards()
|
|
242
|
+
enabled_standards = standards_response.get("StandardsSubscriptions", [])
|
|
243
|
+
|
|
244
|
+
active_standards = [
|
|
245
|
+
s for s in enabled_standards
|
|
246
|
+
if s.get("StandardsStatus") == "READY"
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
if len(active_standards) > 0:
|
|
250
|
+
score_points += 30
|
|
251
|
+
else:
|
|
252
|
+
issues.append("No security standards enabled")
|
|
253
|
+
severity = Severity.HIGH
|
|
254
|
+
|
|
255
|
+
# Check product integrations (20 points)
|
|
256
|
+
self.logger.info("checking_product_integrations")
|
|
257
|
+
try:
|
|
258
|
+
integrations_response = securityhub_client.list_enabled_products_for_import()
|
|
259
|
+
enabled_products = integrations_response.get("ProductSubscriptions", [])
|
|
260
|
+
|
|
261
|
+
if len(enabled_products) > 0:
|
|
262
|
+
score_points += 20
|
|
263
|
+
else:
|
|
264
|
+
issues.append("No product integrations enabled")
|
|
265
|
+
except ClientError:
|
|
266
|
+
issues.append("Could not check product integrations")
|
|
267
|
+
|
|
268
|
+
# Check auto-enable controls (10 points - bonus)
|
|
269
|
+
if auto_enable_controls:
|
|
270
|
+
score_points += 10
|
|
271
|
+
|
|
272
|
+
# Create evidence
|
|
273
|
+
evidence = self.create_evidence(
|
|
274
|
+
resource_id=hub_arn,
|
|
275
|
+
resource_type="security_hub",
|
|
276
|
+
data={
|
|
277
|
+
"hub_arn": hub_arn,
|
|
278
|
+
"subscribed_at": subscribed_at,
|
|
279
|
+
"auto_enable_controls": auto_enable_controls,
|
|
280
|
+
"enabled_standards_count": len(active_standards),
|
|
281
|
+
"enabled_products_count": len(enabled_products) if 'enabled_products' in locals() else 0,
|
|
282
|
+
"has_issues": len(issues) > 0,
|
|
283
|
+
"issues": issues,
|
|
284
|
+
"security_score": score_points,
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
result.add_evidence(evidence)
|
|
288
|
+
|
|
289
|
+
if len(issues) > 0:
|
|
290
|
+
# Create finding for Security Hub configuration issues
|
|
291
|
+
finding = self.create_finding(
|
|
292
|
+
resource_id=hub_arn,
|
|
293
|
+
resource_type="security_hub",
|
|
294
|
+
severity=severity,
|
|
295
|
+
title="AWS Security Hub has configuration issues",
|
|
296
|
+
description=f"AWS Security Hub is enabled but has configuration issues: {'; '.join(issues)}. "
|
|
297
|
+
"Security Hub should have security standards enabled and product integrations "
|
|
298
|
+
"configured to provide comprehensive security monitoring. ISO 27001 A.8.16 requires "
|
|
299
|
+
"effective monitoring and correlation of security events.",
|
|
300
|
+
remediation=(
|
|
301
|
+
"Improve Security Hub configuration:\n\n"
|
|
302
|
+
"1. Enable security standards:\n"
|
|
303
|
+
"aws securityhub batch-enable-standards \\\n"
|
|
304
|
+
" --standards-subscription-requests \\\n"
|
|
305
|
+
" StandardsArn=arn:aws:securityhub:REGION::standards/aws-foundational-security-best-practices/v/1.0.0 \\\n"
|
|
306
|
+
" StandardsArn=arn:aws:securityhub:REGION::standards/cis-aws-foundations-benchmark/v/1.2.0\n\n"
|
|
307
|
+
"2. Enable product integrations:\n"
|
|
308
|
+
"aws securityhub enable-import-findings-for-product \\\n"
|
|
309
|
+
" --product-arn arn:aws:securityhub:REGION::product/aws/guardduty\n\n"
|
|
310
|
+
"aws securityhub enable-import-findings-for-product \\\n"
|
|
311
|
+
" --product-arn arn:aws:securityhub:REGION::product/aws/config\n\n"
|
|
312
|
+
"3. Enable auto-enable controls:\n"
|
|
313
|
+
"aws securityhub update-security-hub-configuration \\\n"
|
|
314
|
+
" --auto-enable-controls\n\n"
|
|
315
|
+
"Or use AWS Console:\n"
|
|
316
|
+
"1. Go to Security Hub console\n"
|
|
317
|
+
"2. Click 'Security standards'\n"
|
|
318
|
+
"3. Enable recommended standards\n"
|
|
319
|
+
"4. Go to 'Integrations'\n"
|
|
320
|
+
"5. Enable AWS service integrations\n"
|
|
321
|
+
"6. Go to 'Settings'\n"
|
|
322
|
+
"7. Enable 'Auto-enable new controls'\n\n"
|
|
323
|
+
"Monitor and respond:\n"
|
|
324
|
+
"- Review critical/high findings daily\n"
|
|
325
|
+
"- Create EventBridge rules for automated responses\n"
|
|
326
|
+
"- Use custom insights for trending\n"
|
|
327
|
+
"- Suppress accepted risks with notes\n"
|
|
328
|
+
"- Track compliance scores over time"
|
|
329
|
+
),
|
|
330
|
+
evidence=evidence
|
|
331
|
+
)
|
|
332
|
+
result.add_finding(finding)
|
|
333
|
+
|
|
334
|
+
result.score = score_points
|
|
335
|
+
result.passed = score_points >= 80 # Require 80% for pass
|
|
336
|
+
result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
|
|
337
|
+
|
|
338
|
+
self.logger.warning(
|
|
339
|
+
"security_hub_has_issues",
|
|
340
|
+
hub_arn=hub_arn,
|
|
341
|
+
issues=issues,
|
|
342
|
+
score=score_points
|
|
343
|
+
)
|
|
344
|
+
else:
|
|
345
|
+
# Security Hub is properly configured
|
|
346
|
+
result.score = 100.0
|
|
347
|
+
result.passed = True
|
|
348
|
+
result.status = TestStatus.PASSED
|
|
349
|
+
|
|
350
|
+
self.logger.info(
|
|
351
|
+
"security_hub_properly_configured",
|
|
352
|
+
hub_arn=hub_arn
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Add metadata
|
|
356
|
+
result.metadata = {
|
|
357
|
+
"security_hub_enabled": True,
|
|
358
|
+
"hub_arn": hub_arn,
|
|
359
|
+
"enabled_standards": len(active_standards),
|
|
360
|
+
"enabled_products": len(enabled_products) if 'enabled_products' in locals() else 0,
|
|
361
|
+
"auto_enable_controls": auto_enable_controls,
|
|
362
|
+
"security_score": score_points,
|
|
363
|
+
"compliance_percentage": result.score,
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
self.logger.info(
|
|
367
|
+
"security_hub_enabled_test_completed",
|
|
368
|
+
hub_arn=hub_arn,
|
|
369
|
+
enabled_standards=len(active_standards),
|
|
370
|
+
score=result.score,
|
|
371
|
+
passed=result.passed
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
except ClientError as e:
|
|
375
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
376
|
+
self.logger.error("security_hub_enabled_test_error", error_code=error_code, error=str(e))
|
|
377
|
+
result.status = TestStatus.ERROR
|
|
378
|
+
result.passed = False
|
|
379
|
+
result.score = 0.0
|
|
380
|
+
result.error_message = f"AWS API Error: {error_code} - {str(e)}"
|
|
381
|
+
|
|
382
|
+
except Exception as e:
|
|
383
|
+
self.logger.error("security_hub_enabled_test_error", error=str(e))
|
|
384
|
+
result.status = TestStatus.ERROR
|
|
385
|
+
result.passed = False
|
|
386
|
+
result.score = 0.0
|
|
387
|
+
result.error_message = str(e)
|
|
388
|
+
|
|
389
|
+
return result
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
# ============================================================================
|
|
393
|
+
# CONVENIENCE FUNCTION
|
|
394
|
+
# ============================================================================
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def run_security_hub_enabled_test(connector: AWSConnector) -> TestResult:
|
|
398
|
+
"""Run AWS Security Hub enabled compliance test.
|
|
399
|
+
|
|
400
|
+
Convenience function for running the test.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
connector: AWS connector
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
TestResult
|
|
407
|
+
|
|
408
|
+
Example:
|
|
409
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
410
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
411
|
+
>>> connector.connect()
|
|
412
|
+
>>> result = run_security_hub_enabled_test(connector)
|
|
413
|
+
>>> print(f"Score: {result.score}%")
|
|
414
|
+
"""
|
|
415
|
+
test = SecurityHubEnabledTest(connector)
|
|
416
|
+
return test.execute()
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SNS topic encryption compliance test.
|
|
3
|
+
|
|
4
|
+
Checks that all SNS topics use encryption at rest.
|
|
5
|
+
|
|
6
|
+
ISO 27001 Control: A.8.24 - Use of cryptography
|
|
7
|
+
Requirement: SNS topics must be encrypted with KMS keys
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
11
|
+
>>> from complio.tests_library.logging.sns_encryption import SNSEncryptionTest
|
|
12
|
+
>>>
|
|
13
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
14
|
+
>>> connector.connect()
|
|
15
|
+
>>>
|
|
16
|
+
>>> test = SNSEncryptionTest(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 SNSEncryptionTest(ComplianceTest):
|
|
35
|
+
"""Test for SNS topic encryption compliance.
|
|
36
|
+
|
|
37
|
+
Verifies that all SNS topics use server-side encryption with KMS keys
|
|
38
|
+
to protect message data at rest.
|
|
39
|
+
|
|
40
|
+
Compliance Requirements:
|
|
41
|
+
- All SNS topics must have KmsMasterKeyId configured
|
|
42
|
+
- Encryption protects sensitive notification data
|
|
43
|
+
- Customer-managed KMS keys recommended for better control
|
|
44
|
+
|
|
45
|
+
Scoring:
|
|
46
|
+
- 100% if all topics are encrypted
|
|
47
|
+
- Proportional score based on compliant/total ratio
|
|
48
|
+
- 100% if no topics exist
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> test = SNSEncryptionTest(connector)
|
|
52
|
+
>>> result = test.execute()
|
|
53
|
+
>>> for finding in result.findings:
|
|
54
|
+
... print(f"{finding.resource_id}: {finding.title}")
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, connector: AWSConnector) -> None:
|
|
58
|
+
"""Initialize SNS encryption test.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
connector: AWS connector instance
|
|
62
|
+
"""
|
|
63
|
+
super().__init__(
|
|
64
|
+
test_id="sns_encryption",
|
|
65
|
+
test_name="SNS Topic Encryption Check",
|
|
66
|
+
description="Verify all SNS topics use encryption at rest with KMS",
|
|
67
|
+
control_id="A.8.24",
|
|
68
|
+
connector=connector,
|
|
69
|
+
scope="regional",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def execute(self) -> TestResult:
|
|
73
|
+
"""Execute SNS topic encryption compliance test.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
TestResult with findings for unencrypted topics
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
>>> test = SNSEncryptionTest(connector)
|
|
80
|
+
>>> result = test.execute()
|
|
81
|
+
>>> print(result.score)
|
|
82
|
+
100.0
|
|
83
|
+
"""
|
|
84
|
+
result = TestResult(
|
|
85
|
+
test_id=self.test_id,
|
|
86
|
+
test_name=self.test_name,
|
|
87
|
+
status=TestStatus.PASSED,
|
|
88
|
+
passed=True,
|
|
89
|
+
score=100.0,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
# Get SNS client
|
|
94
|
+
sns_client = self.connector.get_client("sns")
|
|
95
|
+
|
|
96
|
+
# List all SNS topics
|
|
97
|
+
self.logger.info("listing_sns_topics")
|
|
98
|
+
topics = []
|
|
99
|
+
|
|
100
|
+
paginator = sns_client.get_paginator("list_topics")
|
|
101
|
+
for page in paginator.paginate():
|
|
102
|
+
topics.extend(page.get("Topics", []))
|
|
103
|
+
|
|
104
|
+
if not topics:
|
|
105
|
+
self.logger.info("no_sns_topics_found")
|
|
106
|
+
result.metadata["message"] = "No SNS topics found in region"
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
self.logger.info("sns_topics_found", count=len(topics))
|
|
110
|
+
|
|
111
|
+
# Check encryption for each topic
|
|
112
|
+
encrypted_count = 0
|
|
113
|
+
|
|
114
|
+
for topic in topics:
|
|
115
|
+
topic_arn = topic["TopicArn"]
|
|
116
|
+
result.resources_scanned += 1
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
# Get topic attributes to check encryption
|
|
120
|
+
attributes_response = sns_client.get_topic_attributes(TopicArn=topic_arn)
|
|
121
|
+
attributes = attributes_response.get("Attributes", {})
|
|
122
|
+
|
|
123
|
+
# Check if KMS master key is configured
|
|
124
|
+
kms_master_key_id = attributes.get("KmsMasterKeyId")
|
|
125
|
+
is_encrypted = kms_master_key_id is not None and kms_master_key_id != ""
|
|
126
|
+
|
|
127
|
+
# Create evidence
|
|
128
|
+
evidence = self.create_evidence(
|
|
129
|
+
resource_id=topic_arn,
|
|
130
|
+
resource_type="sns_topic",
|
|
131
|
+
data={
|
|
132
|
+
"topic_arn": topic_arn,
|
|
133
|
+
"kms_master_key_id": kms_master_key_id,
|
|
134
|
+
"is_encrypted": is_encrypted,
|
|
135
|
+
"display_name": attributes.get("DisplayName"),
|
|
136
|
+
"subscriptions_confirmed": attributes.get("SubscriptionsConfirmed", "0"),
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
result.add_evidence(evidence)
|
|
140
|
+
|
|
141
|
+
if is_encrypted:
|
|
142
|
+
encrypted_count += 1
|
|
143
|
+
self.logger.debug(
|
|
144
|
+
"sns_topic_encrypted",
|
|
145
|
+
topic_arn=topic_arn,
|
|
146
|
+
kms_key_id=kms_master_key_id
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
# Create finding for unencrypted topic
|
|
150
|
+
finding = self.create_finding(
|
|
151
|
+
resource_id=topic_arn,
|
|
152
|
+
resource_type="sns_topic",
|
|
153
|
+
severity=Severity.HIGH,
|
|
154
|
+
title="SNS topic encryption not enabled",
|
|
155
|
+
description=f"SNS topic '{topic_arn}' does not have encryption enabled. "
|
|
156
|
+
"Without encryption, messages published to this topic are stored "
|
|
157
|
+
"unencrypted at rest, potentially exposing sensitive notification "
|
|
158
|
+
"data. SNS encryption with AWS KMS protects message data and metadata "
|
|
159
|
+
"at rest. ISO 27001 A.8.24 requires cryptographic controls for "
|
|
160
|
+
"protecting sensitive information.",
|
|
161
|
+
remediation=(
|
|
162
|
+
f"Enable encryption for SNS topic:\n\n"
|
|
163
|
+
"Using AWS CLI:\n"
|
|
164
|
+
f"aws sns set-topic-attributes \\\n"
|
|
165
|
+
f" --topic-arn {topic_arn} \\\n"
|
|
166
|
+
" --attribute-name KmsMasterKeyId \\\n"
|
|
167
|
+
" --attribute-value alias/aws/sns\n\n"
|
|
168
|
+
"Or use a customer-managed KMS key (recommended):\n"
|
|
169
|
+
f"aws sns set-topic-attributes \\\n"
|
|
170
|
+
f" --topic-arn {topic_arn} \\\n"
|
|
171
|
+
" --attribute-name KmsMasterKeyId \\\n"
|
|
172
|
+
" --attribute-value arn:aws:kms:REGION:ACCOUNT:key/KEY-ID\n\n"
|
|
173
|
+
"Or use AWS Console:\n"
|
|
174
|
+
"1. Go to AWS SNS console\n"
|
|
175
|
+
"2. Select the topic\n"
|
|
176
|
+
"3. Click 'Edit'\n"
|
|
177
|
+
"4. Expand 'Encryption'\n"
|
|
178
|
+
"5. Select 'Enable encryption'\n"
|
|
179
|
+
"6. Choose a KMS key (AWS managed or customer managed)\n"
|
|
180
|
+
"7. Click 'Save changes'\n\n"
|
|
181
|
+
"Important notes:\n"
|
|
182
|
+
"- Update KMS key policy to allow SNS to use it\n"
|
|
183
|
+
"- Publishers need kms:GenerateDataKey permission\n"
|
|
184
|
+
"- Subscribers need kms:Decrypt permission\n"
|
|
185
|
+
"- Consider using customer-managed keys for better audit trail"
|
|
186
|
+
),
|
|
187
|
+
evidence=evidence
|
|
188
|
+
)
|
|
189
|
+
result.add_finding(finding)
|
|
190
|
+
|
|
191
|
+
self.logger.warning(
|
|
192
|
+
"sns_topic_not_encrypted",
|
|
193
|
+
topic_arn=topic_arn
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
except ClientError as e:
|
|
197
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
198
|
+
if error_code in ["NotFound", "AuthorizationError"]:
|
|
199
|
+
self.logger.warning(
|
|
200
|
+
"sns_topic_access_error",
|
|
201
|
+
topic_arn=topic_arn,
|
|
202
|
+
error_code=error_code
|
|
203
|
+
)
|
|
204
|
+
continue
|
|
205
|
+
else:
|
|
206
|
+
raise
|
|
207
|
+
|
|
208
|
+
# Calculate compliance score
|
|
209
|
+
result.score = (encrypted_count / len(topics)) * 100
|
|
210
|
+
|
|
211
|
+
# Determine pass/fail
|
|
212
|
+
result.passed = encrypted_count == len(topics)
|
|
213
|
+
result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
|
|
214
|
+
|
|
215
|
+
# Add metadata
|
|
216
|
+
result.metadata = {
|
|
217
|
+
"total_topics": len(topics),
|
|
218
|
+
"encrypted_topics": encrypted_count,
|
|
219
|
+
"unencrypted_topics": len(topics) - encrypted_count,
|
|
220
|
+
"compliance_percentage": result.score,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
self.logger.info(
|
|
224
|
+
"sns_encryption_test_completed",
|
|
225
|
+
total_topics=len(topics),
|
|
226
|
+
encrypted=encrypted_count,
|
|
227
|
+
score=result.score,
|
|
228
|
+
passed=result.passed
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
except ClientError as e:
|
|
232
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
233
|
+
self.logger.error("sns_encryption_test_error", error_code=error_code, error=str(e))
|
|
234
|
+
result.status = TestStatus.ERROR
|
|
235
|
+
result.passed = False
|
|
236
|
+
result.score = 0.0
|
|
237
|
+
result.error_message = f"AWS API Error: {error_code} - {str(e)}"
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
self.logger.error("sns_encryption_test_error", error=str(e))
|
|
241
|
+
result.status = TestStatus.ERROR
|
|
242
|
+
result.passed = False
|
|
243
|
+
result.score = 0.0
|
|
244
|
+
result.error_message = str(e)
|
|
245
|
+
|
|
246
|
+
return result
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# ============================================================================
|
|
250
|
+
# CONVENIENCE FUNCTION
|
|
251
|
+
# ============================================================================
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def run_sns_encryption_test(connector: AWSConnector) -> TestResult:
|
|
255
|
+
"""Run SNS topic encryption compliance test.
|
|
256
|
+
|
|
257
|
+
Convenience function for running the test.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
connector: AWS connector
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
TestResult
|
|
264
|
+
|
|
265
|
+
Example:
|
|
266
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
267
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
268
|
+
>>> connector.connect()
|
|
269
|
+
>>> result = run_sns_encryption_test(connector)
|
|
270
|
+
>>> print(f"Score: {result.score}%")
|
|
271
|
+
"""
|
|
272
|
+
test = SNSEncryptionTest(connector)
|
|
273
|
+
return test.execute()
|