aws-cis-controls-assessment 1.0.3__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 (77) hide show
  1. aws_cis_assessment/__init__.py +11 -0
  2. aws_cis_assessment/cli/__init__.py +3 -0
  3. aws_cis_assessment/cli/examples.py +274 -0
  4. aws_cis_assessment/cli/main.py +1259 -0
  5. aws_cis_assessment/cli/utils.py +356 -0
  6. aws_cis_assessment/config/__init__.py +1 -0
  7. aws_cis_assessment/config/config_loader.py +328 -0
  8. aws_cis_assessment/config/rules/cis_controls_ig1.yaml +590 -0
  9. aws_cis_assessment/config/rules/cis_controls_ig2.yaml +412 -0
  10. aws_cis_assessment/config/rules/cis_controls_ig3.yaml +100 -0
  11. aws_cis_assessment/controls/__init__.py +1 -0
  12. aws_cis_assessment/controls/base_control.py +400 -0
  13. aws_cis_assessment/controls/ig1/__init__.py +239 -0
  14. aws_cis_assessment/controls/ig1/control_1_1.py +586 -0
  15. aws_cis_assessment/controls/ig1/control_2_2.py +231 -0
  16. aws_cis_assessment/controls/ig1/control_3_3.py +718 -0
  17. aws_cis_assessment/controls/ig1/control_3_4.py +235 -0
  18. aws_cis_assessment/controls/ig1/control_4_1.py +461 -0
  19. aws_cis_assessment/controls/ig1/control_access_keys.py +310 -0
  20. aws_cis_assessment/controls/ig1/control_advanced_security.py +512 -0
  21. aws_cis_assessment/controls/ig1/control_backup_recovery.py +510 -0
  22. aws_cis_assessment/controls/ig1/control_cloudtrail_logging.py +197 -0
  23. aws_cis_assessment/controls/ig1/control_critical_security.py +422 -0
  24. aws_cis_assessment/controls/ig1/control_data_protection.py +898 -0
  25. aws_cis_assessment/controls/ig1/control_iam_advanced.py +573 -0
  26. aws_cis_assessment/controls/ig1/control_iam_governance.py +493 -0
  27. aws_cis_assessment/controls/ig1/control_iam_policies.py +383 -0
  28. aws_cis_assessment/controls/ig1/control_instance_optimization.py +100 -0
  29. aws_cis_assessment/controls/ig1/control_network_enhancements.py +203 -0
  30. aws_cis_assessment/controls/ig1/control_network_security.py +672 -0
  31. aws_cis_assessment/controls/ig1/control_s3_enhancements.py +173 -0
  32. aws_cis_assessment/controls/ig1/control_s3_security.py +422 -0
  33. aws_cis_assessment/controls/ig1/control_vpc_security.py +235 -0
  34. aws_cis_assessment/controls/ig2/__init__.py +172 -0
  35. aws_cis_assessment/controls/ig2/control_3_10.py +698 -0
  36. aws_cis_assessment/controls/ig2/control_3_11.py +1330 -0
  37. aws_cis_assessment/controls/ig2/control_5_2.py +393 -0
  38. aws_cis_assessment/controls/ig2/control_advanced_encryption.py +355 -0
  39. aws_cis_assessment/controls/ig2/control_codebuild_security.py +263 -0
  40. aws_cis_assessment/controls/ig2/control_encryption_rest.py +382 -0
  41. aws_cis_assessment/controls/ig2/control_encryption_transit.py +382 -0
  42. aws_cis_assessment/controls/ig2/control_network_ha.py +467 -0
  43. aws_cis_assessment/controls/ig2/control_remaining_encryption.py +426 -0
  44. aws_cis_assessment/controls/ig2/control_remaining_rules.py +363 -0
  45. aws_cis_assessment/controls/ig2/control_service_logging.py +402 -0
  46. aws_cis_assessment/controls/ig3/__init__.py +49 -0
  47. aws_cis_assessment/controls/ig3/control_12_8.py +395 -0
  48. aws_cis_assessment/controls/ig3/control_13_1.py +467 -0
  49. aws_cis_assessment/controls/ig3/control_3_14.py +523 -0
  50. aws_cis_assessment/controls/ig3/control_7_1.py +359 -0
  51. aws_cis_assessment/core/__init__.py +1 -0
  52. aws_cis_assessment/core/accuracy_validator.py +425 -0
  53. aws_cis_assessment/core/assessment_engine.py +1266 -0
  54. aws_cis_assessment/core/audit_trail.py +491 -0
  55. aws_cis_assessment/core/aws_client_factory.py +313 -0
  56. aws_cis_assessment/core/error_handler.py +607 -0
  57. aws_cis_assessment/core/models.py +166 -0
  58. aws_cis_assessment/core/scoring_engine.py +459 -0
  59. aws_cis_assessment/reporters/__init__.py +8 -0
  60. aws_cis_assessment/reporters/base_reporter.py +454 -0
  61. aws_cis_assessment/reporters/csv_reporter.py +835 -0
  62. aws_cis_assessment/reporters/html_reporter.py +2162 -0
  63. aws_cis_assessment/reporters/json_reporter.py +561 -0
  64. aws_cis_controls_assessment-1.0.3.dist-info/METADATA +248 -0
  65. aws_cis_controls_assessment-1.0.3.dist-info/RECORD +77 -0
  66. aws_cis_controls_assessment-1.0.3.dist-info/WHEEL +5 -0
  67. aws_cis_controls_assessment-1.0.3.dist-info/entry_points.txt +2 -0
  68. aws_cis_controls_assessment-1.0.3.dist-info/licenses/LICENSE +21 -0
  69. aws_cis_controls_assessment-1.0.3.dist-info/top_level.txt +2 -0
  70. docs/README.md +94 -0
  71. docs/assessment-logic.md +766 -0
  72. docs/cli-reference.md +698 -0
  73. docs/config-rule-mappings.md +393 -0
  74. docs/developer-guide.md +858 -0
  75. docs/installation.md +299 -0
  76. docs/troubleshooting.md +634 -0
  77. docs/user-guide.md +487 -0
@@ -0,0 +1,235 @@
1
+ """Control 3.4: Enforce Data Retention assessments."""
2
+
3
+ from typing import Dict, List, Any
4
+ import logging
5
+ from botocore.exceptions import ClientError
6
+
7
+ from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
8
+ from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
9
+ from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class S3VersionLifecyclePolicyAssessment(BaseConfigRuleAssessment):
15
+ """Assessment for s3-version-lifecycle-policy-check Config rule."""
16
+
17
+ def __init__(self):
18
+ """Initialize S3 version lifecycle policy assessment."""
19
+ super().__init__(
20
+ rule_name="s3-version-lifecycle-policy-check",
21
+ control_id="3.4",
22
+ resource_types=["AWS::S3::Bucket"]
23
+ )
24
+
25
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
26
+ """Get all S3 buckets."""
27
+ if resource_type != "AWS::S3::Bucket":
28
+ return []
29
+
30
+ try:
31
+ s3_client = aws_factory.get_client('s3', region)
32
+
33
+ response = aws_factory.aws_api_call_with_retry(
34
+ lambda: s3_client.list_buckets()
35
+ )
36
+
37
+ buckets = []
38
+ for bucket in response.get('Buckets', []):
39
+ bucket_name = bucket.get('Name')
40
+
41
+ # Check if bucket is in the current region
42
+ try:
43
+ bucket_location = aws_factory.aws_api_call_with_retry(
44
+ lambda: s3_client.get_bucket_location(Bucket=bucket_name)
45
+ )
46
+ bucket_region = bucket_location.get('LocationConstraint') or 'us-east-1'
47
+
48
+ if bucket_region == region or (region == 'us-east-1' and bucket_region is None):
49
+ buckets.append({
50
+ 'BucketName': bucket_name,
51
+ 'CreationDate': bucket.get('CreationDate')
52
+ })
53
+ except ClientError as e:
54
+ # Skip buckets we can't access
55
+ logger.debug(f"Cannot access bucket {bucket_name}: {e}")
56
+ continue
57
+
58
+ logger.debug(f"Found {len(buckets)} S3 buckets in region {region}")
59
+ return buckets
60
+
61
+ except ClientError as e:
62
+ logger.error(f"Error retrieving S3 buckets: {e}")
63
+ raise
64
+ except Exception as e:
65
+ logger.error(f"Unexpected error retrieving S3 buckets: {e}")
66
+ raise
67
+
68
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
69
+ """Evaluate if S3 bucket has lifecycle policy configured."""
70
+ bucket_name = resource.get('BucketName', 'unknown')
71
+
72
+ try:
73
+ s3_client = aws_factory.get_client('s3', region)
74
+
75
+ # Check if bucket has lifecycle configuration
76
+ try:
77
+ lifecycle_response = aws_factory.aws_api_call_with_retry(
78
+ lambda: s3_client.get_bucket_lifecycle_configuration(Bucket=bucket_name)
79
+ )
80
+
81
+ rules = lifecycle_response.get('Rules', [])
82
+
83
+ # Check if there are any lifecycle rules
84
+ if rules:
85
+ # Check for versioning-related rules
86
+ has_version_rules = False
87
+ rule_details = []
88
+
89
+ for rule in rules:
90
+ if rule.get('Status') == 'Enabled':
91
+ rule_id = rule.get('Id', 'unnamed')
92
+ rule_details.append(rule_id)
93
+
94
+ # Check for noncurrent version transitions or expiration
95
+ if (rule.get('NoncurrentVersionTransitions') or
96
+ rule.get('NoncurrentVersionExpiration')):
97
+ has_version_rules = True
98
+
99
+ if has_version_rules:
100
+ compliance_status = ComplianceStatus.COMPLIANT
101
+ evaluation_reason = f"Bucket {bucket_name} has lifecycle policy with version management rules: {', '.join(rule_details)}"
102
+ else:
103
+ compliance_status = ComplianceStatus.NON_COMPLIANT
104
+ evaluation_reason = f"Bucket {bucket_name} has lifecycle policy but no version management rules"
105
+ else:
106
+ compliance_status = ComplianceStatus.NON_COMPLIANT
107
+ evaluation_reason = f"Bucket {bucket_name} has no enabled lifecycle rules"
108
+
109
+ except ClientError as lifecycle_error:
110
+ if lifecycle_error.response.get('Error', {}).get('Code') == 'NoSuchLifecycleConfiguration':
111
+ compliance_status = ComplianceStatus.NON_COMPLIANT
112
+ evaluation_reason = f"Bucket {bucket_name} has no lifecycle configuration"
113
+ else:
114
+ raise lifecycle_error
115
+
116
+ except ClientError as e:
117
+ error_code = e.response.get('Error', {}).get('Code', '')
118
+ if error_code in ['AccessDenied', 'NoSuchBucket']:
119
+ compliance_status = ComplianceStatus.ERROR
120
+ evaluation_reason = f"Cannot access bucket {bucket_name}: {error_code}"
121
+ else:
122
+ compliance_status = ComplianceStatus.ERROR
123
+ evaluation_reason = f"Error checking lifecycle policy for bucket {bucket_name}: {str(e)}"
124
+ except Exception as e:
125
+ compliance_status = ComplianceStatus.ERROR
126
+ evaluation_reason = f"Unexpected error checking lifecycle policy for bucket {bucket_name}: {str(e)}"
127
+
128
+ return ComplianceResult(
129
+ resource_id=bucket_name,
130
+ resource_type="AWS::S3::Bucket",
131
+ compliance_status=compliance_status,
132
+ evaluation_reason=evaluation_reason,
133
+ config_rule_name=self.rule_name,
134
+ region=region
135
+ )
136
+
137
+ def _get_rule_remediation_steps(self) -> List[str]:
138
+ """Get specific remediation steps for S3 lifecycle policies."""
139
+ return [
140
+ "Identify S3 buckets without lifecycle policies or version management rules",
141
+ "For each bucket, configure appropriate lifecycle policies:",
142
+ " 1. Go to the S3 console",
143
+ " 2. Select the bucket",
144
+ " 3. Go to Management > Lifecycle rules",
145
+ " 4. Create a new lifecycle rule",
146
+ " 5. Configure transitions for current and noncurrent versions",
147
+ " 6. Set expiration policies for old versions",
148
+ "Use AWS CLI: aws s3api put-bucket-lifecycle-configuration",
149
+ "Consider cost optimization by transitioning old versions to cheaper storage classes",
150
+ "Set up monitoring for lifecycle rule effectiveness"
151
+ ]
152
+
153
+
154
+ class CloudWatchLogGroupRetentionAssessment(BaseConfigRuleAssessment):
155
+ """Assessment for cw-loggroup-retention-period-check Config rule."""
156
+
157
+ def __init__(self, min_retention_days: int = 30):
158
+ """Initialize CloudWatch log group retention assessment."""
159
+ super().__init__(
160
+ rule_name="cw-loggroup-retention-period-check",
161
+ control_id="3.4",
162
+ resource_types=["AWS::Logs::LogGroup"],
163
+ parameters={"minRetentionTime": min_retention_days}
164
+ )
165
+ self.min_retention_days = min_retention_days
166
+
167
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
168
+ """Get all CloudWatch log groups in the region."""
169
+ if resource_type != "AWS::Logs::LogGroup":
170
+ return []
171
+
172
+ try:
173
+ logs_client = aws_factory.get_client('logs', region)
174
+
175
+ log_groups = []
176
+ paginator = logs_client.get_paginator('describe_log_groups')
177
+
178
+ for page in paginator.paginate():
179
+ for log_group in page.get('logGroups', []):
180
+ log_groups.append({
181
+ 'LogGroupName': log_group.get('logGroupName'),
182
+ 'RetentionInDays': log_group.get('retentionInDays'),
183
+ 'CreationTime': log_group.get('creationTime'),
184
+ 'StoredBytes': log_group.get('storedBytes', 0)
185
+ })
186
+
187
+ logger.debug(f"Found {len(log_groups)} CloudWatch log groups in region {region}")
188
+ return log_groups
189
+
190
+ except ClientError as e:
191
+ logger.error(f"Error retrieving CloudWatch log groups in region {region}: {e}")
192
+ raise
193
+ except Exception as e:
194
+ logger.error(f"Unexpected error retrieving CloudWatch log groups in region {region}: {e}")
195
+ raise
196
+
197
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
198
+ """Evaluate if CloudWatch log group has appropriate retention period."""
199
+ log_group_name = resource.get('LogGroupName', 'unknown')
200
+ retention_days = resource.get('RetentionInDays')
201
+
202
+ if retention_days is None:
203
+ # No retention policy means logs are kept indefinitely
204
+ compliance_status = ComplianceStatus.NON_COMPLIANT
205
+ evaluation_reason = f"Log group {log_group_name} has no retention policy (logs kept indefinitely)"
206
+ elif retention_days >= self.min_retention_days:
207
+ compliance_status = ComplianceStatus.COMPLIANT
208
+ evaluation_reason = f"Log group {log_group_name} has {retention_days} days retention (meets minimum {self.min_retention_days} days)"
209
+ else:
210
+ compliance_status = ComplianceStatus.NON_COMPLIANT
211
+ evaluation_reason = f"Log group {log_group_name} has {retention_days} days retention (below minimum {self.min_retention_days} days)"
212
+
213
+ return ComplianceResult(
214
+ resource_id=log_group_name,
215
+ resource_type="AWS::Logs::LogGroup",
216
+ compliance_status=compliance_status,
217
+ evaluation_reason=evaluation_reason,
218
+ config_rule_name=self.rule_name,
219
+ region=region
220
+ )
221
+
222
+ def _get_rule_remediation_steps(self) -> List[str]:
223
+ """Get specific remediation steps for CloudWatch log group retention."""
224
+ return [
225
+ f"Identify CloudWatch log groups without retention policies or with retention below {self.min_retention_days} days",
226
+ "For each log group, set appropriate retention period:",
227
+ " 1. Go to the CloudWatch console",
228
+ " 2. Navigate to Logs > Log groups",
229
+ " 3. Select the log group",
230
+ " 4. Go to Actions > Edit retention setting",
231
+ " 5. Set retention period based on compliance requirements",
232
+ f"Use AWS CLI: aws logs put-retention-policy --log-group-name <name> --retention-in-days {self.min_retention_days}",
233
+ "Consider cost implications of longer retention periods",
234
+ "Set up automated retention policy management for new log groups"
235
+ ]