aws-cis-controls-assessment 1.1.4__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 +163 -0
  32. aws_cis_controls_assessment-1.2.0.dist-info/METADATA +320 -0
  33. {aws_cis_controls_assessment-1.1.4.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.4.dist-info/METADATA +0 -404
  37. {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/WHEEL +0 -0
  38. {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/entry_points.txt +0 -0
  39. {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/licenses/LICENSE +0 -0
  40. {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,579 @@
1
+ """
2
+ CIS Control 11.3, 11.4 - Backup Security
3
+ Ensures proper backup security and protection measures.
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 BackupVaultEncryptionEnabledAssessment(BaseConfigRuleAssessment):
18
+ """
19
+ CIS Control 11.3 - Protect Recovery Data
20
+ AWS Config Rule: backup-vault-encryption-enabled
21
+
22
+ Ensures AWS Backup vaults are encrypted with KMS.
23
+ """
24
+
25
+ def __init__(self):
26
+ super().__init__(
27
+ rule_name="backup-vault-encryption-enabled",
28
+ control_id="11.3",
29
+ resource_types=["AWS::Backup::BackupVault"]
30
+ )
31
+
32
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
33
+ """Get backup vaults and check encryption."""
34
+ if resource_type != "AWS::Backup::BackupVault":
35
+ return []
36
+
37
+ try:
38
+ backup_client = aws_factory.get_client('backup', region)
39
+
40
+ response = backup_client.list_backup_vaults()
41
+ vaults = []
42
+
43
+ for vault in response.get('BackupVaultList', []):
44
+ vault_name = vault.get('BackupVaultName')
45
+ encryption_key_arn = vault.get('EncryptionKeyArn')
46
+
47
+ # Check if encrypted with KMS
48
+ is_encrypted = bool(encryption_key_arn)
49
+
50
+ vaults.append({
51
+ 'VaultName': vault_name,
52
+ 'VaultArn': vault.get('BackupVaultArn'),
53
+ 'EncryptionKeyArn': encryption_key_arn,
54
+ 'IsEncrypted': is_encrypted
55
+ })
56
+
57
+ return vaults
58
+
59
+ except ClientError as e:
60
+ logger.error(f"Error retrieving backup vaults in {region}: {e}")
61
+ return []
62
+
63
+ def _evaluate_resource_compliance(
64
+ self,
65
+ resource: Dict[str, Any],
66
+ aws_factory: AWSClientFactory,
67
+ region: str
68
+ ) -> ComplianceResult:
69
+ """Evaluate if backup vault is encrypted."""
70
+ vault_name = resource.get('VaultName', 'unknown')
71
+ is_encrypted = resource.get('IsEncrypted', False)
72
+ encryption_key = resource.get('EncryptionKeyArn', '')
73
+
74
+ if is_encrypted:
75
+ evaluation_reason = f"Backup vault '{vault_name}' is encrypted with KMS key: {encryption_key}"
76
+ compliance_status = ComplianceStatus.COMPLIANT
77
+ else:
78
+ evaluation_reason = f"Backup vault '{vault_name}' is not encrypted with KMS"
79
+ compliance_status = ComplianceStatus.NON_COMPLIANT
80
+
81
+ return ComplianceResult(
82
+ resource_id=resource.get('VaultArn', vault_name),
83
+ resource_type="AWS::Backup::BackupVault",
84
+ compliance_status=compliance_status,
85
+ evaluation_reason=evaluation_reason,
86
+ config_rule_name=self.rule_name,
87
+ region=region
88
+ )
89
+
90
+ def _get_rule_remediation_steps(self) -> List[str]:
91
+ """Get remediation steps for backup vault encryption."""
92
+ return [
93
+ "1. Create encrypted backup vault:",
94
+ " aws backup create-backup-vault \\",
95
+ " --backup-vault-name encrypted-vault \\",
96
+ " --encryption-key-arn <kms-key-arn>",
97
+ "",
98
+ "2. Migrate backups to encrypted vault:",
99
+ " - Create new encrypted vault",
100
+ " - Update backup plans to use new vault",
101
+ " - Copy existing backups to new vault",
102
+ " - Delete old unencrypted vault",
103
+ "",
104
+ "3. Console method:",
105
+ " - Navigate to AWS Backup",
106
+ " - Click 'Backup vaults'",
107
+ " - Click 'Create backup vault'",
108
+ " - Enter vault name",
109
+ " - Select KMS key for encryption",
110
+ " - Click 'Create backup vault'",
111
+ "",
112
+ "Priority: HIGH - Protects backup data",
113
+ "Effort: Low - Simple vault creation",
114
+ "",
115
+ "AWS Documentation:",
116
+ "https://docs.aws.amazon.com/aws-backup/latest/devguide/vaults.html"
117
+ ]
118
+
119
+
120
+ class BackupCrossRegionCopyEnabledAssessment(BaseConfigRuleAssessment):
121
+ """
122
+ CIS Control 11.4 - Establish and Maintain an Isolated Instance of Recovery Data
123
+ AWS Config Rule: backup-cross-region-copy-enabled
124
+
125
+ Ensures backup plans include cross-region copy for disaster recovery.
126
+ """
127
+
128
+ def __init__(self):
129
+ super().__init__(
130
+ rule_name="backup-cross-region-copy-enabled",
131
+ control_id="11.4",
132
+ resource_types=["AWS::Backup::BackupPlan"]
133
+ )
134
+
135
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
136
+ """Get backup plans and check for cross-region copy."""
137
+ if resource_type != "AWS::Backup::BackupPlan":
138
+ return []
139
+
140
+ try:
141
+ backup_client = aws_factory.get_client('backup', region)
142
+
143
+ response = backup_client.list_backup_plans()
144
+ plans = []
145
+
146
+ for plan_summary in response.get('BackupPlansList', []):
147
+ plan_id = plan_summary.get('BackupPlanId')
148
+ plan_name = plan_summary.get('BackupPlanName')
149
+
150
+ try:
151
+ # Get plan details
152
+ plan_response = backup_client.get_backup_plan(BackupPlanId=plan_id)
153
+ plan = plan_response.get('BackupPlan', {})
154
+
155
+ # Check if any rule has cross-region copy
156
+ has_cross_region = False
157
+ for rule in plan.get('Rules', []):
158
+ copy_actions = rule.get('CopyActions', [])
159
+ if copy_actions:
160
+ has_cross_region = True
161
+ break
162
+
163
+ plans.append({
164
+ 'PlanId': plan_id,
165
+ 'PlanName': plan_name,
166
+ 'PlanArn': plan_summary.get('BackupPlanArn'),
167
+ 'HasCrossRegionCopy': has_cross_region
168
+ })
169
+
170
+ except ClientError:
171
+ continue
172
+
173
+ return plans
174
+
175
+ except ClientError as e:
176
+ logger.error(f"Error retrieving backup plans in {region}: {e}")
177
+ return []
178
+
179
+ def _evaluate_resource_compliance(
180
+ self,
181
+ resource: Dict[str, Any],
182
+ aws_factory: AWSClientFactory,
183
+ region: str
184
+ ) -> ComplianceResult:
185
+ """Evaluate if backup plan has cross-region copy."""
186
+ plan_name = resource.get('PlanName', 'unknown')
187
+ has_cross_region = resource.get('HasCrossRegionCopy', False)
188
+
189
+ if has_cross_region:
190
+ evaluation_reason = f"Backup plan '{plan_name}' includes cross-region copy"
191
+ compliance_status = ComplianceStatus.COMPLIANT
192
+ else:
193
+ evaluation_reason = f"Backup plan '{plan_name}' does not include cross-region copy"
194
+ compliance_status = ComplianceStatus.NON_COMPLIANT
195
+
196
+ return ComplianceResult(
197
+ resource_id=resource.get('PlanArn', plan_name),
198
+ resource_type="AWS::Backup::BackupPlan",
199
+ compliance_status=compliance_status,
200
+ evaluation_reason=evaluation_reason,
201
+ config_rule_name=self.rule_name,
202
+ region=region
203
+ )
204
+
205
+ def _get_rule_remediation_steps(self) -> List[str]:
206
+ """Get remediation steps for cross-region backup copy."""
207
+ return [
208
+ "1. Update backup plan to include cross-region copy:",
209
+ " aws backup update-backup-plan \\",
210
+ " --backup-plan-id <plan-id> \\",
211
+ " --backup-plan '{",
212
+ ' "Rules": [{',
213
+ ' "RuleName": "daily-backup",',
214
+ ' "TargetBackupVaultName": "primary-vault",',
215
+ ' "ScheduleExpression": "cron(0 5 * * ? *)",',
216
+ ' "CopyActions": [{',
217
+ ' "DestinationBackupVaultArn": "arn:aws:backup:us-west-2:...:backup-vault:dr-vault",',
218
+ ' "Lifecycle": {"DeleteAfterDays": 90}',
219
+ " }]",
220
+ " }]",
221
+ " }'",
222
+ "",
223
+ "2. Console method:",
224
+ " - Navigate to AWS Backup",
225
+ " - Select backup plan",
226
+ " - Click 'Edit'",
227
+ " - In backup rule, expand 'Copy to destination'",
228
+ " - Select destination region",
229
+ " - Select destination vault",
230
+ " - Configure lifecycle",
231
+ " - Click 'Save plan'",
232
+ "",
233
+ "3. Best practices:",
234
+ " - Copy to geographically distant region",
235
+ " - Use separate AWS account for DR",
236
+ " - Encrypt copies with different KMS key",
237
+ " - Test restore from DR region regularly",
238
+ "",
239
+ "Priority: HIGH - Critical for disaster recovery",
240
+ "Effort: Medium - Requires DR planning",
241
+ "",
242
+ "AWS Documentation:",
243
+ "https://docs.aws.amazon.com/aws-backup/latest/devguide/cross-region-backup.html"
244
+ ]
245
+
246
+
247
+ class BackupVaultLockEnabledAssessment(BaseConfigRuleAssessment):
248
+ """
249
+ CIS Control 11.3 - Protect Recovery Data
250
+ AWS Config Rule: backup-vault-lock-enabled
251
+
252
+ Ensures backup vaults have vault lock enabled to prevent deletion.
253
+ """
254
+
255
+ def __init__(self):
256
+ super().__init__(
257
+ rule_name="backup-vault-lock-enabled",
258
+ control_id="11.3",
259
+ resource_types=["AWS::Backup::BackupVault"]
260
+ )
261
+
262
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
263
+ """Get backup vaults and check for vault lock."""
264
+ if resource_type != "AWS::Backup::BackupVault":
265
+ return []
266
+
267
+ try:
268
+ backup_client = aws_factory.get_client('backup', region)
269
+
270
+ response = backup_client.list_backup_vaults()
271
+ vaults = []
272
+
273
+ for vault in response.get('BackupVaultList', []):
274
+ vault_name = vault.get('BackupVaultName')
275
+
276
+ # Check for vault lock configuration
277
+ try:
278
+ lock_response = backup_client.describe_backup_vault(BackupVaultName=vault_name)
279
+ min_retention_days = lock_response.get('MinRetentionDays')
280
+ max_retention_days = lock_response.get('MaxRetentionDays')
281
+ locked = lock_response.get('Locked', False)
282
+
283
+ has_lock = bool(locked or min_retention_days or max_retention_days)
284
+ except ClientError:
285
+ has_lock = False
286
+
287
+ vaults.append({
288
+ 'VaultName': vault_name,
289
+ 'VaultArn': vault.get('BackupVaultArn'),
290
+ 'HasLock': has_lock
291
+ })
292
+
293
+ return vaults
294
+
295
+ except ClientError as e:
296
+ logger.error(f"Error retrieving backup vaults in {region}: {e}")
297
+ return []
298
+
299
+ def _evaluate_resource_compliance(
300
+ self,
301
+ resource: Dict[str, Any],
302
+ aws_factory: AWSClientFactory,
303
+ region: str
304
+ ) -> ComplianceResult:
305
+ """Evaluate if backup vault has lock enabled."""
306
+ vault_name = resource.get('VaultName', 'unknown')
307
+ has_lock = resource.get('HasLock', False)
308
+
309
+ if has_lock:
310
+ evaluation_reason = f"Backup vault '{vault_name}' has vault lock enabled"
311
+ compliance_status = ComplianceStatus.COMPLIANT
312
+ else:
313
+ evaluation_reason = f"Backup vault '{vault_name}' does not have vault lock enabled"
314
+ compliance_status = ComplianceStatus.NON_COMPLIANT
315
+
316
+ return ComplianceResult(
317
+ resource_id=resource.get('VaultArn', vault_name),
318
+ resource_type="AWS::Backup::BackupVault",
319
+ compliance_status=compliance_status,
320
+ evaluation_reason=evaluation_reason,
321
+ config_rule_name=self.rule_name,
322
+ region=region
323
+ )
324
+
325
+ def _get_rule_remediation_steps(self) -> List[str]:
326
+ """Get remediation steps for backup vault lock."""
327
+ return [
328
+ "1. Enable vault lock on backup vault:",
329
+ " aws backup put-backup-vault-lock-configuration \\",
330
+ " --backup-vault-name <vault-name> \\",
331
+ " --min-retention-days 7 \\",
332
+ " --max-retention-days 365",
333
+ "",
334
+ "2. Console method:",
335
+ " - Navigate to AWS Backup",
336
+ " - Select backup vault",
337
+ " - Click 'Vault lock configuration'",
338
+ " - Enable vault lock",
339
+ " - Set minimum retention days",
340
+ " - Set maximum retention days (optional)",
341
+ " - Click 'Save'",
342
+ "",
343
+ "3. Important notes:",
344
+ " - Vault lock is IRREVERSIBLE once enabled",
345
+ " - Test with non-production vault first",
346
+ " - Prevents deletion of backups within retention period",
347
+ " - Protects against ransomware and insider threats",
348
+ "",
349
+ "Priority: HIGH - Protects against backup deletion",
350
+ "Effort: Low - Simple configuration (but irreversible!)",
351
+ "",
352
+ "AWS Documentation:",
353
+ "https://docs.aws.amazon.com/aws-backup/latest/devguide/vault-lock.html"
354
+ ]
355
+
356
+
357
+ class Route53QueryLoggingEnabledAssessment(BaseConfigRuleAssessment):
358
+ """
359
+ CIS Control 8.2 - Collect Audit Logs
360
+ AWS Config Rule: route53-query-logging-enabled
361
+
362
+ Ensures Route 53 hosted zones have query logging enabled.
363
+ """
364
+
365
+ def __init__(self):
366
+ super().__init__(
367
+ rule_name="route53-query-logging-enabled",
368
+ control_id="8.2",
369
+ resource_types=["AWS::Route53::HostedZone"]
370
+ )
371
+
372
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
373
+ """Get Route 53 hosted zones and check for query logging."""
374
+ if resource_type != "AWS::Route53::HostedZone":
375
+ return []
376
+
377
+ # Route 53 is global, only process in us-east-1
378
+ if region != 'us-east-1':
379
+ return []
380
+
381
+ try:
382
+ route53_client = aws_factory.get_client('route53', region)
383
+
384
+ response = route53_client.list_hosted_zones()
385
+ zones = []
386
+
387
+ for zone in response.get('HostedZones', []):
388
+ zone_id = zone.get('Id').split('/')[-1] # Extract ID from full path
389
+ zone_name = zone.get('Name')
390
+
391
+ # Check for query logging configuration
392
+ try:
393
+ logging_response = route53_client.list_query_logging_configs(
394
+ HostedZoneId=zone_id
395
+ )
396
+ has_logging = len(logging_response.get('QueryLoggingConfigs', [])) > 0
397
+ except ClientError:
398
+ has_logging = False
399
+
400
+ zones.append({
401
+ 'ZoneId': zone_id,
402
+ 'ZoneName': zone_name,
403
+ 'HasQueryLogging': has_logging,
404
+ 'IsPrivate': zone.get('Config', {}).get('PrivateZone', False)
405
+ })
406
+
407
+ return zones
408
+
409
+ except ClientError as e:
410
+ logger.error(f"Error retrieving Route 53 hosted zones: {e}")
411
+ return []
412
+
413
+ def _evaluate_resource_compliance(
414
+ self,
415
+ resource: Dict[str, Any],
416
+ aws_factory: AWSClientFactory,
417
+ region: str
418
+ ) -> ComplianceResult:
419
+ """Evaluate if hosted zone has query logging enabled."""
420
+ zone_id = resource.get('ZoneId', 'unknown')
421
+ zone_name = resource.get('ZoneName', 'unknown')
422
+ has_logging = resource.get('HasQueryLogging', False)
423
+ is_private = resource.get('IsPrivate', False)
424
+
425
+ if has_logging:
426
+ evaluation_reason = f"Route 53 hosted zone '{zone_name}' has query logging enabled"
427
+ compliance_status = ComplianceStatus.COMPLIANT
428
+ else:
429
+ evaluation_reason = f"Route 53 hosted zone '{zone_name}' does not have query logging enabled"
430
+ compliance_status = ComplianceStatus.NON_COMPLIANT
431
+
432
+ return ComplianceResult(
433
+ resource_id=zone_id,
434
+ resource_type="AWS::Route53::HostedZone",
435
+ compliance_status=compliance_status,
436
+ evaluation_reason=evaluation_reason,
437
+ config_rule_name=self.rule_name,
438
+ region=region
439
+ )
440
+
441
+ def _get_rule_remediation_steps(self) -> List[str]:
442
+ """Get remediation steps for Route 53 query logging."""
443
+ return [
444
+ "1. Create CloudWatch log group for query logs:",
445
+ " aws logs create-log-group \\",
446
+ " --log-group-name /aws/route53/queries",
447
+ "",
448
+ "2. Create query logging configuration:",
449
+ " aws route53 create-query-logging-config \\",
450
+ " --hosted-zone-id <zone-id> \\",
451
+ " --cloudwatch-logs-log-group-arn <log-group-arn>",
452
+ "",
453
+ "3. Console method:",
454
+ " - Navigate to Route 53",
455
+ " - Select hosted zone",
456
+ " - Click 'Configure query logging'",
457
+ " - Select or create CloudWatch log group",
458
+ " - Click 'Create'",
459
+ "",
460
+ "4. Best practices:",
461
+ " - Enable for all public hosted zones",
462
+ " - Consider for private zones with sensitive data",
463
+ " - Set appropriate log retention period",
464
+ " - Monitor logs for suspicious queries",
465
+ "",
466
+ "Priority: MEDIUM - DNS query monitoring",
467
+ "Effort: Low - Simple configuration",
468
+ "",
469
+ "AWS Documentation:",
470
+ "https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/query-logs.html"
471
+ ]
472
+
473
+
474
+ class RDSBackupRetentionCheckAssessment(BaseConfigRuleAssessment):
475
+ """
476
+ CIS Control 11.2 - Perform Automated Backups
477
+ AWS Config Rule: rds-backup-retention-check
478
+
479
+ Ensures RDS instances have adequate backup retention periods.
480
+ """
481
+
482
+ def __init__(self):
483
+ super().__init__(
484
+ rule_name="rds-backup-retention-check",
485
+ control_id="11.2",
486
+ resource_types=["AWS::RDS::DBInstance"]
487
+ )
488
+
489
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
490
+ """Get RDS instances and check backup retention."""
491
+ if resource_type != "AWS::RDS::DBInstance":
492
+ return []
493
+
494
+ try:
495
+ rds_client = aws_factory.get_client('rds', region)
496
+
497
+ response = rds_client.describe_db_instances()
498
+ instances = []
499
+
500
+ for db in response.get('DBInstances', []):
501
+ retention_period = db.get('BackupRetentionPeriod', 0)
502
+
503
+ # Minimum recommended retention is 7 days
504
+ meets_minimum = retention_period >= 7
505
+
506
+ instances.append({
507
+ 'DBInstanceIdentifier': db.get('DBInstanceIdentifier'),
508
+ 'BackupRetentionPeriod': retention_period,
509
+ 'MeetsMinimum': meets_minimum
510
+ })
511
+
512
+ return instances
513
+
514
+ except ClientError as e:
515
+ logger.error(f"Error retrieving RDS instances in {region}: {e}")
516
+ return []
517
+
518
+ def _evaluate_resource_compliance(
519
+ self,
520
+ resource: Dict[str, Any],
521
+ aws_factory: AWSClientFactory,
522
+ region: str
523
+ ) -> ComplianceResult:
524
+ """Evaluate if RDS instance has adequate backup retention."""
525
+ db_id = resource.get('DBInstanceIdentifier', 'unknown')
526
+ retention_period = resource.get('BackupRetentionPeriod', 0)
527
+ meets_minimum = resource.get('MeetsMinimum', False)
528
+
529
+ if meets_minimum:
530
+ evaluation_reason = f"RDS instance '{db_id}' has {retention_period} days backup retention (meets 7-day minimum)"
531
+ compliance_status = ComplianceStatus.COMPLIANT
532
+ else:
533
+ evaluation_reason = f"RDS instance '{db_id}' has {retention_period} days backup retention (below 7-day minimum)"
534
+ compliance_status = ComplianceStatus.NON_COMPLIANT
535
+
536
+ return ComplianceResult(
537
+ resource_id=db_id,
538
+ resource_type="AWS::RDS::DBInstance",
539
+ compliance_status=compliance_status,
540
+ evaluation_reason=evaluation_reason,
541
+ config_rule_name=self.rule_name,
542
+ region=region
543
+ )
544
+
545
+ def _get_rule_remediation_steps(self) -> List[str]:
546
+ """Get remediation steps for RDS backup retention."""
547
+ return [
548
+ "1. Update RDS backup retention period:",
549
+ " aws rds modify-db-instance \\",
550
+ " --db-instance-identifier <db-id> \\",
551
+ " --backup-retention-period 7 \\",
552
+ " --apply-immediately",
553
+ "",
554
+ "2. Set retention for new instances:",
555
+ " aws rds create-db-instance \\",
556
+ " --db-instance-identifier <db-id> \\",
557
+ " --backup-retention-period 30 \\",
558
+ " ...",
559
+ "",
560
+ "3. Console method:",
561
+ " - Navigate to RDS",
562
+ " - Select database instance",
563
+ " - Click 'Modify'",
564
+ " - Under 'Backup', set retention period",
565
+ " - Click 'Continue'",
566
+ " - Select 'Apply immediately'",
567
+ " - Click 'Modify DB instance'",
568
+ "",
569
+ "4. Recommended retention periods:",
570
+ " - Development: 7 days minimum",
571
+ " - Production: 30 days recommended",
572
+ " - Compliance: 90+ days as required",
573
+ "",
574
+ "Priority: HIGH - Essential for data recovery",
575
+ "Effort: Low - Simple configuration change",
576
+ "",
577
+ "AWS Documentation:",
578
+ "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithAutomatedBackups.html"
579
+ ]