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,382 @@
1
+ """Control 3.11: Encrypt Sensitive Data at Rest 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 CloudTrailEncryptionEnabledAssessment(BaseConfigRuleAssessment):
15
+ """Assessment for cloud-trail-encryption-enabled Config rule."""
16
+
17
+ def __init__(self):
18
+ """Initialize CloudTrail encryption assessment."""
19
+ super().__init__(
20
+ rule_name="cloud-trail-encryption-enabled",
21
+ control_id="3.11",
22
+ resource_types=["AWS::CloudTrail::Trail"]
23
+ )
24
+
25
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
26
+ """Get all CloudTrail trails."""
27
+ if resource_type != "AWS::CloudTrail::Trail":
28
+ return []
29
+
30
+ try:
31
+ cloudtrail_client = aws_factory.get_client('cloudtrail', region)
32
+
33
+ response = aws_factory.aws_api_call_with_retry(
34
+ lambda: cloudtrail_client.describe_trails()
35
+ )
36
+
37
+ trails = []
38
+ for trail in response.get('trailList', []):
39
+ trails.append({
40
+ 'TrailARN': trail.get('TrailARN'),
41
+ 'Name': trail.get('Name'),
42
+ 'S3BucketName': trail.get('S3BucketName'),
43
+ 'KMSKeyId': trail.get('KMSKeyId'),
44
+ 'IsMultiRegionTrail': trail.get('IsMultiRegionTrail', False),
45
+ 'HomeRegion': trail.get('HomeRegion')
46
+ })
47
+
48
+ logger.debug(f"Found {len(trails)} CloudTrail trails")
49
+ return trails
50
+
51
+ except ClientError as e:
52
+ logger.error(f"Error retrieving CloudTrail trails: {e}")
53
+ raise
54
+ except Exception as e:
55
+ logger.error(f"Unexpected error retrieving CloudTrail trails: {e}")
56
+ raise
57
+
58
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
59
+ """Evaluate if CloudTrail trail has encryption enabled."""
60
+ trail_arn = resource.get('TrailARN', 'unknown')
61
+ trail_name = resource.get('Name', 'unknown')
62
+ kms_key_id = resource.get('KMSKeyId')
63
+
64
+ if kms_key_id:
65
+ compliance_status = ComplianceStatus.COMPLIANT
66
+ evaluation_reason = f"CloudTrail {trail_name} has encryption enabled with KMS key: {kms_key_id}"
67
+ else:
68
+ compliance_status = ComplianceStatus.NON_COMPLIANT
69
+ evaluation_reason = f"CloudTrail {trail_name} does not have encryption enabled"
70
+
71
+ return ComplianceResult(
72
+ resource_id=trail_arn,
73
+ resource_type="AWS::CloudTrail::Trail",
74
+ compliance_status=compliance_status,
75
+ evaluation_reason=evaluation_reason,
76
+ config_rule_name=self.rule_name,
77
+ region=region
78
+ )
79
+
80
+ def _get_rule_remediation_steps(self) -> List[str]:
81
+ """Get specific remediation steps for CloudTrail encryption."""
82
+ return [
83
+ "Identify CloudTrail trails without encryption enabled",
84
+ "For each unencrypted trail:",
85
+ " 1. Create or identify a KMS key for CloudTrail encryption",
86
+ " 2. Update the trail configuration to use the KMS key",
87
+ " 3. Ensure CloudTrail service has permissions to use the key",
88
+ " 4. Test that logs are being encrypted properly",
89
+ " 5. Monitor for any delivery failures",
90
+ "Use AWS CLI: aws cloudtrail put-trail --name <trail> --kms-key-id <key-id>",
91
+ "Ensure KMS key policy allows CloudTrail service access",
92
+ "Consider using separate KMS keys for different environments",
93
+ "Monitor KMS key usage and costs"
94
+ ]
95
+
96
+
97
+ class EFSEncryptedCheckAssessment(BaseConfigRuleAssessment):
98
+ """Assessment for efs-encrypted-check Config rule."""
99
+
100
+ def __init__(self):
101
+ """Initialize EFS encryption assessment."""
102
+ super().__init__(
103
+ rule_name="efs-encrypted-check",
104
+ control_id="3.11",
105
+ resource_types=["AWS::EFS::FileSystem"]
106
+ )
107
+
108
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
109
+ """Get all EFS file systems in the region."""
110
+ if resource_type != "AWS::EFS::FileSystem":
111
+ return []
112
+
113
+ try:
114
+ efs_client = aws_factory.get_client('efs', region)
115
+
116
+ response = aws_factory.aws_api_call_with_retry(
117
+ lambda: efs_client.describe_file_systems()
118
+ )
119
+
120
+ file_systems = []
121
+ for fs in response.get('FileSystems', []):
122
+ file_systems.append({
123
+ 'FileSystemId': fs.get('FileSystemId'),
124
+ 'FileSystemArn': fs.get('FileSystemArn'),
125
+ 'CreationTime': fs.get('CreationTime'),
126
+ 'LifeCycleState': fs.get('LifeCycleState'),
127
+ 'Encrypted': fs.get('Encrypted', False),
128
+ 'KmsKeyId': fs.get('KmsKeyId'),
129
+ 'Name': fs.get('Name', fs.get('FileSystemId'))
130
+ })
131
+
132
+ logger.debug(f"Found {len(file_systems)} EFS file systems in region {region}")
133
+ return file_systems
134
+
135
+ except ClientError as e:
136
+ logger.error(f"Error retrieving EFS file systems in region {region}: {e}")
137
+ raise
138
+ except Exception as e:
139
+ logger.error(f"Unexpected error retrieving EFS file systems in region {region}: {e}")
140
+ raise
141
+
142
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
143
+ """Evaluate if EFS file system has encryption enabled."""
144
+ fs_id = resource.get('FileSystemId', 'unknown')
145
+ fs_arn = resource.get('FileSystemArn', 'unknown')
146
+ encrypted = resource.get('Encrypted', False)
147
+ kms_key_id = resource.get('KmsKeyId')
148
+
149
+ if encrypted:
150
+ compliance_status = ComplianceStatus.COMPLIANT
151
+ evaluation_reason = f"EFS file system {fs_id} has encryption enabled"
152
+ if kms_key_id:
153
+ evaluation_reason += f" with KMS key: {kms_key_id}"
154
+ else:
155
+ compliance_status = ComplianceStatus.NON_COMPLIANT
156
+ evaluation_reason = f"EFS file system {fs_id} does not have encryption enabled"
157
+
158
+ return ComplianceResult(
159
+ resource_id=fs_arn or fs_id,
160
+ resource_type="AWS::EFS::FileSystem",
161
+ compliance_status=compliance_status,
162
+ evaluation_reason=evaluation_reason,
163
+ config_rule_name=self.rule_name,
164
+ region=region
165
+ )
166
+
167
+ def _get_rule_remediation_steps(self) -> List[str]:
168
+ """Get specific remediation steps for EFS encryption."""
169
+ return [
170
+ "Identify EFS file systems without encryption enabled",
171
+ "For each unencrypted file system:",
172
+ " 1. Create a new encrypted EFS file system",
173
+ " 2. Copy data from the unencrypted file system to the encrypted one",
174
+ " 3. Update applications to use the new encrypted file system",
175
+ " 4. Test functionality with the encrypted file system",
176
+ " 5. Delete the old unencrypted file system",
177
+ "Note: Encryption cannot be enabled on existing EFS file systems",
178
+ "Use AWS CLI: aws efs create-file-system --encrypted --kms-key-id <key-id>",
179
+ "Use AWS DataSync or rsync to migrate data between file systems",
180
+ "Plan for downtime during the migration process"
181
+ ]
182
+
183
+
184
+ class EC2EBSEncryptionByDefaultAssessment(BaseConfigRuleAssessment):
185
+ """Assessment for ec2-ebs-encryption-by-default Config rule."""
186
+
187
+ def __init__(self):
188
+ """Initialize EC2 EBS encryption by default assessment."""
189
+ super().__init__(
190
+ rule_name="ec2-ebs-encryption-by-default",
191
+ control_id="3.11",
192
+ resource_types=["AWS::::Account"]
193
+ )
194
+
195
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
196
+ """Get account information for EBS encryption by default check."""
197
+ if resource_type != "AWS::::Account":
198
+ return []
199
+
200
+ try:
201
+ # Get account ID
202
+ account_info = aws_factory.get_account_info()
203
+ account_id = account_info.get('account_id', 'unknown')
204
+
205
+ return [{
206
+ 'AccountId': account_id,
207
+ 'Region': region,
208
+ 'Type': 'Account'
209
+ }]
210
+
211
+ except Exception as e:
212
+ logger.error(f"Error getting account information: {e}")
213
+ raise
214
+
215
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
216
+ """Evaluate if EBS encryption by default is enabled."""
217
+ account_id = resource.get('AccountId', 'unknown')
218
+
219
+ try:
220
+ ec2_client = aws_factory.get_client('ec2', region)
221
+
222
+ # Check if EBS encryption by default is enabled
223
+ response = aws_factory.aws_api_call_with_retry(
224
+ lambda: ec2_client.get_ebs_encryption_by_default()
225
+ )
226
+
227
+ encryption_by_default = response.get('EbsEncryptionByDefault', False)
228
+
229
+ if encryption_by_default:
230
+ # Get the default KMS key
231
+ try:
232
+ key_response = aws_factory.aws_api_call_with_retry(
233
+ lambda: ec2_client.get_ebs_default_kms_key_id()
234
+ )
235
+ default_key = key_response.get('KmsKeyId', 'AWS managed key')
236
+
237
+ compliance_status = ComplianceStatus.COMPLIANT
238
+ evaluation_reason = f"Account {account_id} has EBS encryption by default enabled in region {region} with key: {default_key}"
239
+ except ClientError:
240
+ compliance_status = ComplianceStatus.COMPLIANT
241
+ evaluation_reason = f"Account {account_id} has EBS encryption by default enabled in region {region}"
242
+ else:
243
+ compliance_status = ComplianceStatus.NON_COMPLIANT
244
+ evaluation_reason = f"Account {account_id} does not have EBS encryption by default enabled in region {region}"
245
+
246
+ except ClientError as e:
247
+ error_code = e.response.get('Error', {}).get('Code', '')
248
+ if error_code in ['AccessDenied', 'UnauthorizedOperation']:
249
+ compliance_status = ComplianceStatus.ERROR
250
+ evaluation_reason = f"Insufficient permissions to check EBS encryption by default for account {account_id} in region {region}"
251
+ else:
252
+ compliance_status = ComplianceStatus.ERROR
253
+ evaluation_reason = f"Error checking EBS encryption by default for account {account_id} in region {region}: {str(e)}"
254
+ except Exception as e:
255
+ compliance_status = ComplianceStatus.ERROR
256
+ evaluation_reason = f"Unexpected error checking EBS encryption by default for account {account_id} in region {region}: {str(e)}"
257
+
258
+ return ComplianceResult(
259
+ resource_id=f"{account_id}-{region}",
260
+ resource_type="AWS::::Account",
261
+ compliance_status=compliance_status,
262
+ evaluation_reason=evaluation_reason,
263
+ config_rule_name=self.rule_name,
264
+ region=region
265
+ )
266
+
267
+ def _get_rule_remediation_steps(self) -> List[str]:
268
+ """Get specific remediation steps for EBS encryption by default."""
269
+ return [
270
+ "Enable EBS encryption by default in each AWS region",
271
+ "For each region where it's not enabled:",
272
+ " 1. Enable EBS encryption by default",
273
+ " 2. Optionally set a customer-managed KMS key as default",
274
+ " 3. Verify that new volumes are encrypted by default",
275
+ " 4. Update any automation/scripts that create volumes",
276
+ "Use AWS CLI: aws ec2 enable-ebs-encryption-by-default --region <region>",
277
+ "Use AWS CLI: aws ec2 modify-ebs-default-kms-key-id --kms-key-id <key-id> --region <region>",
278
+ "Note: This only affects new volumes; existing volumes remain unchanged",
279
+ "Consider encrypting existing unencrypted volumes as needed"
280
+ ]
281
+
282
+
283
+ class RDSSnapshotEncryptedAssessment(BaseConfigRuleAssessment):
284
+ """Assessment for rds-snapshot-encrypted Config rule."""
285
+
286
+ def __init__(self):
287
+ """Initialize RDS snapshot encryption assessment."""
288
+ super().__init__(
289
+ rule_name="rds-snapshot-encrypted",
290
+ control_id="3.11",
291
+ resource_types=["AWS::RDS::DBSnapshot", "AWS::RDS::DBClusterSnapshot"]
292
+ )
293
+
294
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
295
+ """Get all RDS snapshots in the region."""
296
+ resources = []
297
+
298
+ try:
299
+ rds_client = aws_factory.get_client('rds', region)
300
+
301
+ if resource_type == "AWS::RDS::DBSnapshot":
302
+ # Get DB snapshots
303
+ response = aws_factory.aws_api_call_with_retry(
304
+ lambda: rds_client.describe_db_snapshots(SnapshotType='manual')
305
+ )
306
+
307
+ for snapshot in response.get('DBSnapshots', []):
308
+ resources.append({
309
+ 'SnapshotId': snapshot.get('DBSnapshotIdentifier'),
310
+ 'SnapshotArn': snapshot.get('DBSnapshotArn'),
311
+ 'Type': 'DBSnapshot',
312
+ 'Encrypted': snapshot.get('Encrypted', False),
313
+ 'KmsKeyId': snapshot.get('KmsKeyId'),
314
+ 'Status': snapshot.get('Status')
315
+ })
316
+
317
+ elif resource_type == "AWS::RDS::DBClusterSnapshot":
318
+ # Get DB cluster snapshots
319
+ response = aws_factory.aws_api_call_with_retry(
320
+ lambda: rds_client.describe_db_cluster_snapshots(SnapshotType='manual')
321
+ )
322
+
323
+ for snapshot in response.get('DBClusterSnapshots', []):
324
+ resources.append({
325
+ 'SnapshotId': snapshot.get('DBClusterSnapshotIdentifier'),
326
+ 'SnapshotArn': snapshot.get('DBClusterSnapshotArn'),
327
+ 'Type': 'DBClusterSnapshot',
328
+ 'Encrypted': snapshot.get('StorageEncrypted', False),
329
+ 'KmsKeyId': snapshot.get('KmsKeyId'),
330
+ 'Status': snapshot.get('Status')
331
+ })
332
+
333
+ logger.debug(f"Found {len(resources)} RDS {resource_type.split('::')[-1]}s in region {region}")
334
+ return resources
335
+
336
+ except ClientError as e:
337
+ logger.error(f"Error retrieving RDS {resource_type} in region {region}: {e}")
338
+ raise
339
+ except Exception as e:
340
+ logger.error(f"Unexpected error retrieving RDS {resource_type} in region {region}: {e}")
341
+ raise
342
+
343
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
344
+ """Evaluate if RDS snapshot is encrypted."""
345
+ snapshot_id = resource.get('SnapshotId', 'unknown')
346
+ snapshot_arn = resource.get('SnapshotArn', 'unknown')
347
+ snapshot_type = resource.get('Type', 'unknown')
348
+ encrypted = resource.get('Encrypted', False)
349
+ kms_key_id = resource.get('KmsKeyId')
350
+
351
+ if encrypted:
352
+ compliance_status = ComplianceStatus.COMPLIANT
353
+ evaluation_reason = f"RDS {snapshot_type} {snapshot_id} is encrypted"
354
+ if kms_key_id:
355
+ evaluation_reason += f" with KMS key: {kms_key_id}"
356
+ else:
357
+ compliance_status = ComplianceStatus.NON_COMPLIANT
358
+ evaluation_reason = f"RDS {snapshot_type} {snapshot_id} is not encrypted"
359
+
360
+ return ComplianceResult(
361
+ resource_id=snapshot_arn or snapshot_id,
362
+ resource_type=f"AWS::RDS::{snapshot_type}",
363
+ compliance_status=compliance_status,
364
+ evaluation_reason=evaluation_reason,
365
+ config_rule_name=self.rule_name,
366
+ region=region
367
+ )
368
+
369
+ def _get_rule_remediation_steps(self) -> List[str]:
370
+ """Get specific remediation steps for RDS snapshot encryption."""
371
+ return [
372
+ "Identify unencrypted RDS snapshots",
373
+ "For each unencrypted snapshot:",
374
+ " 1. Create an encrypted copy of the snapshot",
375
+ " 2. Verify the encrypted copy is complete and functional",
376
+ " 3. Update any references to use the encrypted snapshot",
377
+ " 4. Delete the unencrypted snapshot",
378
+ "Use AWS CLI: aws rds copy-db-snapshot --source-db-snapshot-identifier <source> --target-db-snapshot-identifier <target> --kms-key-id <key-id>",
379
+ "Use AWS CLI: aws rds copy-db-cluster-snapshot --source-db-cluster-snapshot-identifier <source> --target-db-cluster-snapshot-identifier <target> --kms-key-id <key-id>",
380
+ "Ensure source databases have encryption enabled to prevent future unencrypted snapshots",
381
+ "Implement policies to automatically encrypt snapshots"
382
+ ]