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,766 @@
1
+ # Assessment Logic Documentation
2
+
3
+ This document provides detailed information about the assessment logic used by the AWS CIS Controls Compliance Assessment Framework - a production-ready, enterprise-grade solution with complete CIS Controls coverage.
4
+
5
+ ## Production Framework Overview
6
+
7
+ **✅ Complete Implementation Status**
8
+ - 136 AWS Config rules implemented (131 CIS Controls + 5 bonus)
9
+ - 100% coverage across all Implementation Groups (IG1, IG2, IG3)
10
+ - Production-tested with enterprise-grade error handling
11
+ - Optimized for large-scale enterprise deployments
12
+
13
+ ## Table of Contents
14
+
15
+ 1. [Overview](#overview)
16
+ 2. [Assessment Framework](#assessment-framework)
17
+ 3. [Resource Discovery](#resource-discovery)
18
+ 4. [Compliance Evaluation](#compliance-evaluation)
19
+ 5. [Scoring Methodology](#scoring-methodology)
20
+ 6. [Error Handling](#error-handling)
21
+ 7. [Control-Specific Logic](#control-specific-logic)
22
+ 8. [Performance Optimizations](#performance-optimizations)
23
+
24
+ ## Overview
25
+
26
+ The assessment tool evaluates AWS account configurations against CIS Controls using the same logic as AWS Config rules, but without requiring AWS Config to be enabled. Each assessment follows a standardized process while implementing control-specific evaluation logic.
27
+
28
+ **Framework Scope**: 136 implemented rules covering all CIS Controls requirements plus 5 bonus security enhancements for additional value.
29
+
30
+ ### Key Principles
31
+
32
+ 1. **Config Rule Fidelity**: Assessment logic mirrors AWS Config rule specifications exactly
33
+ 2. **Resource Coverage**: All applicable AWS resource types are evaluated
34
+ 3. **Regional Scope**: Assessments are performed across all specified regions
35
+ 4. **Error Resilience**: Graceful handling of API errors and service unavailability
36
+ 5. **Performance**: Optimized for large-scale enterprise environments
37
+
38
+ ## Assessment Framework
39
+
40
+ ### Base Assessment Pattern
41
+
42
+ All assessments follow this standardized pattern:
43
+
44
+ ```python
45
+ class BaseConfigRuleAssessment:
46
+ def evaluate_compliance(self, aws_factory, region):
47
+ """Main assessment entry point."""
48
+ all_results = []
49
+
50
+ for resource_type in self.resource_types:
51
+ # 1. Discover resources
52
+ resources = self._get_resources(aws_factory, resource_type, region)
53
+
54
+ # 2. Evaluate each resource
55
+ for resource in resources:
56
+ result = self._evaluate_resource_compliance(resource, aws_factory)
57
+ all_results.append(result)
58
+
59
+ return all_results
60
+
61
+ def _get_resources(self, aws_factory, resource_type, region):
62
+ """Discover resources of specified type in region."""
63
+ # Implementation varies by resource type
64
+ pass
65
+
66
+ def _evaluate_resource_compliance(self, resource, aws_factory):
67
+ """Evaluate compliance for individual resource."""
68
+ # Implementation varies by control logic
69
+ pass
70
+ ```
71
+
72
+ ### Assessment Lifecycle
73
+
74
+ 1. **Initialization**: Load configuration and validate parameters
75
+ 2. **Resource Discovery**: Find all applicable resources in target regions
76
+ 3. **Compliance Evaluation**: Apply control-specific logic to each resource
77
+ 4. **Result Aggregation**: Collect and format compliance results
78
+ 5. **Scoring Calculation**: Calculate compliance percentages
79
+ 6. **Report Generation**: Generate output in requested formats
80
+
81
+ ## Resource Discovery
82
+
83
+ ### Discovery Strategies
84
+
85
+ Different AWS services require different discovery approaches:
86
+
87
+ #### EC2 Resources
88
+ ```python
89
+ def discover_ec2_instances(self, ec2_client, region):
90
+ """Discover EC2 instances using describe_instances."""
91
+ try:
92
+ paginator = ec2_client.get_paginator('describe_instances')
93
+
94
+ instances = []
95
+ for page in paginator.paginate():
96
+ for reservation in page['Reservations']:
97
+ for instance in reservation['Instances']:
98
+ # Filter out terminated instances
99
+ if instance['State']['Name'] != 'terminated':
100
+ instances.append({
101
+ 'InstanceId': instance['InstanceId'],
102
+ 'InstanceType': instance['InstanceType'],
103
+ 'State': instance['State']['Name'],
104
+ 'Region': region,
105
+ 'LaunchTime': instance['LaunchTime'],
106
+ 'SecurityGroups': instance.get('SecurityGroups', []),
107
+ 'IamInstanceProfile': instance.get('IamInstanceProfile'),
108
+ 'Monitoring': instance.get('Monitoring', {}),
109
+ 'Tags': instance.get('Tags', [])
110
+ })
111
+
112
+ return instances
113
+
114
+ except ClientError as e:
115
+ if e.response['Error']['Code'] == 'UnauthorizedOperation':
116
+ self.logger.warning(f"Insufficient permissions for EC2 in {region}")
117
+ return []
118
+ raise
119
+ ```
120
+
121
+ #### S3 Resources
122
+ ```python
123
+ def discover_s3_buckets(self, s3_client, region):
124
+ """Discover S3 buckets in specific region."""
125
+ try:
126
+ # List all buckets (global operation)
127
+ response = s3_client.list_buckets()
128
+
129
+ regional_buckets = []
130
+ for bucket in response['Buckets']:
131
+ try:
132
+ # Get bucket region
133
+ bucket_region = s3_client.get_bucket_location(
134
+ Bucket=bucket['Name']
135
+ )['LocationConstraint']
136
+
137
+ # Handle us-east-1 special case
138
+ if bucket_region is None:
139
+ bucket_region = 'us-east-1'
140
+
141
+ if bucket_region == region:
142
+ regional_buckets.append({
143
+ 'Name': bucket['Name'],
144
+ 'CreationDate': bucket['CreationDate'],
145
+ 'Region': region
146
+ })
147
+
148
+ except ClientError as e:
149
+ # Skip buckets we can't access
150
+ if e.response['Error']['Code'] in ['AccessDenied', 'NoSuchBucket']:
151
+ continue
152
+ raise
153
+
154
+ return regional_buckets
155
+
156
+ except ClientError as e:
157
+ if e.response['Error']['Code'] == 'AccessDenied':
158
+ self.logger.warning(f"Insufficient permissions for S3")
159
+ return []
160
+ raise
161
+ ```
162
+
163
+ #### IAM Resources
164
+ ```python
165
+ def discover_iam_users(self, iam_client):
166
+ """Discover IAM users (global service)."""
167
+ try:
168
+ paginator = iam_client.get_paginator('list_users')
169
+
170
+ users = []
171
+ for page in paginator.paginate():
172
+ for user in page['Users']:
173
+ users.append({
174
+ 'UserName': user['UserName'],
175
+ 'UserId': user['UserId'],
176
+ 'Arn': user['Arn'],
177
+ 'CreateDate': user['CreateDate'],
178
+ 'PasswordLastUsed': user.get('PasswordLastUsed'),
179
+ 'Tags': user.get('Tags', [])
180
+ })
181
+
182
+ return users
183
+
184
+ except ClientError as e:
185
+ if e.response['Error']['Code'] == 'AccessDenied':
186
+ self.logger.warning("Insufficient permissions for IAM")
187
+ return []
188
+ raise
189
+ ```
190
+
191
+ ### Pagination Handling
192
+
193
+ All discovery operations use proper pagination to handle large resource sets:
194
+
195
+ ```python
196
+ def paginated_discovery(self, client, operation_name, **kwargs):
197
+ """Generic paginated resource discovery."""
198
+ try:
199
+ if client.can_paginate(operation_name):
200
+ paginator = client.get_paginator(operation_name)
201
+ for page in paginator.paginate(**kwargs):
202
+ yield page
203
+ else:
204
+ # Single page operation
205
+ response = getattr(client, operation_name)(**kwargs)
206
+ yield response
207
+
208
+ except ClientError as e:
209
+ self.logger.error(f"Failed to paginate {operation_name}: {e}")
210
+ raise
211
+ ```
212
+
213
+ ## Compliance Evaluation
214
+
215
+ ### Evaluation Patterns
216
+
217
+ Different controls use different evaluation patterns:
218
+
219
+ #### Binary Compliance
220
+ Simple yes/no compliance checks:
221
+
222
+ ```python
223
+ def evaluate_eip_attached(self, resource, aws_factory):
224
+ """Evaluate if EIP is attached to an instance or ENI."""
225
+ eip_allocation_id = resource['AllocationId']
226
+ region = resource['Region']
227
+
228
+ # Check if EIP has InstanceId or NetworkInterfaceId
229
+ if resource.get('InstanceId') or resource.get('NetworkInterfaceId'):
230
+ return ComplianceResult(
231
+ resource_id=eip_allocation_id,
232
+ resource_type="AWS::EC2::EIP",
233
+ compliance_status="COMPLIANT",
234
+ evaluation_reason="EIP is attached to an instance or ENI",
235
+ config_rule_name="eip-attached",
236
+ region=region,
237
+ timestamp=datetime.now()
238
+ )
239
+ else:
240
+ return ComplianceResult(
241
+ resource_id=eip_allocation_id,
242
+ resource_type="AWS::EC2::EIP",
243
+ compliance_status="NON_COMPLIANT",
244
+ evaluation_reason="EIP is not attached to any instance or ENI",
245
+ config_rule_name="eip-attached",
246
+ region=region,
247
+ timestamp=datetime.now(),
248
+ remediation_guidance="Attach the EIP to an EC2 instance or release it to avoid charges"
249
+ )
250
+ ```
251
+
252
+ #### Parameter-Based Compliance
253
+ Compliance based on configuration parameters:
254
+
255
+ ```python
256
+ def evaluate_iam_password_policy(self, resource, aws_factory):
257
+ """Evaluate IAM password policy against parameters."""
258
+ policy = resource['PasswordPolicy']
259
+
260
+ # Check all required parameters
261
+ compliance_issues = []
262
+
263
+ if not policy.get('RequireUppercaseCharacters', False):
264
+ compliance_issues.append("uppercase characters not required")
265
+
266
+ if not policy.get('RequireLowercaseCharacters', False):
267
+ compliance_issues.append("lowercase characters not required")
268
+
269
+ if not policy.get('RequireNumbers', False):
270
+ compliance_issues.append("numbers not required")
271
+
272
+ if not policy.get('RequireSymbols', False):
273
+ compliance_issues.append("symbols not required")
274
+
275
+ min_length = policy.get('MinimumPasswordLength', 0)
276
+ if min_length < self.parameters.get('MinimumPasswordLength', 14):
277
+ compliance_issues.append(f"minimum length {min_length} is too short")
278
+
279
+ if compliance_issues:
280
+ return ComplianceResult(
281
+ resource_id="account-password-policy",
282
+ resource_type="AWS::IAM::AccountPasswordPolicy",
283
+ compliance_status="NON_COMPLIANT",
284
+ evaluation_reason=f"Password policy issues: {', '.join(compliance_issues)}",
285
+ config_rule_name="iam-password-policy",
286
+ region="global",
287
+ timestamp=datetime.now(),
288
+ remediation_guidance="Update IAM password policy to meet security requirements"
289
+ )
290
+ else:
291
+ return ComplianceResult(
292
+ resource_id="account-password-policy",
293
+ resource_type="AWS::IAM::AccountPasswordPolicy",
294
+ compliance_status="COMPLIANT",
295
+ evaluation_reason="Password policy meets all requirements",
296
+ config_rule_name="iam-password-policy",
297
+ region="global",
298
+ timestamp=datetime.now()
299
+ )
300
+ ```
301
+
302
+ #### Multi-Step Evaluation
303
+ Complex evaluations requiring multiple API calls:
304
+
305
+ ```python
306
+ def evaluate_s3_bucket_encryption(self, resource, aws_factory):
307
+ """Evaluate S3 bucket encryption configuration."""
308
+ bucket_name = resource['Name']
309
+ region = resource['Region']
310
+
311
+ try:
312
+ s3_client = aws_factory.get_client('s3', region)
313
+
314
+ # Step 1: Check bucket encryption
315
+ try:
316
+ encryption_response = s3_client.get_bucket_encryption(Bucket=bucket_name)
317
+ encryption_config = encryption_response.get('ServerSideEncryptionConfiguration', {})
318
+ rules = encryption_config.get('Rules', [])
319
+
320
+ if not rules:
321
+ return self._create_non_compliant_result(
322
+ bucket_name, region, "No encryption rules configured"
323
+ )
324
+
325
+ # Step 2: Validate encryption algorithm
326
+ for rule in rules:
327
+ sse_config = rule.get('ApplyServerSideEncryptionByDefault', {})
328
+ algorithm = sse_config.get('SSEAlgorithm')
329
+
330
+ if algorithm not in ['AES256', 'aws:kms']:
331
+ return self._create_non_compliant_result(
332
+ bucket_name, region, f"Invalid encryption algorithm: {algorithm}"
333
+ )
334
+
335
+ # Step 3: For KMS, check key configuration
336
+ if algorithm == 'aws:kms':
337
+ kms_key_id = sse_config.get('KMSMasterKeyID')
338
+ if not kms_key_id:
339
+ return self._create_non_compliant_result(
340
+ bucket_name, region, "KMS encryption without key ID"
341
+ )
342
+
343
+ return ComplianceResult(
344
+ resource_id=bucket_name,
345
+ resource_type="AWS::S3::Bucket",
346
+ compliance_status="COMPLIANT",
347
+ evaluation_reason="Bucket has proper encryption configuration",
348
+ config_rule_name="s3-bucket-server-side-encryption-enabled",
349
+ region=region,
350
+ timestamp=datetime.now()
351
+ )
352
+
353
+ except ClientError as e:
354
+ if e.response['Error']['Code'] == 'ServerSideEncryptionConfigurationNotFoundError':
355
+ return self._create_non_compliant_result(
356
+ bucket_name, region, "No server-side encryption configuration"
357
+ )
358
+ raise
359
+
360
+ except Exception as e:
361
+ return self._create_error_result(bucket_name, region, str(e))
362
+ ```
363
+
364
+ ## Scoring Methodology
365
+
366
+ ### Control-Level Scoring
367
+
368
+ Each control's compliance score is calculated as:
369
+
370
+ ```python
371
+ def calculate_control_score(self, compliance_results):
372
+ """Calculate compliance score for a control."""
373
+ if not compliance_results:
374
+ return ControlScore(
375
+ control_id=self.control_id,
376
+ title=self.title,
377
+ implementation_group=self.implementation_group,
378
+ total_resources=0,
379
+ compliant_resources=0,
380
+ compliance_percentage=0.0,
381
+ config_rules_evaluated=[],
382
+ findings=[]
383
+ )
384
+
385
+ # Count compliant resources
386
+ compliant_count = sum(1 for result in compliance_results
387
+ if result.compliance_status == 'COMPLIANT')
388
+
389
+ # Count total evaluable resources (exclude errors and not applicable)
390
+ evaluable_results = [result for result in compliance_results
391
+ if result.compliance_status in ['COMPLIANT', 'NON_COMPLIANT']]
392
+
393
+ total_count = len(evaluable_results)
394
+
395
+ if total_count == 0:
396
+ compliance_percentage = 0.0
397
+ else:
398
+ compliance_percentage = (compliant_count / total_count) * 100
399
+
400
+ return ControlScore(
401
+ control_id=self.control_id,
402
+ title=self.title,
403
+ implementation_group=self.implementation_group,
404
+ total_resources=total_count,
405
+ compliant_resources=compliant_count,
406
+ compliance_percentage=compliance_percentage,
407
+ config_rules_evaluated=list(set(result.config_rule_name for result in compliance_results)),
408
+ findings=evaluable_results
409
+ )
410
+ ```
411
+
412
+ ### Implementation Group Scoring
413
+
414
+ IG scores are calculated as weighted averages of control scores:
415
+
416
+ ```python
417
+ def calculate_ig_score(self, control_scores):
418
+ """Calculate Implementation Group compliance score."""
419
+ if not control_scores:
420
+ return IGScore(
421
+ implementation_group=self.ig_name,
422
+ total_controls=0,
423
+ compliant_controls=0,
424
+ compliance_percentage=0.0,
425
+ control_scores={}
426
+ )
427
+
428
+ # Calculate weighted average
429
+ total_weight = 0
430
+ weighted_sum = 0
431
+
432
+ for control_id, control_score in control_scores.items():
433
+ weight = control_score.weight
434
+ total_weight += weight
435
+ weighted_sum += control_score.compliance_percentage * weight
436
+
437
+ if total_weight == 0:
438
+ overall_percentage = 0.0
439
+ else:
440
+ overall_percentage = weighted_sum / total_weight
441
+
442
+ # Count fully compliant controls (100% compliance)
443
+ compliant_controls = sum(1 for score in control_scores.values()
444
+ if score.compliance_percentage == 100.0)
445
+
446
+ return IGScore(
447
+ implementation_group=self.ig_name,
448
+ total_controls=len(control_scores),
449
+ compliant_controls=compliant_controls,
450
+ compliance_percentage=overall_percentage,
451
+ control_scores=control_scores
452
+ )
453
+ ```
454
+
455
+ ### Overall Scoring
456
+
457
+ Overall compliance is calculated across all Implementation Groups:
458
+
459
+ ```python
460
+ def calculate_overall_score(self, ig_scores):
461
+ """Calculate overall compliance score."""
462
+ if not ig_scores:
463
+ return 0.0
464
+
465
+ # Weight Implementation Groups
466
+ ig_weights = {
467
+ 'IG1': 0.5, # 50% weight for essential controls
468
+ 'IG2': 0.3, # 30% weight for enhanced controls
469
+ 'IG3': 0.2 # 20% weight for advanced controls
470
+ }
471
+
472
+ total_weight = 0
473
+ weighted_sum = 0
474
+
475
+ for ig_name, ig_score in ig_scores.items():
476
+ weight = ig_weights.get(ig_name, 1.0)
477
+ total_weight += weight
478
+ weighted_sum += ig_score.compliance_percentage * weight
479
+
480
+ if total_weight == 0:
481
+ return 0.0
482
+
483
+ return weighted_sum / total_weight
484
+ ```
485
+
486
+ ## Error Handling
487
+
488
+ ### Error Categories
489
+
490
+ The assessment tool handles various error conditions:
491
+
492
+ #### Permission Errors
493
+ ```python
494
+ def handle_permission_error(self, error, resource_id, region):
495
+ """Handle AWS permission errors."""
496
+ return ComplianceResult(
497
+ resource_id=resource_id,
498
+ resource_type=self.resource_type,
499
+ compliance_status="INSUFFICIENT_PERMISSIONS",
500
+ evaluation_reason=f"Insufficient permissions: {error.response['Error']['Code']}",
501
+ config_rule_name=self.rule_name,
502
+ region=region,
503
+ timestamp=datetime.now(),
504
+ remediation_guidance="Grant necessary IAM permissions for assessment"
505
+ )
506
+ ```
507
+
508
+ #### Service Unavailable
509
+ ```python
510
+ def handle_service_error(self, error, resource_id, region):
511
+ """Handle AWS service unavailability."""
512
+ return ComplianceResult(
513
+ resource_id=resource_id,
514
+ resource_type=self.resource_type,
515
+ compliance_status="ERROR",
516
+ evaluation_reason=f"Service error: {error.response['Error']['Code']}",
517
+ config_rule_name=self.rule_name,
518
+ region=region,
519
+ timestamp=datetime.now(),
520
+ remediation_guidance="Retry assessment when service is available"
521
+ )
522
+ ```
523
+
524
+ #### Resource Not Found
525
+ ```python
526
+ def handle_not_found_error(self, error, resource_id, region):
527
+ """Handle resource not found errors."""
528
+ return ComplianceResult(
529
+ resource_id=resource_id,
530
+ resource_type=self.resource_type,
531
+ compliance_status="NOT_APPLICABLE",
532
+ evaluation_reason="Resource not found or not applicable",
533
+ config_rule_name=self.rule_name,
534
+ region=region,
535
+ timestamp=datetime.now()
536
+ )
537
+ ```
538
+
539
+ ### Retry Logic
540
+
541
+ Transient errors are handled with exponential backoff:
542
+
543
+ ```python
544
+ def retry_with_backoff(self, func, max_retries=3, base_delay=1):
545
+ """Retry function with exponential backoff."""
546
+ for attempt in range(max_retries):
547
+ try:
548
+ return func()
549
+ except ClientError as e:
550
+ error_code = e.response['Error']['Code']
551
+
552
+ # Don't retry permission errors
553
+ if error_code in ['AccessDenied', 'UnauthorizedOperation']:
554
+ raise
555
+
556
+ # Retry throttling and service errors
557
+ if error_code in ['Throttling', 'ThrottlingException', 'ServiceUnavailable']:
558
+ if attempt < max_retries - 1:
559
+ delay = base_delay * (2 ** attempt)
560
+ time.sleep(delay)
561
+ continue
562
+
563
+ raise
564
+
565
+ raise Exception(f"Max retries ({max_retries}) exceeded")
566
+ ```
567
+
568
+ ## Control-Specific Logic
569
+
570
+ ### Asset Inventory Controls (1.x)
571
+
572
+ Focus on resource discovery and management:
573
+
574
+ ```python
575
+ class AssetInventoryAssessment(BaseConfigRuleAssessment):
576
+ """Base class for asset inventory assessments."""
577
+
578
+ def evaluate_asset_management(self, resource, aws_factory):
579
+ """Common asset management evaluation logic."""
580
+ # Check if resource is properly tagged
581
+ tags = resource.get('Tags', [])
582
+ required_tags = self.parameters.get('RequiredTags', [])
583
+
584
+ missing_tags = []
585
+ for required_tag in required_tags:
586
+ if not any(tag['Key'] == required_tag for tag in tags):
587
+ missing_tags.append(required_tag)
588
+
589
+ # Check if resource is managed by Systems Manager (for EC2)
590
+ if resource.get('ResourceType') == 'AWS::EC2::Instance':
591
+ ssm_managed = self.check_ssm_management(resource, aws_factory)
592
+ if not ssm_managed:
593
+ missing_tags.append('SSM_MANAGED')
594
+
595
+ return missing_tags
596
+ ```
597
+
598
+ ### Access Control Controls (3.x, 5.x, 6.x)
599
+
600
+ Focus on authentication, authorization, and access patterns:
601
+
602
+ ```python
603
+ class AccessControlAssessment(BaseConfigRuleAssessment):
604
+ """Base class for access control assessments."""
605
+
606
+ def evaluate_public_access(self, resource, aws_factory):
607
+ """Evaluate if resource allows public access."""
608
+ resource_type = resource.get('ResourceType')
609
+
610
+ if resource_type == 'AWS::S3::Bucket':
611
+ return self.check_s3_public_access(resource, aws_factory)
612
+ elif resource_type == 'AWS::EC2::Instance':
613
+ return self.check_ec2_public_access(resource, aws_factory)
614
+ elif resource_type == 'AWS::RDS::DBInstance':
615
+ return self.check_rds_public_access(resource, aws_factory)
616
+
617
+ return False
618
+
619
+ def check_s3_public_access(self, bucket, aws_factory):
620
+ """Check if S3 bucket allows public access."""
621
+ s3_client = aws_factory.get_client('s3', bucket['Region'])
622
+
623
+ try:
624
+ # Check bucket policy
625
+ policy_response = s3_client.get_bucket_policy(Bucket=bucket['Name'])
626
+ policy = json.loads(policy_response['Policy'])
627
+
628
+ for statement in policy.get('Statement', []):
629
+ principal = statement.get('Principal')
630
+ if principal == '*' or principal == {'AWS': '*'}:
631
+ return True
632
+
633
+ # Check bucket ACL
634
+ acl_response = s3_client.get_bucket_acl(Bucket=bucket['Name'])
635
+ for grant in acl_response.get('Grants', []):
636
+ grantee = grant.get('Grantee', {})
637
+ if grantee.get('URI') in [
638
+ 'http://acs.amazonaws.com/groups/global/AllUsers',
639
+ 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers'
640
+ ]:
641
+ return True
642
+
643
+ return False
644
+
645
+ except ClientError as e:
646
+ if e.response['Error']['Code'] == 'NoSuchBucketPolicy':
647
+ return False
648
+ raise
649
+ ```
650
+
651
+ ### Secure Configuration Controls (4.x)
652
+
653
+ Focus on configuration baselines and hardening:
654
+
655
+ ```python
656
+ class SecureConfigurationAssessment(BaseConfigRuleAssessment):
657
+ """Base class for secure configuration assessments."""
658
+
659
+ def evaluate_security_configuration(self, resource, aws_factory):
660
+ """Evaluate security configuration settings."""
661
+ config_issues = []
662
+
663
+ # Check encryption settings
664
+ if not self.check_encryption_enabled(resource, aws_factory):
665
+ config_issues.append("encryption not enabled")
666
+
667
+ # Check logging settings
668
+ if not self.check_logging_enabled(resource, aws_factory):
669
+ config_issues.append("logging not enabled")
670
+
671
+ # Check monitoring settings
672
+ if not self.check_monitoring_enabled(resource, aws_factory):
673
+ config_issues.append("monitoring not enabled")
674
+
675
+ # Check update settings
676
+ if not self.check_auto_updates_enabled(resource, aws_factory):
677
+ config_issues.append("automatic updates not enabled")
678
+
679
+ return config_issues
680
+ ```
681
+
682
+ ## Performance Optimizations
683
+
684
+ ### Concurrent Processing
685
+
686
+ Assessments are performed concurrently across regions and resource types:
687
+
688
+ ```python
689
+ def run_concurrent_assessments(self, assessment_tasks):
690
+ """Run assessments concurrently with proper resource management."""
691
+ with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
692
+ # Submit all tasks
693
+ future_to_task = {
694
+ executor.submit(self.run_single_assessment, task): task
695
+ for task in assessment_tasks
696
+ }
697
+
698
+ # Collect results as they complete
699
+ results = []
700
+ for future in as_completed(future_to_task):
701
+ task = future_to_task[future]
702
+ try:
703
+ result = future.result(timeout=self.task_timeout)
704
+ results.append(result)
705
+ except Exception as e:
706
+ self.logger.error(f"Assessment task failed: {task}, error: {e}")
707
+ # Create error result
708
+ error_result = self.create_error_result(task, str(e))
709
+ results.append(error_result)
710
+
711
+ return results
712
+ ```
713
+
714
+ ### Caching
715
+
716
+ Resource discovery results are cached to avoid redundant API calls:
717
+
718
+ ```python
719
+ class ResourceCache:
720
+ """Cache for resource discovery results."""
721
+
722
+ def __init__(self, ttl_seconds=300):
723
+ self.cache = {}
724
+ self.ttl = ttl_seconds
725
+
726
+ def get(self, cache_key):
727
+ """Get cached result if still valid."""
728
+ if cache_key in self.cache:
729
+ result, timestamp = self.cache[cache_key]
730
+ if time.time() - timestamp < self.ttl:
731
+ return result
732
+ else:
733
+ del self.cache[cache_key]
734
+ return None
735
+
736
+ def set(self, cache_key, result):
737
+ """Cache result with timestamp."""
738
+ self.cache[cache_key] = (result, time.time())
739
+
740
+ def clear(self):
741
+ """Clear all cached results."""
742
+ self.cache.clear()
743
+ ```
744
+
745
+ ### Batch Operations
746
+
747
+ Where possible, resources are processed in batches:
748
+
749
+ ```python
750
+ def batch_evaluate_resources(self, resources, batch_size=50):
751
+ """Evaluate resources in batches for better performance."""
752
+ results = []
753
+
754
+ for i in range(0, len(resources), batch_size):
755
+ batch = resources[i:i + batch_size]
756
+ batch_results = self.evaluate_resource_batch(batch)
757
+ results.extend(batch_results)
758
+
759
+ # Add small delay between batches to avoid throttling
760
+ if i + batch_size < len(resources):
761
+ time.sleep(0.1)
762
+
763
+ return results
764
+ ```
765
+
766
+ This comprehensive assessment logic ensures accurate, efficient, and reliable evaluation of AWS resources against CIS Controls while maintaining compatibility with AWS Config rule specifications.