aws-cis-controls-assessment 1.1.3__py3-none-any.whl → 1.2.0__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 (40) hide show
  1. aws_cis_assessment/__init__.py +4 -4
  2. aws_cis_assessment/config/rules/cis_controls_ig1.yaml +365 -2
  3. aws_cis_assessment/controls/ig1/control_access_analyzer.py +198 -0
  4. aws_cis_assessment/controls/ig1/control_access_asset_mgmt.py +360 -0
  5. aws_cis_assessment/controls/ig1/control_access_control.py +323 -0
  6. aws_cis_assessment/controls/ig1/control_backup_security.py +579 -0
  7. aws_cis_assessment/controls/ig1/control_cloudfront_logging.py +215 -0
  8. aws_cis_assessment/controls/ig1/control_configuration_mgmt.py +407 -0
  9. aws_cis_assessment/controls/ig1/control_data_classification.py +255 -0
  10. aws_cis_assessment/controls/ig1/control_dynamodb_encryption.py +279 -0
  11. aws_cis_assessment/controls/ig1/control_ebs_encryption.py +177 -0
  12. aws_cis_assessment/controls/ig1/control_efs_encryption.py +243 -0
  13. aws_cis_assessment/controls/ig1/control_elb_logging.py +195 -0
  14. aws_cis_assessment/controls/ig1/control_guardduty.py +156 -0
  15. aws_cis_assessment/controls/ig1/control_inspector.py +184 -0
  16. aws_cis_assessment/controls/ig1/control_inventory.py +511 -0
  17. aws_cis_assessment/controls/ig1/control_macie.py +165 -0
  18. aws_cis_assessment/controls/ig1/control_messaging_encryption.py +419 -0
  19. aws_cis_assessment/controls/ig1/control_mfa.py +485 -0
  20. aws_cis_assessment/controls/ig1/control_network_security.py +194 -619
  21. aws_cis_assessment/controls/ig1/control_patch_management.py +626 -0
  22. aws_cis_assessment/controls/ig1/control_rds_encryption.py +228 -0
  23. aws_cis_assessment/controls/ig1/control_s3_encryption.py +383 -0
  24. aws_cis_assessment/controls/ig1/control_tls_ssl.py +556 -0
  25. aws_cis_assessment/controls/ig1/control_version_mgmt.py +329 -0
  26. aws_cis_assessment/controls/ig1/control_vpc_flow_logs.py +205 -0
  27. aws_cis_assessment/controls/ig1/control_waf_logging.py +226 -0
  28. aws_cis_assessment/core/models.py +20 -1
  29. aws_cis_assessment/core/scoring_engine.py +98 -1
  30. aws_cis_assessment/reporters/base_reporter.py +31 -1
  31. aws_cis_assessment/reporters/html_reporter.py +172 -11
  32. aws_cis_controls_assessment-1.2.0.dist-info/METADATA +320 -0
  33. {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/RECORD +39 -15
  34. docs/developer-guide.md +204 -5
  35. docs/user-guide.md +137 -4
  36. aws_cis_controls_assessment-1.1.3.dist-info/METADATA +0 -404
  37. {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/WHEEL +0 -0
  38. {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/entry_points.txt +0 -0
  39. {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/licenses/LICENSE +0 -0
  40. {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,511 @@
1
+ """
2
+ CIS Control 1.1 - Inventory Management
3
+ Ensures proper asset inventory tracking across AWS resources.
4
+ """
5
+
6
+ import logging
7
+ from typing import List, Dict, Any
8
+ from botocore.exceptions import ClientError
9
+
10
+ from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
11
+ from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
12
+ from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class SSMInventoryEnabledAssessment(BaseConfigRuleAssessment):
18
+ """
19
+ CIS Control 1.1 - Establish and Maintain Detailed Enterprise Asset Inventory
20
+ AWS Config Rule: ssm-inventory-enabled
21
+
22
+ Ensures AWS Systems Manager Inventory is enabled for asset tracking.
23
+ """
24
+
25
+ def __init__(self):
26
+ super().__init__(
27
+ rule_name="ssm-inventory-enabled",
28
+ control_id="1.1",
29
+ resource_types=["AWS::::Account"]
30
+ )
31
+
32
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
33
+ """Check if SSM Inventory is configured."""
34
+ if resource_type != "AWS::::Account":
35
+ return []
36
+
37
+ try:
38
+ ssm_client = aws_factory.get_client('ssm', region)
39
+
40
+ # Check for inventory associations
41
+ response = ssm_client.list_associations(
42
+ AssociationFilterList=[
43
+ {'key': 'AssociationName', 'value': 'AWS-GatherSoftwareInventory'}
44
+ ]
45
+ )
46
+
47
+ associations = response.get('Associations', [])
48
+ inventory_enabled = len(associations) > 0
49
+
50
+ return [{
51
+ 'AccountId': region,
52
+ 'InventoryEnabled': inventory_enabled,
53
+ 'AssociationCount': len(associations)
54
+ }]
55
+
56
+ except ClientError as e:
57
+ logger.error(f"Error checking SSM Inventory in {region}: {e}")
58
+ return []
59
+
60
+ def _evaluate_resource_compliance(
61
+ self,
62
+ resource: Dict[str, Any],
63
+ aws_factory: AWSClientFactory,
64
+ region: str
65
+ ) -> ComplianceResult:
66
+ """Evaluate if SSM Inventory is enabled."""
67
+ inventory_enabled = resource.get('InventoryEnabled', False)
68
+ association_count = resource.get('AssociationCount', 0)
69
+
70
+ if inventory_enabled:
71
+ evaluation_reason = f"SSM Inventory is enabled with {association_count} association(s)"
72
+ compliance_status = ComplianceStatus.COMPLIANT
73
+ else:
74
+ evaluation_reason = "SSM Inventory is not enabled"
75
+ compliance_status = ComplianceStatus.NON_COMPLIANT
76
+
77
+ return ComplianceResult(
78
+ resource_id=f"account-{region}",
79
+ resource_type="AWS::::Account",
80
+ compliance_status=compliance_status,
81
+ evaluation_reason=evaluation_reason,
82
+ config_rule_name=self.rule_name,
83
+ region=region
84
+ )
85
+
86
+ def _get_rule_remediation_steps(self) -> List[str]:
87
+ """Get remediation steps for enabling SSM Inventory."""
88
+ return [
89
+ "1. Enable SSM Inventory using AWS CLI:",
90
+ " aws ssm create-association \\",
91
+ " --name AWS-GatherSoftwareInventory \\",
92
+ " --targets Key=InstanceIds,Values=* \\",
93
+ " --schedule-expression 'rate(30 minutes)'",
94
+ "",
95
+ "2. Console method:",
96
+ " - Navigate to Systems Manager",
97
+ " - Click 'Inventory' in left menu",
98
+ " - Click 'Setup Inventory'",
99
+ " - Configure targets and schedule",
100
+ " - Click 'Setup Inventory'",
101
+ "",
102
+ "Priority: MEDIUM - Important for asset tracking",
103
+ "Effort: Low - Quick setup",
104
+ "",
105
+ "AWS Documentation:",
106
+ "https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-inventory-configuring.html"
107
+ ]
108
+
109
+
110
+ class ConfigEnabledAllRegionsAssessment(BaseConfigRuleAssessment):
111
+ """
112
+ CIS Control 1.1 - Establish and Maintain Detailed Enterprise Asset Inventory
113
+ AWS Config Rule: config-enabled-all-regions
114
+
115
+ Ensures AWS Config is enabled in all regions for comprehensive resource tracking.
116
+ """
117
+
118
+ def __init__(self):
119
+ super().__init__(
120
+ rule_name="config-enabled-all-regions",
121
+ control_id="1.1",
122
+ resource_types=["AWS::::Account"]
123
+ )
124
+
125
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
126
+ """Check AWS Config status across all regions."""
127
+ if resource_type != "AWS::::Account":
128
+ return []
129
+
130
+ try:
131
+ ec2_client = aws_factory.get_client('ec2', 'us-east-1')
132
+ all_regions = [r['RegionName'] for r in ec2_client.describe_regions()['Regions']]
133
+
134
+ enabled_regions = []
135
+ disabled_regions = []
136
+
137
+ for check_region in all_regions:
138
+ try:
139
+ config_client = aws_factory.get_client('config', check_region)
140
+ recorders = config_client.describe_configuration_recorders()
141
+
142
+ if recorders.get('ConfigurationRecorders'):
143
+ # Check if recorder is recording
144
+ status = config_client.describe_configuration_recorder_status()
145
+ if status.get('ConfigurationRecordersStatus'):
146
+ if status['ConfigurationRecordersStatus'][0].get('recording'):
147
+ enabled_regions.append(check_region)
148
+ else:
149
+ disabled_regions.append(check_region)
150
+ else:
151
+ disabled_regions.append(check_region)
152
+ else:
153
+ disabled_regions.append(check_region)
154
+ except ClientError:
155
+ disabled_regions.append(check_region)
156
+
157
+ return [{
158
+ 'AccountId': 'global',
159
+ 'EnabledRegions': enabled_regions,
160
+ 'DisabledRegions': disabled_regions,
161
+ 'TotalRegions': len(all_regions)
162
+ }]
163
+
164
+ except ClientError as e:
165
+ logger.error(f"Error checking Config status: {e}")
166
+ return []
167
+
168
+ def _evaluate_resource_compliance(
169
+ self,
170
+ resource: Dict[str, Any],
171
+ aws_factory: AWSClientFactory,
172
+ region: str
173
+ ) -> ComplianceResult:
174
+ """Evaluate if Config is enabled in all regions."""
175
+ enabled_regions = resource.get('EnabledRegions', [])
176
+ disabled_regions = resource.get('DisabledRegions', [])
177
+ total_regions = resource.get('TotalRegions', 0)
178
+
179
+ if not disabled_regions:
180
+ evaluation_reason = f"AWS Config is enabled in all {total_regions} regions"
181
+ compliance_status = ComplianceStatus.COMPLIANT
182
+ else:
183
+ evaluation_reason = (
184
+ f"AWS Config is disabled in {len(disabled_regions)} region(s): "
185
+ f"{', '.join(disabled_regions[:5])}"
186
+ )
187
+ compliance_status = ComplianceStatus.NON_COMPLIANT
188
+
189
+ return ComplianceResult(
190
+ resource_id="account-config-status",
191
+ resource_type="AWS::::Account",
192
+ compliance_status=compliance_status,
193
+ evaluation_reason=evaluation_reason,
194
+ config_rule_name=self.rule_name,
195
+ region=region
196
+ )
197
+
198
+ def _get_rule_remediation_steps(self) -> List[str]:
199
+ """Get remediation steps for enabling Config in all regions."""
200
+ return [
201
+ "1. Enable AWS Config in a region:",
202
+ " aws configservice put-configuration-recorder \\",
203
+ " --configuration-recorder name=default,roleARN=<role-arn> \\",
204
+ " --recording-group allSupported=true,includeGlobalResourceTypes=true",
205
+ "",
206
+ " aws configservice put-delivery-channel \\",
207
+ " --delivery-channel name=default,s3BucketName=<bucket-name>",
208
+ "",
209
+ " aws configservice start-configuration-recorder \\",
210
+ " --configuration-recorder-name default",
211
+ "",
212
+ "2. Enable in all regions (script):",
213
+ " for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do",
214
+ " aws configservice put-configuration-recorder --region $region ...",
215
+ " done",
216
+ "",
217
+ "Priority: HIGH - Essential for compliance tracking",
218
+ "Effort: Medium - Requires setup in each region",
219
+ "",
220
+ "AWS Documentation:",
221
+ "https://docs.aws.amazon.com/config/latest/developerguide/gs-console.html"
222
+ ]
223
+
224
+
225
+ class AMIInventoryTrackingAssessment(BaseConfigRuleAssessment):
226
+ """
227
+ CIS Control 1.1 - Establish and Maintain Detailed Enterprise Asset Inventory
228
+ AWS Config Rule: ami-inventory-tracking
229
+
230
+ Ensures AMIs are properly tagged for inventory tracking.
231
+ """
232
+
233
+ def __init__(self):
234
+ super().__init__(
235
+ rule_name="ami-inventory-tracking",
236
+ control_id="1.1",
237
+ resource_types=["AWS::EC2::Image"]
238
+ )
239
+
240
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
241
+ """Get all AMIs owned by the account."""
242
+ if resource_type != "AWS::EC2::Image":
243
+ return []
244
+
245
+ try:
246
+ ec2_client = aws_factory.get_client('ec2', region)
247
+
248
+ # Get account ID
249
+ sts_client = aws_factory.get_client('sts', region)
250
+ account_id = sts_client.get_caller_identity()['Account']
251
+
252
+ # List AMIs owned by this account
253
+ response = ec2_client.describe_images(Owners=[account_id])
254
+ images = response.get('Images', [])
255
+
256
+ amis = []
257
+ for image in images:
258
+ tags = {tag['Key']: tag['Value'] for tag in image.get('Tags', [])}
259
+
260
+ amis.append({
261
+ 'ImageId': image.get('ImageId'),
262
+ 'Name': image.get('Name', 'unnamed'),
263
+ 'Tags': tags,
264
+ 'HasInventoryTags': bool(tags.get('Environment') or tags.get('Owner') or tags.get('Application'))
265
+ })
266
+
267
+ return amis
268
+
269
+ except ClientError as e:
270
+ logger.error(f"Error retrieving AMIs in {region}: {e}")
271
+ return []
272
+
273
+ def _evaluate_resource_compliance(
274
+ self,
275
+ resource: Dict[str, Any],
276
+ aws_factory: AWSClientFactory,
277
+ region: str
278
+ ) -> ComplianceResult:
279
+ """Evaluate if AMI has proper inventory tags."""
280
+ image_id = resource.get('ImageId', 'unknown')
281
+ name = resource.get('Name', 'unnamed')
282
+ has_inventory_tags = resource.get('HasInventoryTags', False)
283
+
284
+ if has_inventory_tags:
285
+ evaluation_reason = f"AMI '{name}' has proper inventory tags"
286
+ compliance_status = ComplianceStatus.COMPLIANT
287
+ else:
288
+ evaluation_reason = f"AMI '{name}' is missing inventory tags (Environment, Owner, or Application)"
289
+ compliance_status = ComplianceStatus.NON_COMPLIANT
290
+
291
+ return ComplianceResult(
292
+ resource_id=image_id,
293
+ resource_type="AWS::EC2::Image",
294
+ compliance_status=compliance_status,
295
+ evaluation_reason=evaluation_reason,
296
+ config_rule_name=self.rule_name,
297
+ region=region
298
+ )
299
+
300
+ def _get_rule_remediation_steps(self) -> List[str]:
301
+ """Get remediation steps for AMI inventory tagging."""
302
+ return [
303
+ "1. Tag an AMI with inventory information:",
304
+ " aws ec2 create-tags \\",
305
+ " --resources <ami-id> \\",
306
+ " --tags Key=Environment,Value=production \\",
307
+ " Key=Owner,Value=team-name \\",
308
+ " Key=Application,Value=app-name",
309
+ "",
310
+ "2. Tag multiple AMIs (script):",
311
+ " for ami in $(aws ec2 describe-images --owners self --query 'Images[].ImageId' --output text); do",
312
+ " aws ec2 create-tags --resources $ami --tags Key=Environment,Value=production",
313
+ " done",
314
+ "",
315
+ "Priority: MEDIUM - Important for asset management",
316
+ "Effort: Low - Simple tagging operation",
317
+ "",
318
+ "AWS Documentation:",
319
+ "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html"
320
+ ]
321
+
322
+
323
+ class LambdaRuntimeInventoryAssessment(BaseConfigRuleAssessment):
324
+ """
325
+ CIS Control 1.1 - Establish and Maintain Detailed Enterprise Asset Inventory
326
+ AWS Config Rule: lambda-runtime-inventory
327
+
328
+ Tracks Lambda function runtimes for inventory purposes.
329
+ """
330
+
331
+ def __init__(self):
332
+ super().__init__(
333
+ rule_name="lambda-runtime-inventory",
334
+ control_id="1.1",
335
+ resource_types=["AWS::Lambda::Function"]
336
+ )
337
+
338
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
339
+ """Get all Lambda functions and their runtimes."""
340
+ if resource_type != "AWS::Lambda::Function":
341
+ return []
342
+
343
+ try:
344
+ lambda_client = aws_factory.get_client('lambda', region)
345
+ functions = []
346
+
347
+ paginator = lambda_client.get_paginator('list_functions')
348
+ for page in paginator.paginate():
349
+ for func in page.get('Functions', []):
350
+ functions.append({
351
+ 'FunctionName': func.get('FunctionName'),
352
+ 'FunctionArn': func.get('FunctionArn'),
353
+ 'Runtime': func.get('Runtime', 'unknown'),
354
+ 'LastModified': func.get('LastModified')
355
+ })
356
+
357
+ return functions
358
+
359
+ except ClientError as e:
360
+ logger.error(f"Error retrieving Lambda functions in {region}: {e}")
361
+ return []
362
+
363
+ def _evaluate_resource_compliance(
364
+ self,
365
+ resource: Dict[str, Any],
366
+ aws_factory: AWSClientFactory,
367
+ region: str
368
+ ) -> ComplianceResult:
369
+ """Evaluate Lambda function for inventory tracking."""
370
+ function_name = resource.get('FunctionName', 'unknown')
371
+ runtime = resource.get('Runtime', 'unknown')
372
+
373
+ # This is primarily for inventory - all functions are compliant
374
+ evaluation_reason = f"Lambda function '{function_name}' tracked with runtime: {runtime}"
375
+ compliance_status = ComplianceStatus.COMPLIANT
376
+
377
+ return ComplianceResult(
378
+ resource_id=resource.get('FunctionArn', function_name),
379
+ resource_type="AWS::Lambda::Function",
380
+ compliance_status=compliance_status,
381
+ evaluation_reason=evaluation_reason,
382
+ config_rule_name=self.rule_name,
383
+ region=region
384
+ )
385
+
386
+ def _get_rule_remediation_steps(self) -> List[str]:
387
+ """Get remediation steps for Lambda inventory."""
388
+ return [
389
+ "1. List all Lambda functions and runtimes:",
390
+ " aws lambda list-functions \\",
391
+ " --query 'Functions[].{Name:FunctionName,Runtime:Runtime}' \\",
392
+ " --output table",
393
+ "",
394
+ "2. Export to CSV for inventory:",
395
+ " aws lambda list-functions \\",
396
+ " --query 'Functions[].{Name:FunctionName,Runtime:Runtime,Modified:LastModified}' \\",
397
+ " --output json > lambda-inventory.json",
398
+ "",
399
+ "Priority: LOW - Informational inventory",
400
+ "Effort: None - Automated tracking",
401
+ "",
402
+ "AWS Documentation:",
403
+ "https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html"
404
+ ]
405
+
406
+
407
+ class IAMUserInventoryCheckAssessment(BaseConfigRuleAssessment):
408
+ """
409
+ CIS Control 1.1 - Establish and Maintain Detailed Enterprise Asset Inventory
410
+ AWS Config Rule: iam-user-inventory-check
411
+
412
+ Ensures IAM users have proper inventory tags.
413
+ """
414
+
415
+ def __init__(self):
416
+ super().__init__(
417
+ rule_name="iam-user-inventory-check",
418
+ control_id="1.1",
419
+ resource_types=["AWS::IAM::User"]
420
+ )
421
+
422
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
423
+ """Get all IAM users and their tags."""
424
+ if resource_type != "AWS::IAM::User":
425
+ return []
426
+
427
+ # IAM is global, only process in us-east-1
428
+ if region != 'us-east-1':
429
+ return []
430
+
431
+ try:
432
+ iam_client = aws_factory.get_client('iam', region)
433
+ users = []
434
+
435
+ paginator = iam_client.get_paginator('list_users')
436
+ for page in paginator.paginate():
437
+ for user in page.get('Users', []):
438
+ user_name = user.get('UserName')
439
+
440
+ try:
441
+ # Get user tags
442
+ tags_response = iam_client.list_user_tags(UserName=user_name)
443
+ tags = {tag['Key']: tag['Value'] for tag in tags_response.get('Tags', [])}
444
+
445
+ users.append({
446
+ 'UserName': user_name,
447
+ 'Arn': user.get('Arn'),
448
+ 'Tags': tags,
449
+ 'HasInventoryTags': bool(tags.get('Department') or tags.get('Owner') or tags.get('CostCenter'))
450
+ })
451
+ except ClientError:
452
+ users.append({
453
+ 'UserName': user_name,
454
+ 'Arn': user.get('Arn'),
455
+ 'Tags': {},
456
+ 'HasInventoryTags': False
457
+ })
458
+
459
+ return users
460
+
461
+ except ClientError as e:
462
+ logger.error(f"Error retrieving IAM users: {e}")
463
+ return []
464
+
465
+ def _evaluate_resource_compliance(
466
+ self,
467
+ resource: Dict[str, Any],
468
+ aws_factory: AWSClientFactory,
469
+ region: str
470
+ ) -> ComplianceResult:
471
+ """Evaluate if IAM user has proper inventory tags."""
472
+ user_name = resource.get('UserName', 'unknown')
473
+ has_inventory_tags = resource.get('HasInventoryTags', False)
474
+
475
+ if has_inventory_tags:
476
+ evaluation_reason = f"IAM user '{user_name}' has proper inventory tags"
477
+ compliance_status = ComplianceStatus.COMPLIANT
478
+ else:
479
+ evaluation_reason = f"IAM user '{user_name}' is missing inventory tags (Department, Owner, or CostCenter)"
480
+ compliance_status = ComplianceStatus.NON_COMPLIANT
481
+
482
+ return ComplianceResult(
483
+ resource_id=resource.get('Arn', user_name),
484
+ resource_type="AWS::IAM::User",
485
+ compliance_status=compliance_status,
486
+ evaluation_reason=evaluation_reason,
487
+ config_rule_name=self.rule_name,
488
+ region=region
489
+ )
490
+
491
+ def _get_rule_remediation_steps(self) -> List[str]:
492
+ """Get remediation steps for IAM user inventory tagging."""
493
+ return [
494
+ "1. Tag an IAM user with inventory information:",
495
+ " aws iam tag-user \\",
496
+ " --user-name <user-name> \\",
497
+ " --tags Key=Department,Value=engineering \\",
498
+ " Key=Owner,Value=manager-name \\",
499
+ " Key=CostCenter,Value=12345",
500
+ "",
501
+ "2. Tag multiple users (script):",
502
+ " for user in $(aws iam list-users --query 'Users[].UserName' --output text); do",
503
+ " aws iam tag-user --user-name $user --tags Key=Department,Value=engineering",
504
+ " done",
505
+ "",
506
+ "Priority: MEDIUM - Important for user management",
507
+ "Effort: Low - Simple tagging operation",
508
+ "",
509
+ "AWS Documentation:",
510
+ "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_tags.html"
511
+ ]
@@ -0,0 +1,165 @@
1
+ """
2
+ CIS Control 3.1 - Amazon Macie Data Protection
3
+ Ensures Amazon Macie is enabled for sensitive data discovery.
4
+ """
5
+
6
+ import logging
7
+ from typing import List, Dict, Any
8
+ from botocore.exceptions import ClientError
9
+
10
+ from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
11
+ from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
12
+ from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class MacieEnabledAssessment(BaseConfigRuleAssessment):
18
+ """
19
+ CIS Control 3.1 - Establish and Maintain a Data Management Process
20
+ AWS Config Rule: macie-enabled
21
+
22
+ Ensures Amazon Macie is enabled for automated sensitive data discovery.
23
+ Macie uses machine learning to discover, classify, and protect sensitive
24
+ data in S3 buckets, including PII, financial data, and credentials.
25
+ """
26
+
27
+ def __init__(self):
28
+ super().__init__(
29
+ rule_name="macie-enabled",
30
+ control_id="3.1",
31
+ resource_types=["AWS::Macie::Session"]
32
+ )
33
+
34
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
35
+ """Get Amazon Macie configuration for the region."""
36
+ if resource_type != "AWS::Macie::Session":
37
+ return []
38
+
39
+ try:
40
+ macie_client = aws_factory.get_client('macie2', region)
41
+
42
+ # Check if Macie is enabled by getting session status
43
+ try:
44
+ session_response = macie_client.get_macie_session()
45
+
46
+ status = session_response.get('status', 'DISABLED')
47
+ finding_publishing_frequency = session_response.get('findingPublishingFrequency', 'UNKNOWN')
48
+
49
+ return [{
50
+ 'MacieSessionId': f"macie-{region}",
51
+ 'Status': status,
52
+ 'Region': region,
53
+ 'AccountId': aws_factory.get_account_info().get('account_id', 'unknown'),
54
+ 'FindingPublishingFrequency': finding_publishing_frequency,
55
+ 'ServiceRole': session_response.get('serviceRole', ''),
56
+ 'CreatedAt': session_response.get('createdAt', '')
57
+ }]
58
+
59
+ except ClientError as e:
60
+ error_code = e.response.get('Error', {}).get('Code', '')
61
+ if error_code in ['ResourceNotFoundException', 'AccessDeniedException']:
62
+ # Macie not enabled or no access
63
+ return [{
64
+ 'MacieSessionId': 'none',
65
+ 'Status': 'DISABLED',
66
+ 'Region': region,
67
+ 'AccountId': aws_factory.get_account_info().get('account_id', 'unknown'),
68
+ 'FindingPublishingFrequency': 'UNKNOWN'
69
+ }]
70
+ raise
71
+
72
+ except ClientError as e:
73
+ error_code = e.response.get('Error', {}).get('Code', '')
74
+ if error_code == 'AccessDeniedException':
75
+ logger.warning(f"Access denied to Macie in {region}")
76
+ else:
77
+ logger.error(f"Error checking Macie status in {region}: {e}")
78
+ return []
79
+
80
+ def _evaluate_resource_compliance(
81
+ self,
82
+ resource: Dict[str, Any],
83
+ aws_factory: AWSClientFactory,
84
+ region: str
85
+ ) -> ComplianceResult:
86
+ """Evaluate if Amazon Macie is enabled."""
87
+ macie_id = resource.get('MacieSessionId', 'none')
88
+ status = resource.get('Status', 'DISABLED')
89
+ finding_frequency = resource.get('FindingPublishingFrequency', 'UNKNOWN')
90
+
91
+ # Check if Macie is enabled
92
+ is_compliant = status == 'ENABLED'
93
+
94
+ if is_compliant:
95
+ evaluation_reason = (
96
+ f"Amazon Macie is enabled in {region}. "
97
+ f"Finding publishing frequency: {finding_frequency}"
98
+ )
99
+ compliance_status = ComplianceStatus.COMPLIANT
100
+ else:
101
+ if macie_id == 'none' or status == 'DISABLED':
102
+ evaluation_reason = f"Amazon Macie is not enabled in {region}."
103
+ elif status == 'PAUSED':
104
+ evaluation_reason = f"Amazon Macie is paused in {region}."
105
+ else:
106
+ evaluation_reason = f"Amazon Macie status is {status} in {region}."
107
+ compliance_status = ComplianceStatus.NON_COMPLIANT
108
+
109
+ return ComplianceResult(
110
+ resource_id=macie_id,
111
+ resource_type="AWS::Macie::Session",
112
+ compliance_status=compliance_status,
113
+ evaluation_reason=evaluation_reason,
114
+ config_rule_name=self.rule_name,
115
+ region=region
116
+ )
117
+
118
+ def _get_rule_remediation_steps(self) -> List[str]:
119
+ """Get remediation steps for enabling Amazon Macie."""
120
+ return [
121
+ "1. Enable Amazon Macie in the AWS Console:",
122
+ " - Navigate to Amazon Macie service",
123
+ " - Click 'Get Started' or 'Enable Macie'",
124
+ " - Review service permissions and pricing",
125
+ " - Click 'Enable Macie'",
126
+ "",
127
+ "2. Enable Macie using AWS CLI:",
128
+ " aws macie2 enable-macie \\",
129
+ " --finding-publishing-frequency FIFTEEN_MINUTES \\",
130
+ " --status ENABLED \\",
131
+ " --region <region>",
132
+ "",
133
+ "3. Configure S3 bucket discovery:",
134
+ " - Macie automatically discovers all S3 buckets",
135
+ " - Review bucket inventory in Macie console",
136
+ " - Identify buckets containing sensitive data",
137
+ "",
138
+ "4. Create sensitive data discovery jobs:",
139
+ " - Create classification jobs for high-priority buckets",
140
+ " - Schedule recurring jobs for continuous monitoring",
141
+ " - Use managed data identifiers (PII, credentials, financial data)",
142
+ " - Create custom data identifiers for organization-specific patterns",
143
+ "",
144
+ "5. Configure findings and alerts:",
145
+ " - Review Macie findings in the console",
146
+ " - Create EventBridge rules to route findings to SNS/Slack",
147
+ " - Integrate with Security Hub for centralized findings",
148
+ " - Set up automated remediation for critical findings",
149
+ "",
150
+ "6. Review and act on findings:",
151
+ " - Critical findings: Immediate action required",
152
+ " - High findings: Review within 24 hours",
153
+ " - Medium/Low findings: Review weekly",
154
+ "",
155
+ "7. Optimize costs:",
156
+ " - Macie charges per GB scanned and per bucket monitored",
157
+ " - Focus discovery jobs on high-risk buckets",
158
+ " - Use sampling for large datasets",
159
+ "",
160
+ "Priority: HIGH - Sensitive data discovery is critical for compliance",
161
+ "Effort: Medium - Initial setup is quick, ongoing effort for job configuration",
162
+ "",
163
+ "AWS Documentation:",
164
+ "https://docs.aws.amazon.com/macie/latest/user/what-is-macie.html"
165
+ ]