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.
Files changed (79) hide show
  1. CHANGELOG.md +208 -0
  2. README.md +343 -0
  3. complio/__init__.py +48 -0
  4. complio/cli/__init__.py +0 -0
  5. complio/cli/banner.py +87 -0
  6. complio/cli/commands/__init__.py +0 -0
  7. complio/cli/commands/history.py +439 -0
  8. complio/cli/commands/scan.py +700 -0
  9. complio/cli/main.py +115 -0
  10. complio/cli/output.py +338 -0
  11. complio/config/__init__.py +17 -0
  12. complio/config/settings.py +333 -0
  13. complio/connectors/__init__.py +9 -0
  14. complio/connectors/aws/__init__.py +0 -0
  15. complio/connectors/aws/client.py +342 -0
  16. complio/connectors/base.py +135 -0
  17. complio/core/__init__.py +10 -0
  18. complio/core/registry.py +228 -0
  19. complio/core/runner.py +351 -0
  20. complio/py.typed +0 -0
  21. complio/reporters/__init__.py +7 -0
  22. complio/reporters/generator.py +417 -0
  23. complio/tests_library/__init__.py +0 -0
  24. complio/tests_library/base.py +492 -0
  25. complio/tests_library/identity/__init__.py +0 -0
  26. complio/tests_library/identity/access_key_rotation.py +302 -0
  27. complio/tests_library/identity/mfa_enforcement.py +327 -0
  28. complio/tests_library/identity/root_account_protection.py +470 -0
  29. complio/tests_library/infrastructure/__init__.py +0 -0
  30. complio/tests_library/infrastructure/cloudtrail_encryption.py +286 -0
  31. complio/tests_library/infrastructure/cloudtrail_log_validation.py +274 -0
  32. complio/tests_library/infrastructure/cloudtrail_logging.py +400 -0
  33. complio/tests_library/infrastructure/ebs_encryption.py +244 -0
  34. complio/tests_library/infrastructure/ec2_security_groups.py +321 -0
  35. complio/tests_library/infrastructure/iam_password_policy.py +460 -0
  36. complio/tests_library/infrastructure/nacl_security.py +356 -0
  37. complio/tests_library/infrastructure/rds_encryption.py +252 -0
  38. complio/tests_library/infrastructure/s3_encryption.py +301 -0
  39. complio/tests_library/infrastructure/s3_public_access.py +369 -0
  40. complio/tests_library/infrastructure/secrets_manager_encryption.py +248 -0
  41. complio/tests_library/infrastructure/vpc_flow_logs.py +287 -0
  42. complio/tests_library/logging/__init__.py +0 -0
  43. complio/tests_library/logging/cloudwatch_alarms.py +354 -0
  44. complio/tests_library/logging/cloudwatch_logs_encryption.py +281 -0
  45. complio/tests_library/logging/cloudwatch_retention.py +252 -0
  46. complio/tests_library/logging/config_enabled.py +393 -0
  47. complio/tests_library/logging/eventbridge_rules.py +460 -0
  48. complio/tests_library/logging/guardduty_enabled.py +436 -0
  49. complio/tests_library/logging/security_hub_enabled.py +416 -0
  50. complio/tests_library/logging/sns_encryption.py +273 -0
  51. complio/tests_library/network/__init__.py +0 -0
  52. complio/tests_library/network/alb_nlb_security.py +421 -0
  53. complio/tests_library/network/api_gateway_security.py +452 -0
  54. complio/tests_library/network/cloudfront_https.py +332 -0
  55. complio/tests_library/network/direct_connect_security.py +343 -0
  56. complio/tests_library/network/nacl_configuration.py +367 -0
  57. complio/tests_library/network/network_firewall.py +355 -0
  58. complio/tests_library/network/transit_gateway_security.py +318 -0
  59. complio/tests_library/network/vpc_endpoints_security.py +339 -0
  60. complio/tests_library/network/vpn_security.py +333 -0
  61. complio/tests_library/network/waf_configuration.py +428 -0
  62. complio/tests_library/security/__init__.py +0 -0
  63. complio/tests_library/security/kms_key_rotation.py +314 -0
  64. complio/tests_library/storage/__init__.py +0 -0
  65. complio/tests_library/storage/backup_encryption.py +288 -0
  66. complio/tests_library/storage/dynamodb_encryption.py +280 -0
  67. complio/tests_library/storage/efs_encryption.py +257 -0
  68. complio/tests_library/storage/elasticache_encryption.py +370 -0
  69. complio/tests_library/storage/redshift_encryption.py +252 -0
  70. complio/tests_library/storage/s3_versioning.py +264 -0
  71. complio/utils/__init__.py +26 -0
  72. complio/utils/errors.py +179 -0
  73. complio/utils/exceptions.py +151 -0
  74. complio/utils/history.py +243 -0
  75. complio/utils/logger.py +391 -0
  76. complio-0.1.1.dist-info/METADATA +385 -0
  77. complio-0.1.1.dist-info/RECORD +79 -0
  78. complio-0.1.1.dist-info/WHEEL +4 -0
  79. complio-0.1.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,252 @@
1
+ """
2
+ CloudWatch Logs retention compliance test.
3
+
4
+ Checks that all CloudWatch log groups have retention policies configured.
5
+
6
+ ISO 27001 Control: A.8.15 - Logging
7
+ Requirement: Log groups must have retention policies to manage log lifecycle
8
+
9
+ Example:
10
+ >>> from complio.connectors.aws.client import AWSConnector
11
+ >>> from complio.tests_library.logging.cloudwatch_retention import CloudWatchRetentionTest
12
+ >>>
13
+ >>> connector = AWSConnector("production", "us-east-1")
14
+ >>> connector.connect()
15
+ >>>
16
+ >>> test = CloudWatchRetentionTest(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 CloudWatchRetentionTest(ComplianceTest):
35
+ """Test for CloudWatch Logs retention compliance.
36
+
37
+ Verifies that all CloudWatch log groups have retention policies configured
38
+ to prevent indefinite log storage and manage costs.
39
+
40
+ Compliance Requirements:
41
+ - All log groups must have retention policy set (not "Never expire")
42
+ - Retention period should align with compliance requirements
43
+ - Typical retention: 30, 90, 180, 365 days or longer
44
+
45
+ Scoring:
46
+ - 100% if all log groups have retention configured
47
+ - Proportional score based on compliant/total ratio
48
+ - 100% if no log groups exist
49
+
50
+ Example:
51
+ >>> test = CloudWatchRetentionTest(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 CloudWatch retention test.
59
+
60
+ Args:
61
+ connector: AWS connector instance
62
+ """
63
+ super().__init__(
64
+ test_id="cloudwatch_retention",
65
+ test_name="CloudWatch Logs Retention Check",
66
+ description="Verify all CloudWatch log groups have retention policies configured",
67
+ control_id="A.8.15",
68
+ connector=connector,
69
+ scope="regional",
70
+ )
71
+
72
+ def execute(self) -> TestResult:
73
+ """Execute CloudWatch Logs retention compliance test.
74
+
75
+ Returns:
76
+ TestResult with findings for log groups without retention
77
+
78
+ Example:
79
+ >>> test = CloudWatchRetentionTest(connector)
80
+ >>> result = test.execute()
81
+ >>> print(result.score)
82
+ 95.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 CloudWatch Logs client
94
+ logs_client = self.connector.get_client("logs")
95
+
96
+ # List all log groups
97
+ self.logger.info("listing_log_groups")
98
+ log_groups = []
99
+
100
+ paginator = logs_client.get_paginator("describe_log_groups")
101
+ for page in paginator.paginate():
102
+ log_groups.extend(page.get("logGroups", []))
103
+
104
+ if not log_groups:
105
+ self.logger.info("no_log_groups_found")
106
+ result.metadata["message"] = "No CloudWatch log groups found in region"
107
+ return result
108
+
109
+ self.logger.info("log_groups_found", count=len(log_groups))
110
+
111
+ # Check retention for each log group
112
+ retention_configured_count = 0
113
+
114
+ for log_group in log_groups:
115
+ log_group_name = log_group["logGroupName"]
116
+ result.resources_scanned += 1
117
+
118
+ # Check if retention is configured
119
+ # retentionInDays is only present if retention is set
120
+ retention_in_days = log_group.get("retentionInDays")
121
+ has_retention = retention_in_days is not None
122
+
123
+ # Create evidence
124
+ evidence = self.create_evidence(
125
+ resource_id=log_group_name,
126
+ resource_type="cloudwatch_log_group",
127
+ data={
128
+ "log_group_name": log_group_name,
129
+ "retention_in_days": retention_in_days,
130
+ "has_retention": has_retention,
131
+ "creation_time": log_group.get("creationTime"),
132
+ "stored_bytes": log_group.get("storedBytes", 0),
133
+ }
134
+ )
135
+ result.add_evidence(evidence)
136
+
137
+ if has_retention:
138
+ retention_configured_count += 1
139
+ self.logger.debug(
140
+ "log_group_has_retention",
141
+ log_group=log_group_name,
142
+ retention_days=retention_in_days
143
+ )
144
+ else:
145
+ # Create finding for log group without retention
146
+ finding = self.create_finding(
147
+ resource_id=log_group_name,
148
+ resource_type="cloudwatch_log_group",
149
+ severity=Severity.MEDIUM,
150
+ title="CloudWatch log group has no retention policy",
151
+ description=f"CloudWatch log group '{log_group_name}' does not have a retention "
152
+ "policy configured (set to 'Never expire'). Without retention policies, "
153
+ "logs are stored indefinitely, leading to unnecessary storage costs and "
154
+ "making it difficult to manage log lifecycle according to compliance "
155
+ "requirements. ISO 27001 A.8.15 requires proper logging practices "
156
+ "including log retention management.",
157
+ remediation=(
158
+ f"Set retention policy for log group '{log_group_name}':\n\n"
159
+ "Using AWS CLI (example: 90 days retention):\n"
160
+ f"aws logs put-retention-policy \\\n"
161
+ f" --log-group-name {log_group_name} \\\n"
162
+ " --retention-in-days 90\n\n"
163
+ "Common retention periods:\n"
164
+ "- 1, 3, 5, 7, 14 days (development/testing)\n"
165
+ "- 30, 60, 90 days (operational logs)\n"
166
+ "- 120, 150, 180 days (compliance logs)\n"
167
+ "- 365, 400, 545, 731 days (audit logs)\n"
168
+ "- 1827, 2192, 2557, 2922, 3288, 3653 days (long-term compliance)\n\n"
169
+ "Or use AWS Console:\n"
170
+ "1. Go to CloudWatch Logs console\n"
171
+ f"2. Select log group '{log_group_name}'\n"
172
+ "3. Click 'Actions' → 'Edit retention setting'\n"
173
+ "4. Select appropriate retention period\n"
174
+ "5. Click 'Save'\n\n"
175
+ "Note: Consider your compliance requirements (GDPR, HIPAA, PCI-DSS, etc.) "
176
+ "when choosing retention periods. Archive to S3 for longer retention if needed."
177
+ ),
178
+ evidence=evidence
179
+ )
180
+ result.add_finding(finding)
181
+
182
+ self.logger.warning(
183
+ "log_group_no_retention",
184
+ log_group=log_group_name
185
+ )
186
+
187
+ # Calculate compliance score
188
+ result.score = (retention_configured_count / len(log_groups)) * 100
189
+
190
+ # Determine pass/fail
191
+ result.passed = retention_configured_count == len(log_groups)
192
+ result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
193
+
194
+ # Add metadata
195
+ result.metadata = {
196
+ "total_log_groups": len(log_groups),
197
+ "retention_configured": retention_configured_count,
198
+ "no_retention": len(log_groups) - retention_configured_count,
199
+ "compliance_percentage": result.score,
200
+ }
201
+
202
+ self.logger.info(
203
+ "cloudwatch_retention_test_completed",
204
+ total_log_groups=len(log_groups),
205
+ retention_configured=retention_configured_count,
206
+ score=result.score,
207
+ passed=result.passed
208
+ )
209
+
210
+ except ClientError as e:
211
+ error_code = e.response.get("Error", {}).get("Code")
212
+ self.logger.error("cloudwatch_retention_test_error", error_code=error_code, error=str(e))
213
+ result.status = TestStatus.ERROR
214
+ result.passed = False
215
+ result.score = 0.0
216
+ result.error_message = f"AWS API Error: {error_code} - {str(e)}"
217
+
218
+ except Exception as e:
219
+ self.logger.error("cloudwatch_retention_test_error", error=str(e))
220
+ result.status = TestStatus.ERROR
221
+ result.passed = False
222
+ result.score = 0.0
223
+ result.error_message = str(e)
224
+
225
+ return result
226
+
227
+
228
+ # ============================================================================
229
+ # CONVENIENCE FUNCTION
230
+ # ============================================================================
231
+
232
+
233
+ def run_cloudwatch_retention_test(connector: AWSConnector) -> TestResult:
234
+ """Run CloudWatch Logs retention compliance test.
235
+
236
+ Convenience function for running the test.
237
+
238
+ Args:
239
+ connector: AWS connector
240
+
241
+ Returns:
242
+ TestResult
243
+
244
+ Example:
245
+ >>> from complio.connectors.aws.client import AWSConnector
246
+ >>> connector = AWSConnector("production", "us-east-1")
247
+ >>> connector.connect()
248
+ >>> result = run_cloudwatch_retention_test(connector)
249
+ >>> print(f"Score: {result.score}%")
250
+ """
251
+ test = CloudWatchRetentionTest(connector)
252
+ return test.execute()
@@ -0,0 +1,393 @@
1
+ """
2
+ AWS Config enabled compliance test.
3
+
4
+ Checks that AWS Config is enabled for configuration tracking and compliance.
5
+
6
+ ISO 27001 Control: A.8.16 - Monitoring activities
7
+ Requirement: AWS Config should be enabled for resource configuration tracking
8
+
9
+ Example:
10
+ >>> from complio.connectors.aws.client import AWSConnector
11
+ >>> from complio.tests_library.logging.config_enabled import ConfigEnabledTest
12
+ >>>
13
+ >>> connector = AWSConnector("production", "us-east-1")
14
+ >>> connector.connect()
15
+ >>>
16
+ >>> test = ConfigEnabledTest(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 ConfigEnabledTest(ComplianceTest):
35
+ """Test for AWS Config enabled compliance.
36
+
37
+ Verifies that AWS Config is properly enabled and configured:
38
+ - Configuration recorder should be created
39
+ - Configuration recorder should be recording (not stopped)
40
+ - Delivery channel should be configured (S3 bucket)
41
+ - Should be recording all resource types (global and regional)
42
+
43
+ Compliance Requirements:
44
+ - AWS Config enabled in region
45
+ - Recording all supported resource types
46
+ - Delivery channel configured with S3 bucket
47
+ - Configuration recorder in RECORDING state
48
+
49
+ Scoring:
50
+ - 100% if AWS Config is properly enabled and recording
51
+ - 0% if AWS Config is not enabled or not recording
52
+ - Partial score for partial configuration
53
+
54
+ Example:
55
+ >>> test = ConfigEnabledTest(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 AWS Config enabled test.
63
+
64
+ Args:
65
+ connector: AWS connector instance
66
+ """
67
+ super().__init__(
68
+ test_id="config_enabled",
69
+ test_name="AWS Config Enabled Check",
70
+ description="Verify AWS Config is enabled for configuration tracking and compliance",
71
+ control_id="A.8.16",
72
+ connector=connector,
73
+ scope="regional",
74
+ )
75
+
76
+ def execute(self) -> TestResult:
77
+ """Execute AWS Config enabled compliance test.
78
+
79
+ Returns:
80
+ TestResult with findings if Config is not properly enabled
81
+
82
+ Example:
83
+ >>> test = ConfigEnabledTest(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 Config client
98
+ config_client = self.connector.get_client("config")
99
+
100
+ # Check configuration recorders
101
+ self.logger.info("checking_config_recorders")
102
+ recorders_response = config_client.describe_configuration_recorders()
103
+ recorders = recorders_response.get("ConfigurationRecorders", [])
104
+
105
+ if not recorders:
106
+ self.logger.warning("no_config_recorders_found")
107
+
108
+ # Create finding for missing Config
109
+ finding = self.create_finding(
110
+ resource_id="aws-config",
111
+ resource_type="config_service",
112
+ severity=Severity.HIGH,
113
+ title="AWS Config not enabled",
114
+ description="AWS Config is not enabled in this region. AWS Config provides configuration "
115
+ "history, change tracking, and compliance monitoring for AWS resources. "
116
+ "Without Config, you cannot track configuration changes, assess compliance "
117
+ "against rules, or perform resource inventory. ISO 27001 A.8.16 requires "
118
+ "monitoring of system activities and configuration changes.",
119
+ remediation=(
120
+ "Enable AWS Config in this region:\n\n"
121
+ "1. Create S3 bucket for Config data:\n"
122
+ "aws s3 mb s3://my-config-bucket-<account-id>-<region>\n\n"
123
+ "# Enable versioning on the bucket\n"
124
+ "aws s3api put-bucket-versioning \\\n"
125
+ " --bucket my-config-bucket-<account-id>-<region> \\\n"
126
+ " --versioning-configuration Status=Enabled\n\n"
127
+ "2. Create IAM role for Config:\n"
128
+ "aws iam create-role \\\n"
129
+ " --role-name AWSConfigRole \\\n"
130
+ " --assume-role-policy-document file://config-trust-policy.json\n\n"
131
+ "# Attach AWS managed policy\n"
132
+ "aws iam attach-role-policy \\\n"
133
+ " --role-name AWSConfigRole \\\n"
134
+ " --policy-arn arn:aws:iam::aws:policy/service-role/ConfigRole\n\n"
135
+ "3. Create configuration recorder:\n"
136
+ "aws configservice put-configuration-recorder \\\n"
137
+ " --configuration-recorder name=default,roleARN=arn:aws:iam::<account-id>:role/AWSConfigRole \\\n"
138
+ " --recording-group allSupported=true,includeGlobalResourceTypes=true\n\n"
139
+ "4. Create delivery channel:\n"
140
+ "aws configservice put-delivery-channel \\\n"
141
+ " --delivery-channel name=default,s3BucketName=my-config-bucket-<account-id>-<region>\n\n"
142
+ "5. Start configuration recorder:\n"
143
+ "aws configservice start-configuration-recorder \\\n"
144
+ " --configuration-recorder-name default\n\n"
145
+ "Or use AWS Console:\n"
146
+ "1. Go to AWS Config console\n"
147
+ "2. Click 'Get started' (if first time)\n"
148
+ "3. Configure settings:\n"
149
+ " - Resource types to record: All resources\n"
150
+ " - Include global resources: Yes (in one region)\n"
151
+ " - S3 bucket: Create new or use existing\n"
152
+ " - SNS topic: Optional (for notifications)\n"
153
+ " - IAM role: Create new or use existing\n"
154
+ "4. Click 'Confirm'\n\n"
155
+ "Best practices:\n"
156
+ "- Enable Config in all regions\n"
157
+ "- Record all supported resource types\n"
158
+ "- Include global resources in one region (usually us-east-1)\n"
159
+ "- Enable S3 bucket encryption\n"
160
+ "- Set up SNS notifications for changes\n"
161
+ "- Configure Config Rules for compliance checks\n"
162
+ "- Use Config Aggregator for multi-account view\n"
163
+ "- Set up lifecycle policies for S3 bucket\n"
164
+ "- Enable CloudTrail for Config API activity\n"
165
+ "- Use AWS Organizations for centralized Config\n\n"
166
+ "Config Rules to consider:\n"
167
+ "- required-tags (ensure proper tagging)\n"
168
+ "- encrypted-volumes (EBS encryption)\n"
169
+ "- rds-storage-encrypted (RDS encryption)\n"
170
+ "- s3-bucket-public-read-prohibited\n"
171
+ "- s3-bucket-public-write-prohibited\n"
172
+ "- cloudtrail-enabled\n"
173
+ "- iam-password-policy\n"
174
+ "- root-account-mfa-enabled\n"
175
+ "- vpc-flow-logs-enabled"
176
+ ),
177
+ evidence=None
178
+ )
179
+ result.add_finding(finding)
180
+ result.score = 0.0
181
+ result.passed = False
182
+ result.status = TestStatus.FAILED
183
+ result.metadata = {
184
+ "config_enabled": False,
185
+ "message": "AWS Config not enabled in region"
186
+ }
187
+ return result
188
+
189
+ # Check first recorder (typically there's only one)
190
+ recorder = recorders[0]
191
+ recorder_name = recorder.get("name", "")
192
+ role_arn = recorder.get("roleARN", "")
193
+ recording_group = recorder.get("recordingGroup", {})
194
+
195
+ all_supported = recording_group.get("allSupported", False)
196
+ include_global = recording_group.get("includeGlobalResourceTypes", False)
197
+
198
+ result.resources_scanned += 1
199
+
200
+ # Check recorder status
201
+ status_response = config_client.describe_configuration_recorder_status(
202
+ ConfigurationRecorderNames=[recorder_name]
203
+ )
204
+ recorder_statuses = status_response.get("ConfigurationRecordersStatus", [])
205
+
206
+ is_recording = False
207
+ last_status = None
208
+ if recorder_statuses:
209
+ recorder_status = recorder_statuses[0]
210
+ is_recording = recorder_status.get("recording", False)
211
+ last_status = recorder_status.get("lastStatus", "")
212
+
213
+ # Check delivery channel
214
+ channels_response = config_client.describe_delivery_channels()
215
+ channels = channels_response.get("DeliveryChannels", [])
216
+ has_delivery_channel = len(channels) > 0
217
+
218
+ # Determine issues
219
+ issues = []
220
+ severity = Severity.HIGH
221
+
222
+ if not is_recording:
223
+ issues.append("Configuration recorder not recording (stopped)")
224
+ severity = Severity.HIGH
225
+
226
+ if not all_supported:
227
+ issues.append("Not recording all supported resource types")
228
+ severity = Severity.MEDIUM
229
+
230
+ if not has_delivery_channel:
231
+ issues.append("No delivery channel configured (no S3 bucket)")
232
+ severity = Severity.HIGH
233
+
234
+ if last_status and last_status != "SUCCESS":
235
+ issues.append(f"Last recording status: {last_status}")
236
+ severity = Severity.MEDIUM
237
+
238
+ # Create evidence
239
+ evidence = self.create_evidence(
240
+ resource_id=recorder_name,
241
+ resource_type="config_recorder",
242
+ data={
243
+ "recorder_name": recorder_name,
244
+ "role_arn": role_arn,
245
+ "is_recording": is_recording,
246
+ "all_supported": all_supported,
247
+ "include_global": include_global,
248
+ "has_delivery_channel": has_delivery_channel,
249
+ "last_status": last_status,
250
+ "has_issues": len(issues) > 0,
251
+ "issues": issues,
252
+ }
253
+ )
254
+ result.add_evidence(evidence)
255
+
256
+ if len(issues) > 0:
257
+ # Create finding for Config issues
258
+ finding = self.create_finding(
259
+ resource_id=recorder_name,
260
+ resource_type="config_recorder",
261
+ severity=severity,
262
+ title="AWS Config has configuration issues",
263
+ description=f"AWS Config recorder '{recorder_name}' has configuration issues: "
264
+ f"{'; '.join(issues)}. AWS Config should be actively recording all "
265
+ "supported resource types and delivering data to S3. ISO 27001 A.8.16 "
266
+ "requires proper monitoring and configuration tracking.",
267
+ remediation=(
268
+ f"Fix AWS Config recorder '{recorder_name}' issues:\n\n"
269
+ "1. Start configuration recorder if stopped:\n"
270
+ f"aws configservice start-configuration-recorder \\\n"
271
+ f" --configuration-recorder-name {recorder_name}\n\n"
272
+ "2. Update to record all resource types:\n"
273
+ f"aws configservice put-configuration-recorder \\\n"
274
+ f" --configuration-recorder name={recorder_name},roleARN={role_arn} \\\n"
275
+ " --recording-group allSupported=true,includeGlobalResourceTypes=true\n\n"
276
+ "3. Configure delivery channel if missing:\n"
277
+ "aws configservice put-delivery-channel \\\n"
278
+ " --delivery-channel name=default,s3BucketName=my-config-bucket\n\n"
279
+ "4. Verify recorder is working:\n"
280
+ f"aws configservice describe-configuration-recorder-status \\\n"
281
+ f" --configuration-recorder-names {recorder_name}\n\n"
282
+ "Or use AWS Console:\n"
283
+ "1. Go to AWS Config console\n"
284
+ "2. Click 'Settings'\n"
285
+ "3. Verify configuration:\n"
286
+ " - Recording is ON\n"
287
+ " - All resources selected\n"
288
+ " - Include global resources (if primary region)\n"
289
+ " - S3 bucket configured\n"
290
+ "4. Click 'Save' if changes made\n\n"
291
+ "Troubleshooting:\n"
292
+ "- Check IAM role has correct permissions\n"
293
+ "- Verify S3 bucket exists and Config has access\n"
294
+ "- Check S3 bucket policy allows Config writes\n"
295
+ "- Verify region is correct\n"
296
+ "- Check service limits not exceeded\n"
297
+ "- Review CloudTrail for Config API errors"
298
+ ),
299
+ evidence=evidence
300
+ )
301
+ result.add_finding(finding)
302
+
303
+ # Calculate partial score
304
+ score_components = []
305
+ if is_recording:
306
+ score_components.append(40) # 40% for recording
307
+ if has_delivery_channel:
308
+ score_components.append(40) # 40% for delivery channel
309
+ if all_supported:
310
+ score_components.append(20) # 20% for recording all resources
311
+
312
+ result.score = sum(score_components)
313
+ result.passed = False
314
+ result.status = TestStatus.FAILED
315
+
316
+ self.logger.warning(
317
+ "config_has_issues",
318
+ recorder_name=recorder_name,
319
+ issues=issues
320
+ )
321
+ else:
322
+ # Config is properly configured
323
+ result.score = 100.0
324
+ result.passed = True
325
+ result.status = TestStatus.PASSED
326
+
327
+ self.logger.info(
328
+ "config_properly_configured",
329
+ recorder_name=recorder_name
330
+ )
331
+
332
+ # Add metadata
333
+ result.metadata = {
334
+ "config_enabled": True,
335
+ "recorder_name": recorder_name,
336
+ "is_recording": is_recording,
337
+ "all_supported": all_supported,
338
+ "include_global": include_global,
339
+ "has_delivery_channel": has_delivery_channel,
340
+ "compliance_percentage": result.score,
341
+ }
342
+
343
+ self.logger.info(
344
+ "config_enabled_test_completed",
345
+ recorder_name=recorder_name,
346
+ is_recording=is_recording,
347
+ score=result.score,
348
+ passed=result.passed
349
+ )
350
+
351
+ except ClientError as e:
352
+ error_code = e.response.get("Error", {}).get("Code")
353
+ self.logger.error("config_enabled_test_error", error_code=error_code, error=str(e))
354
+ result.status = TestStatus.ERROR
355
+ result.passed = False
356
+ result.score = 0.0
357
+ result.error_message = f"AWS API Error: {error_code} - {str(e)}"
358
+
359
+ except Exception as e:
360
+ self.logger.error("config_enabled_test_error", error=str(e))
361
+ result.status = TestStatus.ERROR
362
+ result.passed = False
363
+ result.score = 0.0
364
+ result.error_message = str(e)
365
+
366
+ return result
367
+
368
+
369
+ # ============================================================================
370
+ # CONVENIENCE FUNCTION
371
+ # ============================================================================
372
+
373
+
374
+ def run_config_enabled_test(connector: AWSConnector) -> TestResult:
375
+ """Run AWS Config enabled compliance test.
376
+
377
+ Convenience function for running the test.
378
+
379
+ Args:
380
+ connector: AWS connector
381
+
382
+ Returns:
383
+ TestResult
384
+
385
+ Example:
386
+ >>> from complio.connectors.aws.client import AWSConnector
387
+ >>> connector = AWSConnector("production", "us-east-1")
388
+ >>> connector.connect()
389
+ >>> result = run_config_enabled_test(connector)
390
+ >>> print(f"Score: {result.score}%")
391
+ """
392
+ test = ConfigEnabledTest(connector)
393
+ return test.execute()