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,1266 @@
1
+ """Assessment Engine for orchestrating CIS Controls compliance assessments."""
2
+
3
+ import logging
4
+ import time
5
+ import gc
6
+ import threading
7
+ from concurrent.futures import ThreadPoolExecutor, as_completed
8
+ from datetime import datetime, timedelta
9
+ from typing import Dict, List, Optional, Callable, Any
10
+ from dataclasses import dataclass
11
+ import weakref
12
+ import resource
13
+ import sys
14
+
15
+ from aws_cis_assessment.core.models import (
16
+ AssessmentResult, ControlScore, IGScore, ComplianceResult,
17
+ ComplianceStatus, ImplementationGroup, CISControl
18
+ )
19
+ from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
20
+ from aws_cis_assessment.core.scoring_engine import ScoringEngine
21
+ from aws_cis_assessment.config.config_loader import ConfigRuleLoader
22
+ from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
23
+ from aws_cis_assessment.core.error_handler import ErrorHandler, ErrorContext, ErrorCategory
24
+ from aws_cis_assessment.core.audit_trail import AuditTrail, AuditEventType
25
+
26
+ # Import all control assessments
27
+ from aws_cis_assessment.controls.ig1.control_1_1 import (
28
+ EIPAttachedAssessment, EC2StoppedInstanceAssessment, VPCNetworkACLUnusedAssessment,
29
+ EC2InstanceManagedBySSMAssessment, EC2SecurityGroupAttachedAssessment
30
+ )
31
+ from aws_cis_assessment.controls.ig1.control_2_2 import (
32
+ ElasticBeanstalkManagedUpdatesEnabledAssessment, ECSFargateLatestPlatformVersionAssessment
33
+ )
34
+ from aws_cis_assessment.controls.ig1.control_3_3 import (
35
+ IAMPasswordPolicyAssessment, IAMUserMFAEnabledAssessment, IAMRootAccessKeyAssessment,
36
+ S3BucketPublicReadProhibitedAssessment, EC2InstanceNoPublicIPAssessment
37
+ )
38
+ from aws_cis_assessment.controls.ig1.control_3_4 import (
39
+ S3VersionLifecyclePolicyAssessment, CloudWatchLogGroupRetentionAssessment
40
+ )
41
+ from aws_cis_assessment.controls.ig1.control_4_1 import (
42
+ AccountPartOfOrganizationsAssessment, EC2VolumeInUseAssessment,
43
+ RedshiftClusterMaintenanceSettingsAssessment, SecretsManagerRotationEnabledAssessment
44
+ )
45
+ from aws_cis_assessment.controls.ig1.control_access_keys import (
46
+ AccessKeysRotatedAssessment, EC2IMDSv2CheckAssessment, EC2InstanceProfileAttachedAssessment
47
+ )
48
+ from aws_cis_assessment.controls.ig1.control_iam_policies import (
49
+ IAMPolicyNoStatementsWithAdminAccessAssessment, IAMNoInlinePolicyCheckAssessment,
50
+ IAMUserGroupMembershipCheckAssessment
51
+ )
52
+ from aws_cis_assessment.controls.ig1.control_s3_security import (
53
+ S3BucketSSLRequestsOnlyAssessment, S3BucketServerSideEncryptionEnabledAssessment,
54
+ S3BucketLoggingEnabledAssessment, S3BucketVersioningEnabledAssessment
55
+ )
56
+ from aws_cis_assessment.controls.ig1.control_data_protection import (
57
+ EBSSnapshotPublicRestorableCheckAssessment, RDSSnapshotsPublicProhibitedAssessment,
58
+ RDSInstancePublicAccessCheckAssessment, RedshiftClusterPublicAccessCheckAssessment,
59
+ S3BucketLevelPublicAccessProhibitedAssessment
60
+ )
61
+ from aws_cis_assessment.controls.ig1.control_network_security import (
62
+ DMSReplicationNotPublicAssessment, ElasticsearchInVPCOnlyAssessment,
63
+ EC2InstancesInVPCAssessment, EMRMasterNoPublicIPAssessment,
64
+ LambdaFunctionPublicAccessProhibitedAssessment, SageMakerNotebookNoDirectInternetAccessAssessment,
65
+ SubnetAutoAssignPublicIPDisabledAssessment
66
+ )
67
+ from aws_cis_assessment.controls.ig1.control_iam_governance import (
68
+ IAMGroupHasUsersCheckAssessment, IAMPolicyNoStatementsWithFullAccessAssessment,
69
+ IAMUserNoPoliciesCheckAssessment, SSMDocumentNotPublicAssessment
70
+ )
71
+ from aws_cis_assessment.controls.ig1.control_advanced_security import (
72
+ EC2ManagedInstanceAssociationComplianceStatusCheckAssessment, EMRKerberosEnabledAssessment,
73
+ LambdaInsideVPCAssessment, ECSTaskDefinitionUserForHostModeCheckAssessment
74
+ )
75
+ from aws_cis_assessment.controls.ig1.control_iam_advanced import (
76
+ IAMRootAccessKeyCheckAssessment, IAMUserUnusedCredentialsCheckAssessment,
77
+ IAMCustomerPolicyBlockedKMSActionsAssessment, IAMInlinePolicyBlockedKMSActionsAssessment
78
+ )
79
+ from aws_cis_assessment.controls.ig1.control_cloudtrail_logging import (
80
+ CloudTrailEnabledAssessment, CloudWatchLogGroupEncryptedAssessment
81
+ )
82
+ from aws_cis_assessment.controls.ig1.control_vpc_security import (
83
+ VPCDefaultSecurityGroupClosedAssessment, RestrictedSSHAssessment
84
+ )
85
+ from aws_cis_assessment.controls.ig1.control_critical_security import (
86
+ RootAccountHardwareMFAEnabledAssessment, OpenSearchInVPCOnlyAssessment,
87
+ ECSTaskDefinitionNonRootUserAssessment, SecurityHubEnabledAssessment
88
+ )
89
+ from aws_cis_assessment.controls.ig1.control_network_enhancements import (
90
+ ElasticsearchNodeToNodeEncryptionCheckAssessment, AutoScalingLaunchConfigPublicIPDisabledAssessment,
91
+ EFSAccessPointEnforceRootDirectoryAssessment
92
+ )
93
+ from aws_cis_assessment.controls.ig1.control_backup_recovery import (
94
+ DynamoDBInBackupPlanAssessment, EBSInBackupPlanAssessment, EFSInBackupPlanAssessment,
95
+ DBInstanceBackupEnabledAssessment, RedshiftBackupEnabledAssessment, DynamoDBPITREnabledAssessment,
96
+ ElastiCacheRedisClusterAutomaticBackupCheckAssessment, S3BucketReplicationEnabledAssessment
97
+ )
98
+ from aws_cis_assessment.controls.ig1.control_s3_enhancements import (
99
+ S3AccountLevelPublicAccessBlocksPeriodicAssessment, S3BucketPublicWriteProhibitedAssessment
100
+ )
101
+ from aws_cis_assessment.controls.ig1.control_instance_optimization import (
102
+ EBSOptimizedInstanceAssessment
103
+ )
104
+ from aws_cis_assessment.controls.ig2.control_3_10 import (
105
+ APIGatewaySSLEnabledAssessment, ALBHTTPToHTTPSRedirectionAssessment,
106
+ ELBTLSHTTPSListenersOnlyAssessment, S3BucketSSLRequestsOnlyAssessment,
107
+ RedshiftRequireTLSSSLAssessment
108
+ )
109
+ from aws_cis_assessment.controls.ig2.control_3_11 import (
110
+ EncryptedVolumesAssessment, RDSStorageEncryptedAssessment,
111
+ S3DefaultEncryptionKMSAssessment, DynamoDBTableEncryptedKMSAssessment,
112
+ BackupRecoveryPointEncryptedAssessment
113
+ )
114
+ from aws_cis_assessment.controls.ig2.control_5_2 import (
115
+ MFAEnabledForIAMConsoleAccessAssessment, RootAccountMFAEnabledAssessment,
116
+ IAMUserUnusedCredentialsAssessment
117
+ )
118
+ from aws_cis_assessment.controls.ig2.control_encryption_transit import (
119
+ ELBACMCertificateRequiredAssessment, ELBv2ACMCertificateRequiredAssessment,
120
+ OpenSearchHTTPSRequiredAssessment
121
+ )
122
+ from aws_cis_assessment.controls.ig2.control_encryption_rest import (
123
+ CloudTrailEncryptionEnabledAssessment, EFSEncryptedCheckAssessment as EFSEncryptedCheckIG2Assessment,
124
+ EC2EBSEncryptionByDefaultAssessment, RDSSnapshotEncryptedAssessment
125
+ )
126
+ from aws_cis_assessment.controls.ig2.control_advanced_encryption import (
127
+ SecretsManagerUsingCMKAssessment, SNSEncryptedKMSAssessment, SQSQueueEncryptedKMSAssessment,
128
+ KinesisStreamEncryptedAssessment, ElasticsearchEncryptedAtRestAssessment
129
+ )
130
+ from aws_cis_assessment.controls.ig2.control_service_logging import (
131
+ ElasticsearchLogsToCloudWatchAssessment, ELBLoggingEnabledAssessment, RDSLoggingEnabledAssessment,
132
+ WAFv2LoggingEnabledAssessment, CodeBuildProjectLoggingEnabledAssessment, RedshiftClusterConfigurationCheckAssessment
133
+ )
134
+ from aws_cis_assessment.controls.ig2.control_remaining_encryption import (
135
+ OpenSearchEncryptedAtRestAssessment, OpenSearchNodeToNodeEncryptionCheckAssessment,
136
+ RedshiftClusterKMSEnabledAssessment, SageMakerEndpointConfigurationKMSKeyConfiguredAssessment,
137
+ SageMakerNotebookInstanceKMSKeyConfiguredAssessment, CodeBuildProjectArtifactEncryptionAssessment
138
+ )
139
+ from aws_cis_assessment.controls.ig2.control_network_ha import (
140
+ ELBCrossZoneLoadBalancingEnabledAssessment, ELBDeletionProtectionEnabledAssessment,
141
+ ELBv2MultipleAZAssessment, RDSClusterMultiAZEnabledAssessment,
142
+ RDSInstanceDeletionProtectionEnabledAssessment, RDSMultiAZSupportAssessment,
143
+ VPCVPNTwoTunnelsUpAssessment
144
+ )
145
+ from aws_cis_assessment.controls.ig2.control_codebuild_security import (
146
+ CodeBuildProjectEnvironmentPrivilegedCheckAssessment, CodeBuildProjectEnvVarAWSCredCheckAssessment,
147
+ CodeBuildProjectSourceRepoURLCheckAssessment
148
+ )
149
+ from aws_cis_assessment.controls.ig2.control_remaining_rules import (
150
+ ACMCertificateExpirationCheckAssessment, DynamoDBAutoScalingEnabledAssessment,
151
+ RedshiftEnhancedVPCRoutingEnabledAssessment, RestrictedCommonPortsAssessment,
152
+ AuditLogPolicyExistsAssessment
153
+ )
154
+ from aws_cis_assessment.controls.ig3.control_3_14 import (
155
+ APIGatewayExecutionLoggingEnabledAssessment, CloudTrailS3DataEventsEnabledAssessment,
156
+ MultiRegionCloudTrailEnabledAssessment, CloudTrailCloudWatchLogsEnabledAssessment
157
+ )
158
+ from aws_cis_assessment.controls.ig3.control_7_1 import (
159
+ ECRPrivateImageScanningEnabledAssessment, GuardDutyEnabledCentralizedAssessment,
160
+ EC2ManagedInstancePatchComplianceAssessment
161
+ )
162
+ from aws_cis_assessment.controls.ig3.control_12_8 import (
163
+ APIGatewayAssociatedWithWAFAssessment, VPCSecurityGroupOpenOnlyToAuthorizedPortsAssessment,
164
+ NoUnrestrictedRouteToIGWAssessment
165
+ )
166
+ from aws_cis_assessment.controls.ig3.control_13_1 import (
167
+ RestrictedIncomingTrafficAssessment, IncomingSSHDisabledAssessment,
168
+ VPCFlowLogsEnabledAssessment
169
+ )
170
+
171
+ logger = logging.getLogger(__name__)
172
+
173
+
174
+ @dataclass
175
+ class AssessmentProgress:
176
+ """Progress tracking for assessment execution."""
177
+ total_controls: int = 0
178
+ completed_controls: int = 0
179
+ total_regions: int = 0
180
+ completed_regions: int = 0
181
+ current_control: str = ""
182
+ current_region: str = ""
183
+ start_time: Optional[datetime] = None
184
+ estimated_completion: Optional[datetime] = None
185
+ errors: List[str] = None
186
+ memory_usage_mb: float = 0.0
187
+ peak_memory_mb: float = 0.0
188
+ active_threads: int = 0
189
+
190
+ def __post_init__(self):
191
+ if self.errors is None:
192
+ self.errors = []
193
+
194
+ @property
195
+ def progress_percentage(self) -> float:
196
+ """Calculate overall progress percentage."""
197
+ if self.total_controls == 0:
198
+ return 0.0
199
+ return (self.completed_controls / self.total_controls) * 100
200
+
201
+ @property
202
+ def elapsed_time(self) -> Optional[timedelta]:
203
+ """Calculate elapsed time since start."""
204
+ if self.start_time is None:
205
+ return None
206
+ return datetime.now() - self.start_time
207
+
208
+
209
+ @dataclass
210
+ class ResourceUsageStats:
211
+ """Resource usage statistics for performance monitoring."""
212
+ peak_memory_mb: float = 0.0
213
+ current_memory_mb: float = 0.0
214
+ cpu_time_seconds: float = 0.0
215
+ active_connections: int = 0
216
+ total_api_calls: int = 0
217
+ failed_api_calls: int = 0
218
+ avg_response_time_ms: float = 0.0
219
+
220
+ def update_memory_usage(self):
221
+ """Update current memory usage."""
222
+ try:
223
+ # Try to get memory usage from resource module (Unix-like systems)
224
+ memory_kb = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
225
+ if sys.platform == 'darwin': # macOS reports in bytes
226
+ self.current_memory_mb = memory_kb / 1024 / 1024
227
+ else: # Linux reports in KB
228
+ self.current_memory_mb = memory_kb / 1024
229
+
230
+ if self.current_memory_mb > self.peak_memory_mb:
231
+ self.peak_memory_mb = self.current_memory_mb
232
+ except:
233
+ # Fallback - try psutil if available
234
+ try:
235
+ import psutil
236
+ process = psutil.Process()
237
+ memory_info = process.memory_info()
238
+ self.current_memory_mb = memory_info.rss / 1024 / 1024
239
+ if self.current_memory_mb > self.peak_memory_mb:
240
+ self.peak_memory_mb = self.current_memory_mb
241
+ except ImportError:
242
+ pass
243
+
244
+
245
+ class AssessmentEngine:
246
+ """Orchestrates the entire compliance assessment process."""
247
+
248
+ def __init__(self, aws_credentials: Optional[Dict[str, str]] = None,
249
+ regions: Optional[List[str]] = None,
250
+ config_path: Optional[str] = None,
251
+ max_workers: int = 4,
252
+ progress_callback: Optional[Callable[[AssessmentProgress], None]] = None,
253
+ enable_error_recovery: bool = True,
254
+ enable_audit_trail: bool = True,
255
+ timeout: int = 3600,
256
+ memory_limit_mb: Optional[int] = None,
257
+ enable_resource_monitoring: bool = True):
258
+ """Initialize assessment engine with AWS credentials and configuration.
259
+
260
+ Args:
261
+ aws_credentials: Optional AWS credentials dict
262
+ regions: List of AWS regions to assess. If None, uses default regions.
263
+ config_path: Path to CIS Controls configuration files
264
+ max_workers: Maximum number of parallel workers for assessment
265
+ progress_callback: Optional callback function for progress updates
266
+ enable_error_recovery: Whether to enable error recovery mechanisms
267
+ enable_audit_trail: Whether to enable audit trail logging
268
+ timeout: Assessment timeout in seconds (default: 3600)
269
+ memory_limit_mb: Optional memory limit in MB for resource management
270
+ enable_resource_monitoring: Whether to enable resource usage monitoring
271
+ """
272
+ self.aws_factory = AWSClientFactory(aws_credentials, regions)
273
+ self.config_loader = ConfigRuleLoader(config_path)
274
+ self.scoring_engine = ScoringEngine()
275
+ self.max_workers = max_workers
276
+ self.timeout = timeout
277
+ self.memory_limit_mb = memory_limit_mb
278
+ self.enable_resource_monitoring = enable_resource_monitoring
279
+ self.progress_callback = progress_callback
280
+ self.progress = AssessmentProgress()
281
+ self.resource_stats = ResourceUsageStats()
282
+ self._progress_lock = threading.Lock()
283
+ self._resource_lock = threading.Lock()
284
+ self._client_cache = weakref.WeakValueDictionary() # Weak references for memory management
285
+
286
+ # Initialize error handling and audit trail
287
+ self.error_handler = ErrorHandler() if enable_error_recovery else None
288
+ self.audit_trail = AuditTrail() if enable_audit_trail else None
289
+
290
+ # Initialize control assessment registry
291
+ self._assessment_registry = self._build_assessment_registry()
292
+
293
+ # Start resource monitoring thread if enabled
294
+ self._monitoring_active = False
295
+ self._monitoring_thread = None
296
+ if self.enable_resource_monitoring:
297
+ self._start_resource_monitoring()
298
+
299
+ logger.info(f"Assessment Engine initialized for regions: {self.aws_factory.regions}")
300
+ logger.info(f"Performance settings: max_workers={max_workers}, memory_limit={memory_limit_mb}MB")
301
+
302
+ if self.audit_trail:
303
+ self.audit_trail.log_event(
304
+ event_type=AuditEventType.CONFIGURATION_LOAD,
305
+ message="Assessment Engine initialized",
306
+ details={
307
+ "regions": self.aws_factory.regions,
308
+ "max_workers": max_workers,
309
+ "memory_limit_mb": memory_limit_mb,
310
+ "resource_monitoring": enable_resource_monitoring,
311
+ "error_recovery_enabled": enable_error_recovery,
312
+ "audit_trail_enabled": enable_audit_trail
313
+ }
314
+ )
315
+
316
+ def _build_assessment_registry(self) -> Dict[str, Dict[str, BaseConfigRuleAssessment]]:
317
+ """Build registry of all available control assessments."""
318
+ registry = {
319
+ 'IG1': {
320
+ # Control 1.1 - Asset Inventory
321
+ 'eip-attached': EIPAttachedAssessment(),
322
+ 'ec2-stopped-instance': EC2StoppedInstanceAssessment(),
323
+ 'vpc-network-acl-unused-check': VPCNetworkACLUnusedAssessment(),
324
+ 'ec2-instance-managed-by-systems-manager': EC2InstanceManagedBySSMAssessment(),
325
+ 'ec2-security-group-attached-to-eni': EC2SecurityGroupAttachedAssessment(),
326
+
327
+ # Control 2.2 - Authorized Software Support
328
+ 'elastic-beanstalk-managed-updates-enabled': ElasticBeanstalkManagedUpdatesEnabledAssessment(),
329
+ 'ecs-fargate-latest-platform-version': ECSFargateLatestPlatformVersionAssessment(),
330
+
331
+ # Control 3.3 - Data Access Control
332
+ 'iam-password-policy': IAMPasswordPolicyAssessment(),
333
+ 'iam-user-mfa-enabled': IAMUserMFAEnabledAssessment(),
334
+ 'iam-root-access-key-check': IAMRootAccessKeyAssessment(),
335
+ 's3-bucket-public-read-prohibited': S3BucketPublicReadProhibitedAssessment(),
336
+ 'ec2-instance-no-public-ip': EC2InstanceNoPublicIPAssessment(),
337
+ 'ec2-imdsv2-check': EC2IMDSv2CheckAssessment(),
338
+ 'ec2-instance-profile-attached': EC2InstanceProfileAttachedAssessment(),
339
+ 'iam-policy-no-statements-with-admin-access': IAMPolicyNoStatementsWithAdminAccessAssessment(),
340
+ 'iam-no-inline-policy-check': IAMNoInlinePolicyCheckAssessment(),
341
+ 'iam-user-group-membership-check': IAMUserGroupMembershipCheckAssessment(),
342
+ 'ebs-snapshot-public-restorable-check': EBSSnapshotPublicRestorableCheckAssessment(),
343
+ 'rds-snapshots-public-prohibited': RDSSnapshotsPublicProhibitedAssessment(),
344
+ 'rds-instance-public-access-check': RDSInstancePublicAccessCheckAssessment(),
345
+ 'redshift-cluster-public-access-check': RedshiftClusterPublicAccessCheckAssessment(),
346
+ 's3-bucket-level-public-access-prohibited': S3BucketLevelPublicAccessProhibitedAssessment(),
347
+ 'dms-replication-not-public': DMSReplicationNotPublicAssessment(),
348
+ 'elasticsearch-in-vpc-only': ElasticsearchInVPCOnlyAssessment(),
349
+ 'ec2-instances-in-vpc': EC2InstancesInVPCAssessment(),
350
+ 'emr-master-no-public-ip': EMRMasterNoPublicIPAssessment(),
351
+ 'lambda-function-public-access-prohibited': LambdaFunctionPublicAccessProhibitedAssessment(),
352
+ 'sagemaker-notebook-no-direct-internet-access': SageMakerNotebookNoDirectInternetAccessAssessment(),
353
+ 'subnet-auto-assign-public-ip-disabled': SubnetAutoAssignPublicIPDisabledAssessment(),
354
+ 'iam-group-has-users-check': IAMGroupHasUsersCheckAssessment(),
355
+ 'iam-policy-no-statements-with-full-access': IAMPolicyNoStatementsWithFullAccessAssessment(),
356
+ 'iam-user-no-policies-check': IAMUserNoPoliciesCheckAssessment(),
357
+ 'ssm-document-not-public': SSMDocumentNotPublicAssessment(),
358
+ 'ec2-managedinstance-association-compliance-status-check': EC2ManagedInstanceAssociationComplianceStatusCheckAssessment(),
359
+ 'emr-kerberos-enabled': EMRKerberosEnabledAssessment(),
360
+ 'lambda-inside-vpc': LambdaInsideVPCAssessment(),
361
+ 'ecs-task-definition-user-for-host-mode-check': ECSTaskDefinitionUserForHostModeCheckAssessment(),
362
+ 'iam-root-access-key-check': IAMRootAccessKeyCheckAssessment(),
363
+ 'iam-user-unused-credentials-check': IAMUserUnusedCredentialsCheckAssessment(),
364
+ 'iam-customer-policy-blocked-kms-actions': IAMCustomerPolicyBlockedKMSActionsAssessment(),
365
+ 'iam-inline-policy-blocked-kms-actions': IAMInlinePolicyBlockedKMSActionsAssessment(),
366
+
367
+ # Control 3.4 - Data Retention
368
+ 's3-version-lifecycle-policy-check': S3VersionLifecyclePolicyAssessment(),
369
+ 'cw-loggroup-retention-period-check': CloudWatchLogGroupRetentionAssessment(),
370
+
371
+ # Control 4.1 - Secure Configuration
372
+ 'account-part-of-organizations': AccountPartOfOrganizationsAssessment(),
373
+ 'ec2-volume-inuse-check': EC2VolumeInUseAssessment(),
374
+ 'redshift-cluster-maintenancesettings-check': RedshiftClusterMaintenanceSettingsAssessment(),
375
+ 'secretsmanager-rotation-enabled-check': SecretsManagerRotationEnabledAssessment(),
376
+ 'access-keys-rotated': AccessKeysRotatedAssessment(),
377
+
378
+ # Control 11.2 - Backup Management
379
+ 's3-bucket-versioning-enabled': S3BucketVersioningEnabledAssessment(),
380
+
381
+ # S3 Security Controls (Phase 5)
382
+ 's3-bucket-ssl-requests-only': S3BucketSSLRequestsOnlyAssessment(),
383
+ 's3-bucket-server-side-encryption-enabled': S3BucketServerSideEncryptionEnabledAssessment(),
384
+ 's3-bucket-logging-enabled': S3BucketLoggingEnabledAssessment(),
385
+
386
+ # CloudTrail & Logging Controls (Phase 6)
387
+ 'cloudtrail-enabled': CloudTrailEnabledAssessment(),
388
+ 'cloudwatch-log-group-encrypted': CloudWatchLogGroupEncryptedAssessment(),
389
+
390
+ # VPC Security Controls (Phase 6)
391
+ 'vpc-default-security-group-closed': VPCDefaultSecurityGroupClosedAssessment(),
392
+ 'restricted-ssh': RestrictedSSHAssessment(),
393
+
394
+ # Critical Security Controls
395
+ 'root-account-hardware-mfa-enabled': RootAccountHardwareMFAEnabledAssessment(),
396
+ 'opensearch-in-vpc-only': OpenSearchInVPCOnlyAssessment(),
397
+ 'ecs-task-definition-nonroot-user': ECSTaskDefinitionNonRootUserAssessment(),
398
+ 'securityhub-enabled': SecurityHubEnabledAssessment(),
399
+
400
+ # Network Security Enhancements
401
+ 'elasticsearch-node-to-node-encryption-check': ElasticsearchNodeToNodeEncryptionCheckAssessment(),
402
+ 'autoscaling-launch-config-public-ip-disabled': AutoScalingLaunchConfigPublicIPDisabledAssessment(),
403
+ 'efs-access-point-enforce-root-directory': EFSAccessPointEnforceRootDirectoryAssessment(),
404
+
405
+ # Backup & Recovery Controls
406
+ 'dynamodb-in-backup-plan': DynamoDBInBackupPlanAssessment(),
407
+ 'ebs-in-backup-plan': EBSInBackupPlanAssessment(),
408
+ 'efs-in-backup-plan': EFSInBackupPlanAssessment(),
409
+ 'db-instance-backup-enabled': DBInstanceBackupEnabledAssessment(),
410
+ 'redshift-backup-enabled': RedshiftBackupEnabledAssessment(),
411
+ 'dynamodb-pitr-enabled': DynamoDBPITREnabledAssessment(),
412
+ 'elasticache-redis-cluster-automatic-backup-check': ElastiCacheRedisClusterAutomaticBackupCheckAssessment(),
413
+ 's3-bucket-replication-enabled': S3BucketReplicationEnabledAssessment(),
414
+
415
+ # S3 Security Enhancements
416
+ 's3-account-level-public-access-blocks-periodic': S3AccountLevelPublicAccessBlocksPeriodicAssessment(),
417
+ 's3-bucket-public-write-prohibited': S3BucketPublicWriteProhibitedAssessment(),
418
+
419
+ # Instance Optimization
420
+ 'ebs-optimized-instance': EBSOptimizedInstanceAssessment(),
421
+ },
422
+ 'IG2': {
423
+ # Control 3.10 - Encryption in Transit
424
+ 'api-gw-ssl-enabled': APIGatewaySSLEnabledAssessment(),
425
+ 'alb-http-to-https-redirection-check': ALBHTTPToHTTPSRedirectionAssessment(),
426
+ 'elb-tls-https-listeners-only': ELBTLSHTTPSListenersOnlyAssessment(),
427
+ 'redshift-require-tls-ssl': RedshiftRequireTLSSSLAssessment(),
428
+ 'elb-acm-certificate-required': ELBACMCertificateRequiredAssessment(),
429
+ 'elbv2-acm-certificate-required': ELBv2ACMCertificateRequiredAssessment(),
430
+ 'opensearch-https-required': OpenSearchHTTPSRequiredAssessment(),
431
+
432
+ # Control 3.11 - Encryption at Rest
433
+ 'encrypted-volumes': EncryptedVolumesAssessment(),
434
+ 'rds-storage-encrypted': RDSStorageEncryptedAssessment(),
435
+ 's3-default-encryption-kms': S3DefaultEncryptionKMSAssessment(),
436
+ 'dynamodb-table-encrypted-kms': DynamoDBTableEncryptedKMSAssessment(),
437
+ 'backup-recovery-point-encrypted': BackupRecoveryPointEncryptedAssessment(),
438
+ 'cloud-trail-encryption-enabled': CloudTrailEncryptionEnabledAssessment(),
439
+ 'efs-encrypted-check': EFSEncryptedCheckIG2Assessment(),
440
+ 'ec2-ebs-encryption-by-default': EC2EBSEncryptionByDefaultAssessment(),
441
+ 'rds-snapshot-encrypted': RDSSnapshotEncryptedAssessment(),
442
+
443
+ # Control 5.2 - Password Management
444
+ 'mfa-enabled-for-iam-console-access': MFAEnabledForIAMConsoleAccessAssessment(),
445
+ 'root-account-mfa-enabled': RootAccountMFAEnabledAssessment(),
446
+ 'iam-user-unused-credentials-check': IAMUserUnusedCredentialsAssessment(),
447
+
448
+ # Advanced Encryption Controls
449
+ 'secretsmanager-using-cmk': SecretsManagerUsingCMKAssessment(),
450
+ 'sns-encrypted-kms': SNSEncryptedKMSAssessment(),
451
+ 'sqs-queue-encrypted-kms': SQSQueueEncryptedKMSAssessment(),
452
+ 'kinesis-stream-encrypted': KinesisStreamEncryptedAssessment(),
453
+ 'elasticsearch-encrypted-at-rest': ElasticsearchEncryptedAtRestAssessment(),
454
+
455
+ # Service Logging Controls
456
+ 'elasticsearch-logs-to-cloudwatch': ElasticsearchLogsToCloudWatchAssessment(),
457
+ 'elb-logging-enabled': ELBLoggingEnabledAssessment(),
458
+ 'rds-logging-enabled': RDSLoggingEnabledAssessment(),
459
+ 'wafv2-logging-enabled': WAFv2LoggingEnabledAssessment(),
460
+ 'codebuild-project-logging-enabled': CodeBuildProjectLoggingEnabledAssessment(),
461
+ 'redshift-cluster-configuration-check': RedshiftClusterConfigurationCheckAssessment(),
462
+
463
+ # Remaining Encryption Controls
464
+ 'opensearch-encrypted-at-rest': OpenSearchEncryptedAtRestAssessment(),
465
+ 'opensearch-node-to-node-encryption-check': OpenSearchNodeToNodeEncryptionCheckAssessment(),
466
+ 'redshift-cluster-kms-enabled': RedshiftClusterKMSEnabledAssessment(),
467
+ 'sagemaker-endpoint-configuration-kms-key-configured': SageMakerEndpointConfigurationKMSKeyConfiguredAssessment(),
468
+ 'sagemaker-notebook-instance-kms-key-configured': SageMakerNotebookInstanceKMSKeyConfiguredAssessment(),
469
+ 'codebuild-project-artifact-encryption': CodeBuildProjectArtifactEncryptionAssessment(),
470
+
471
+ # Network/HA Controls
472
+ 'elb-cross-zone-load-balancing-enabled': ELBCrossZoneLoadBalancingEnabledAssessment(),
473
+ 'elb-deletion-protection-enabled': ELBDeletionProtectionEnabledAssessment(),
474
+ 'elbv2-multiple-az': ELBv2MultipleAZAssessment(),
475
+ 'rds-cluster-multi-az-enabled': RDSClusterMultiAZEnabledAssessment(),
476
+ 'rds-instance-deletion-protection-enabled': RDSInstanceDeletionProtectionEnabledAssessment(),
477
+ 'rds-multi-az-support': RDSMultiAZSupportAssessment(),
478
+ 'vpc-vpn-2-tunnels-up': VPCVPNTwoTunnelsUpAssessment(),
479
+
480
+ # CodeBuild Security Controls
481
+ 'codebuild-project-environment-privileged-check': CodeBuildProjectEnvironmentPrivilegedCheckAssessment(),
482
+ 'codebuild-project-envvar-awscred-check': CodeBuildProjectEnvVarAWSCredCheckAssessment(),
483
+ 'codebuild-project-source-repo-url-check': CodeBuildProjectSourceRepoURLCheckAssessment(),
484
+
485
+ # Remaining Rules
486
+ 'acm-certificate-expiration-check': ACMCertificateExpirationCheckAssessment(),
487
+ 'dynamodb-autoscaling-enabled': DynamoDBAutoScalingEnabledAssessment(),
488
+ 'redshift-enhanced-vpc-routing-enabled': RedshiftEnhancedVPCRoutingEnabledAssessment(),
489
+ 'restricted-common-ports': RestrictedCommonPortsAssessment(),
490
+ 'audit-log-policy-exists (Process check)': AuditLogPolicyExistsAssessment(),
491
+ },
492
+ 'IG3': {
493
+ # Control 3.14 - Sensitive Data Logging
494
+ 'api-gw-execution-logging-enabled': APIGatewayExecutionLoggingEnabledAssessment(),
495
+ 'cloudtrail-s3-dataevents-enabled': CloudTrailS3DataEventsEnabledAssessment(),
496
+ 'multi-region-cloudtrail-enabled': MultiRegionCloudTrailEnabledAssessment(),
497
+ 'cloud-trail-cloud-watch-logs-enabled': CloudTrailCloudWatchLogsEnabledAssessment(),
498
+
499
+ # Control 7.1 - Vulnerability Management
500
+ 'ecr-private-image-scanning-enabled': ECRPrivateImageScanningEnabledAssessment(),
501
+ 'guardduty-enabled-centralized': GuardDutyEnabledCentralizedAssessment(),
502
+ 'ec2-managedinstance-patch-compliance-status-check': EC2ManagedInstancePatchComplianceAssessment(),
503
+
504
+ # Control 12.8 - Network Segmentation
505
+ 'api-gw-associated-with-waf': APIGatewayAssociatedWithWAFAssessment(),
506
+ 'vpc-sg-open-only-to-authorized-ports': VPCSecurityGroupOpenOnlyToAuthorizedPortsAssessment(),
507
+ 'no-unrestricted-route-to-igw': NoUnrestrictedRouteToIGWAssessment(),
508
+
509
+ # Control 13.1 - Network Monitoring
510
+ 'restricted-incoming-traffic': RestrictedIncomingTrafficAssessment(),
511
+ 'incoming-ssh-disabled': IncomingSSHDisabledAssessment(),
512
+ 'vpc-flow-logs-enabled': VPCFlowLogsEnabledAssessment(),
513
+ }
514
+ }
515
+
516
+ # Add IG1 assessments to IG2 and IG3 (inheritance)
517
+ # IG2 includes all IG1 controls plus its own
518
+ registry['IG2'].update(registry['IG1'])
519
+ # IG3 includes all IG2 controls (which already includes IG1) plus its own
520
+ registry['IG3'].update(registry['IG2'])
521
+
522
+ return registry
523
+
524
+ def _start_resource_monitoring(self):
525
+ """Start background resource monitoring thread."""
526
+ self._monitoring_active = True
527
+ self._monitoring_thread = threading.Thread(target=self._monitor_resources, daemon=True)
528
+ self._monitoring_thread.start()
529
+ logger.debug("Resource monitoring thread started")
530
+
531
+ def _stop_resource_monitoring(self):
532
+ """Stop background resource monitoring thread."""
533
+ self._monitoring_active = False
534
+ if self._monitoring_thread and self._monitoring_thread.is_alive():
535
+ self._monitoring_thread.join(timeout=1.0)
536
+ logger.debug("Resource monitoring thread stopped")
537
+
538
+ def _monitor_resources(self):
539
+ """Background thread to monitor resource usage."""
540
+ while self._monitoring_active:
541
+ try:
542
+ with self._resource_lock:
543
+ self.resource_stats.update_memory_usage()
544
+
545
+ # Update progress with current memory usage
546
+ with self._progress_lock:
547
+ self.progress.memory_usage_mb = self.resource_stats.current_memory_mb
548
+ self.progress.peak_memory_mb = self.resource_stats.peak_memory_mb
549
+
550
+ # Check memory limit if set
551
+ if (self.memory_limit_mb and
552
+ self.resource_stats.current_memory_mb > self.memory_limit_mb):
553
+ logger.warning(f"Memory usage ({self.resource_stats.current_memory_mb:.1f}MB) "
554
+ f"exceeds limit ({self.memory_limit_mb}MB). Triggering garbage collection.")
555
+ self._optimize_memory_usage()
556
+
557
+ time.sleep(1.0) # Monitor every second
558
+ except Exception as e:
559
+ logger.debug(f"Resource monitoring error: {e}")
560
+ time.sleep(5.0) # Back off on errors
561
+
562
+ def _optimize_memory_usage(self):
563
+ """Optimize memory usage by cleaning up resources."""
564
+ try:
565
+ # Force garbage collection
566
+ collected = gc.collect()
567
+ logger.debug(f"Garbage collection freed {collected} objects")
568
+
569
+ # Clear weak reference cache
570
+ self._client_cache.clear()
571
+
572
+ # Clear any cached data in AWS factory
573
+ if hasattr(self.aws_factory, '_clients'):
574
+ # Keep only essential clients, clear others
575
+ essential_services = ['sts', 'ec2', 'iam']
576
+ clients_to_remove = []
577
+ for key in self.aws_factory._clients.keys():
578
+ service_name = key.split('_')[0]
579
+ if service_name not in essential_services:
580
+ clients_to_remove.append(key)
581
+
582
+ for key in clients_to_remove:
583
+ self.aws_factory._clients.pop(key, None)
584
+
585
+ logger.debug(f"Cleared {len(clients_to_remove)} cached AWS clients")
586
+
587
+ # Update memory stats after cleanup
588
+ self.resource_stats.update_memory_usage()
589
+
590
+ except Exception as e:
591
+ logger.warning(f"Memory optimization failed: {e}")
592
+
593
+ def get_resource_stats(self) -> ResourceUsageStats:
594
+ """Get current resource usage statistics.
595
+
596
+ Returns:
597
+ ResourceUsageStats object with current metrics
598
+ """
599
+ with self._resource_lock:
600
+ self.resource_stats.update_memory_usage()
601
+ return ResourceUsageStats(
602
+ peak_memory_mb=self.resource_stats.peak_memory_mb,
603
+ current_memory_mb=self.resource_stats.current_memory_mb,
604
+ cpu_time_seconds=self.resource_stats.cpu_time_seconds,
605
+ active_connections=self.resource_stats.active_connections,
606
+ total_api_calls=self.resource_stats.total_api_calls,
607
+ failed_api_calls=self.resource_stats.failed_api_calls,
608
+ avg_response_time_ms=self.resource_stats.avg_response_time_ms
609
+ )
610
+
611
+ def run_assessment(self, implementation_groups: Optional[List[str]] = None,
612
+ controls: Optional[List[str]] = None,
613
+ exclude_controls: Optional[List[str]] = None) -> AssessmentResult:
614
+ """Execute compliance assessment for specified Implementation Groups.
615
+
616
+ Args:
617
+ implementation_groups: List of IGs to assess (IG1, IG2, IG3). If None, assesses all.
618
+ controls: List of specific control IDs to assess. If specified, overrides implementation_groups.
619
+ exclude_controls: List of control IDs to exclude from assessment.
620
+
621
+ Returns:
622
+ AssessmentResult with complete assessment data
623
+ """
624
+ if implementation_groups is None:
625
+ implementation_groups = ['IG1', 'IG2', 'IG3']
626
+
627
+ # Validate implementation groups
628
+ valid_igs = [ig.value for ig in ImplementationGroup]
629
+ for ig in implementation_groups:
630
+ if ig not in valid_igs:
631
+ raise ValueError(f"Invalid implementation group: {ig}. Valid options: {valid_igs}")
632
+
633
+ logger.info(f"Starting assessment for Implementation Groups: {implementation_groups}")
634
+ if controls:
635
+ logger.info(f"Specific controls requested: {controls}")
636
+ if exclude_controls:
637
+ logger.info(f"Excluded controls: {exclude_controls}")
638
+
639
+ # Log assessment start
640
+ if self.audit_trail:
641
+ account_info = self.aws_factory.get_account_info()
642
+ account_id = account_info.get('account_id', 'unknown')
643
+ self.audit_trail.log_assessment_start(
644
+ account_id=account_id,
645
+ regions=self.aws_factory.regions,
646
+ implementation_groups=implementation_groups
647
+ )
648
+
649
+ # Initialize progress tracking
650
+ with self._progress_lock:
651
+ self.progress = AssessmentProgress(
652
+ start_time=datetime.now(),
653
+ total_regions=len(self.aws_factory.regions)
654
+ )
655
+
656
+ # Count total controls to assess
657
+ total_controls = 0
658
+ for ig in implementation_groups:
659
+ if ig in self._assessment_registry:
660
+ ig_controls = self._filter_controls_for_ig(ig, controls, exclude_controls)
661
+ total_controls += len(ig_controls)
662
+ self.progress.total_controls = total_controls
663
+
664
+ self._update_progress()
665
+
666
+ try:
667
+ # Validate AWS credentials with error handling
668
+ credential_validation_start = time.time()
669
+
670
+ if not self.aws_factory.validate_credentials():
671
+ raise RuntimeError("AWS credential validation failed")
672
+
673
+ # Log credential validation
674
+ if self.audit_trail:
675
+ validation_duration = int((time.time() - credential_validation_start) * 1000)
676
+ self.audit_trail.log_event(
677
+ event_type=AuditEventType.CREDENTIAL_VALIDATION,
678
+ status="SUCCESS",
679
+ message="AWS credentials validated successfully",
680
+ duration_ms=validation_duration
681
+ )
682
+
683
+ account_info = self.aws_factory.get_account_info()
684
+ account_id = account_info.get('account_id', 'unknown')
685
+
686
+ # Execute assessments for each Implementation Group
687
+ ig_scores = {}
688
+ all_results = []
689
+
690
+ for ig in implementation_groups:
691
+ logger.info(f"Assessing Implementation Group: {ig}")
692
+
693
+ ig_results = self._assess_implementation_group(ig, controls, exclude_controls)
694
+ all_results.extend(ig_results)
695
+
696
+ # Calculate IG score using scoring engine
697
+ ig_score = self._calculate_ig_score(ig, ig_results)
698
+ ig_scores[ig] = ig_score
699
+
700
+ logger.info(f"Completed {ig} assessment: {ig_score.compliance_percentage:.1f}% compliant")
701
+
702
+ # Calculate overall score using scoring engine
703
+ overall_score = self.scoring_engine.calculate_overall_score(ig_scores)
704
+
705
+ # Create final assessment result
706
+ assessment_result = AssessmentResult(
707
+ account_id=account_id,
708
+ regions_assessed=self.aws_factory.regions.copy(),
709
+ timestamp=datetime.now(),
710
+ overall_score=overall_score,
711
+ ig_scores=ig_scores,
712
+ total_resources_evaluated=len(all_results),
713
+ assessment_duration=self.progress.elapsed_time
714
+ )
715
+
716
+ # Log assessment completion
717
+ if self.audit_trail:
718
+ self.audit_trail.log_assessment_complete(
719
+ account_id=account_id,
720
+ overall_score=overall_score,
721
+ total_resources=len(all_results),
722
+ duration=self.progress.elapsed_time or timedelta(0)
723
+ )
724
+
725
+ logger.info(f"Assessment completed. Overall compliance: {overall_score:.1f}%")
726
+ logger.info(f"Resource usage: Peak memory {self.resource_stats.peak_memory_mb:.1f}MB")
727
+
728
+ return assessment_result
729
+
730
+ except Exception as e:
731
+ logger.error(f"Assessment failed: {e}")
732
+
733
+ # Log assessment error
734
+ if self.audit_trail:
735
+ self.audit_trail.log_event(
736
+ event_type=AuditEventType.ASSESSMENT_ERROR,
737
+ status="FAILURE",
738
+ message=f"Assessment failed: {str(e)}",
739
+ details={"exception_type": type(e).__name__}
740
+ )
741
+
742
+ # Handle error with error handler
743
+ if self.error_handler:
744
+ context = ErrorContext(operation="assessment_execution")
745
+ self.error_handler.handle_error(e, context)
746
+
747
+ with self._progress_lock:
748
+ self.progress.errors.append(f"Assessment failed: {str(e)}")
749
+ self._update_progress()
750
+ raise
751
+
752
+ finally:
753
+ # Cleanup resources
754
+ self._cleanup_resources()
755
+
756
+ def _assess_implementation_group(self, implementation_group: str,
757
+ controls: Optional[List[str]] = None,
758
+ exclude_controls: Optional[List[str]] = None) -> List[ComplianceResult]:
759
+ """Assess all controls for a specific Implementation Group.
760
+
761
+ Args:
762
+ implementation_group: IG1, IG2, or IG3
763
+ controls: List of specific control IDs to assess
764
+ exclude_controls: List of control IDs to exclude
765
+
766
+ Returns:
767
+ List of ComplianceResult objects
768
+ """
769
+ if implementation_group not in self._assessment_registry:
770
+ logger.warning(f"No assessments found for Implementation Group: {implementation_group}")
771
+ return []
772
+
773
+ # Filter assessments based on criteria
774
+ assessments = self._filter_controls_for_ig(implementation_group, controls, exclude_controls)
775
+ all_results = []
776
+
777
+ # Calculate optimal batch size based on memory constraints
778
+ batch_size = self._calculate_optimal_batch_size(len(assessments))
779
+ assessment_items = list(assessments.items())
780
+
781
+ # Process assessments in batches to manage memory usage
782
+ for batch_start in range(0, len(assessment_items), batch_size):
783
+ batch_end = min(batch_start + batch_size, len(assessment_items))
784
+ batch_assessments = dict(assessment_items[batch_start:batch_end])
785
+
786
+ logger.debug(f"Processing batch {batch_start//batch_size + 1}: "
787
+ f"{len(batch_assessments)} assessments")
788
+
789
+ # Use ThreadPoolExecutor for parallel assessment execution within batch
790
+ with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
791
+ # Submit assessment tasks for current batch
792
+ future_to_assessment = {}
793
+
794
+ for rule_name, assessment in batch_assessments.items():
795
+ for region in self.aws_factory.regions:
796
+ future = executor.submit(
797
+ self._run_single_assessment, assessment, region, rule_name
798
+ )
799
+ future_to_assessment[future] = (rule_name, region)
800
+
801
+ # Update active threads count
802
+ with self._progress_lock:
803
+ self.progress.active_threads = len(future_to_assessment)
804
+
805
+ # Collect results as they complete
806
+ batch_results = []
807
+ for future in as_completed(future_to_assessment):
808
+ rule_name, region = future_to_assessment[future]
809
+
810
+ try:
811
+ results = future.result()
812
+ batch_results.extend(results)
813
+
814
+ with self._progress_lock:
815
+ self.progress.completed_controls += 1
816
+ self.progress.current_control = rule_name
817
+ self.progress.current_region = region
818
+ self.progress.active_threads = len([f for f in future_to_assessment if not f.done()])
819
+
820
+ self._update_progress()
821
+
822
+ logger.debug(f"Completed assessment: {rule_name} in {region} ({len(results)} results)")
823
+
824
+ except Exception as e:
825
+ error_msg = f"Assessment failed for {rule_name} in {region}: {str(e)}"
826
+ logger.error(error_msg)
827
+
828
+ with self._progress_lock:
829
+ self.progress.errors.append(error_msg)
830
+ self.progress.completed_controls += 1
831
+
832
+ self._update_progress()
833
+
834
+ # Add batch results to overall results
835
+ all_results.extend(batch_results)
836
+
837
+ # Optimize memory usage between batches
838
+ if batch_end < len(assessment_items):
839
+ self._optimize_memory_usage()
840
+ logger.debug(f"Completed batch {batch_start//batch_size + 1}, "
841
+ f"memory usage: {self.resource_stats.current_memory_mb:.1f}MB")
842
+
843
+ return all_results
844
+
845
+ def _calculate_optimal_batch_size(self, total_assessments: int) -> int:
846
+ """Calculate optimal batch size based on available resources.
847
+
848
+ Args:
849
+ total_assessments: Total number of assessments to process
850
+
851
+ Returns:
852
+ Optimal batch size for memory management
853
+ """
854
+ # Base batch size on memory constraints and worker count
855
+ base_batch_size = max(self.max_workers * 2, 10) # At least 2x workers, minimum 10
856
+
857
+ if self.memory_limit_mb:
858
+ # Estimate memory per assessment (rough heuristic)
859
+ estimated_memory_per_assessment = 5 # MB
860
+ max_assessments_for_memory = self.memory_limit_mb // estimated_memory_per_assessment
861
+ base_batch_size = min(base_batch_size, max_assessments_for_memory)
862
+
863
+ # Don't exceed total assessments
864
+ return min(base_batch_size, total_assessments)
865
+
866
+ def _run_single_assessment(self, assessment: BaseConfigRuleAssessment,
867
+ region: str, rule_name: str) -> List[ComplianceResult]:
868
+ """Run a single assessment in a specific region with error handling.
869
+
870
+ Args:
871
+ assessment: Assessment instance to run
872
+ region: AWS region
873
+ rule_name: Name of the Config rule
874
+
875
+ Returns:
876
+ List of ComplianceResult objects
877
+ """
878
+ assessment_start_time = time.time()
879
+
880
+ try:
881
+ logger.debug(f"Starting assessment: {rule_name} in region {region}")
882
+
883
+ # Check service availability if error handler is enabled
884
+ if self.error_handler:
885
+ required_services = assessment._get_required_services()
886
+ for service in required_services:
887
+ if not self.error_handler.is_service_available(service, region):
888
+ logger.warning(f"Service {service} marked as unavailable in {region}, skipping {rule_name}")
889
+ return [ComplianceResult(
890
+ resource_id=f"SKIPPED_{rule_name}_{region}",
891
+ resource_type="SKIPPED",
892
+ compliance_status=ComplianceStatus.NOT_APPLICABLE,
893
+ evaluation_reason=f"Service {service} unavailable in region {region}",
894
+ config_rule_name=rule_name,
895
+ region=region
896
+ )]
897
+
898
+ # Execute assessment with error handling
899
+ results = assessment.evaluate_compliance(self.aws_factory, region)
900
+
901
+ assessment_duration = int((time.time() - assessment_start_time) * 1000)
902
+
903
+ # Log control evaluation
904
+ if self.audit_trail:
905
+ compliant_count = sum(1 for r in results if r.compliance_status == ComplianceStatus.COMPLIANT)
906
+ self.audit_trail.log_control_evaluation(
907
+ control_id=assessment.control_id,
908
+ config_rule_name=rule_name,
909
+ region=region,
910
+ resource_count=len(results),
911
+ compliant_count=compliant_count,
912
+ duration_ms=assessment_duration
913
+ )
914
+
915
+ logger.debug(f"Assessment {rule_name} in {region} completed with {len(results)} results")
916
+ return results
917
+
918
+ except Exception as e:
919
+ assessment_duration = int((time.time() - assessment_start_time) * 1000)
920
+
921
+ logger.error(f"Error in assessment {rule_name} for region {region}: {e}")
922
+
923
+ # Log assessment error
924
+ if self.audit_trail:
925
+ self.audit_trail.log_event(
926
+ event_type=AuditEventType.ASSESSMENT_ERROR,
927
+ region=region,
928
+ service_name=assessment._get_required_services()[0] if assessment._get_required_services() else "",
929
+ operation=rule_name,
930
+ status="FAILURE",
931
+ message=f"Assessment error: {str(e)}",
932
+ duration_ms=assessment_duration,
933
+ details={
934
+ "control_id": assessment.control_id,
935
+ "exception_type": type(e).__name__
936
+ }
937
+ )
938
+
939
+ # Handle error with error handler
940
+ if self.error_handler:
941
+ context = ErrorContext(
942
+ service_name=assessment._get_required_services()[0] if assessment._get_required_services() else "",
943
+ region=region,
944
+ operation=rule_name,
945
+ control_id=assessment.control_id,
946
+ config_rule_name=rule_name
947
+ )
948
+ self.error_handler.handle_error(e, context)
949
+
950
+ # Return error result instead of failing completely
951
+ return [ComplianceResult(
952
+ resource_id=f"ERROR_{rule_name}_{region}",
953
+ resource_type="ERROR",
954
+ compliance_status=ComplianceStatus.ERROR,
955
+ evaluation_reason=f"Assessment error: {str(e)}",
956
+ config_rule_name=rule_name,
957
+ region=region
958
+ )]
959
+
960
+ def _calculate_ig_score(self, implementation_group: str,
961
+ results: List[ComplianceResult]) -> IGScore:
962
+ """Calculate compliance score for an Implementation Group using ScoringEngine.
963
+
964
+ Args:
965
+ implementation_group: IG1, IG2, or IG3
966
+ results: List of compliance results
967
+
968
+ Returns:
969
+ IGScore object
970
+ """
971
+ # Group results by control ID
972
+ results_by_control = {}
973
+ for result in results:
974
+ # Extract control ID from the assessment registry
975
+ control_id = self._get_control_id_for_rule(result.config_rule_name, implementation_group)
976
+ if control_id not in results_by_control:
977
+ results_by_control[control_id] = []
978
+ results_by_control[control_id].append(result)
979
+
980
+ # Calculate control scores using scoring engine
981
+ control_scores = {}
982
+ for control_id, control_results in results_by_control.items():
983
+ control_score = self.scoring_engine.calculate_control_score(
984
+ control_id=control_id,
985
+ rule_results=control_results,
986
+ control_title=f"CIS Control {control_id}",
987
+ implementation_group=implementation_group
988
+ )
989
+ control_scores[control_id] = control_score
990
+
991
+ # Calculate IG score using scoring engine
992
+ return self.scoring_engine.calculate_ig_score(implementation_group, control_scores)
993
+
994
+ def _calculate_control_score(self, control_id: str,
995
+ results: List[ComplianceResult]) -> ControlScore:
996
+ """Calculate compliance score for a specific control using ScoringEngine.
997
+
998
+ Args:
999
+ control_id: CIS Control ID
1000
+ results: List of compliance results for the control
1001
+
1002
+ Returns:
1003
+ ControlScore object
1004
+ """
1005
+ return self.scoring_engine.calculate_control_score(
1006
+ control_id=control_id,
1007
+ rule_results=results,
1008
+ control_title=f"CIS Control {control_id}"
1009
+ )
1010
+
1011
+ def generate_compliance_summary(self, assessment_result: AssessmentResult):
1012
+ """Generate executive summary using ScoringEngine.
1013
+
1014
+ Args:
1015
+ assessment_result: Complete assessment result
1016
+
1017
+ Returns:
1018
+ ComplianceSummary object
1019
+ """
1020
+ return self.scoring_engine.generate_compliance_summary(assessment_result)
1021
+
1022
+ def get_scoring_engine(self) -> ScoringEngine:
1023
+ """Get the scoring engine instance.
1024
+
1025
+ Returns:
1026
+ ScoringEngine instance
1027
+ """
1028
+ return self.scoring_engine
1029
+
1030
+ def _get_control_id_for_rule(self, rule_name: str, implementation_group: str) -> str:
1031
+ """Get control ID for a specific Config rule.
1032
+
1033
+ Args:
1034
+ rule_name: AWS Config rule name
1035
+ implementation_group: Implementation Group
1036
+
1037
+ Returns:
1038
+ Control ID string
1039
+ """
1040
+ if implementation_group in self._assessment_registry:
1041
+ assessment = self._assessment_registry[implementation_group].get(rule_name)
1042
+ if assessment:
1043
+ return assessment.control_id
1044
+
1045
+ # Fallback - try to determine from rule name patterns
1046
+ if 'iam' in rule_name or 'password' in rule_name or 'mfa' in rule_name:
1047
+ return '3.3' # Access Control
1048
+ elif 'eip' in rule_name or 'instance' in rule_name or 'volume' in rule_name:
1049
+ return '1.1' # Asset Inventory
1050
+ elif 'ssl' in rule_name or 'tls' in rule_name or 'encrypt' in rule_name:
1051
+ return '3.10' # Encryption
1052
+ elif 'log' in rule_name or 'trail' in rule_name:
1053
+ return '3.14' # Logging
1054
+ else:
1055
+ return 'unknown'
1056
+
1057
+ def _update_progress(self):
1058
+ """Update progress and call progress callback if provided."""
1059
+ if self.progress_callback:
1060
+ try:
1061
+ self.progress_callback(self.progress)
1062
+ except Exception as e:
1063
+ logger.warning(f"Progress callback failed: {e}")
1064
+
1065
+ def get_supported_controls(self) -> Dict[str, List[str]]:
1066
+ """Return mapping of Implementation Groups to supported CIS Controls.
1067
+
1068
+ Returns:
1069
+ Dictionary mapping IG names to lists of control IDs
1070
+ """
1071
+ supported_controls = {}
1072
+
1073
+ for ig, assessments in self._assessment_registry.items():
1074
+ control_ids = set()
1075
+ for assessment in assessments.values():
1076
+ control_ids.add(assessment.control_id)
1077
+ supported_controls[ig] = sorted(list(control_ids))
1078
+
1079
+ return supported_controls
1080
+
1081
+ def validate_configuration(self) -> List[str]:
1082
+ """Validate assessment configuration and return any errors.
1083
+
1084
+ Returns:
1085
+ List of validation error messages
1086
+ """
1087
+ errors = []
1088
+
1089
+ # Validate config loader
1090
+ try:
1091
+ config_errors = self.config_loader.validate_configuration()
1092
+ errors.extend(config_errors)
1093
+ except Exception as e:
1094
+ errors.append(f"Config loader validation failed: {str(e)}")
1095
+
1096
+ # Validate AWS credentials
1097
+ try:
1098
+ if not self.aws_factory.validate_credentials():
1099
+ errors.append("AWS credential validation failed")
1100
+ except Exception as e:
1101
+ errors.append(f"AWS credential validation error: {str(e)}")
1102
+
1103
+ # Validate assessment registry
1104
+ if not self._assessment_registry:
1105
+ errors.append("No assessment implementations found")
1106
+
1107
+ for ig, assessments in self._assessment_registry.items():
1108
+ if not assessments:
1109
+ errors.append(f"No assessments found for Implementation Group: {ig}")
1110
+
1111
+ return errors
1112
+
1113
+ def get_assessment_summary(self, implementation_groups: Optional[List[str]] = None,
1114
+ controls: Optional[List[str]] = None,
1115
+ exclude_controls: Optional[List[str]] = None) -> Dict[str, Any]:
1116
+ """Get summary of available assessments.
1117
+
1118
+ Args:
1119
+ implementation_groups: List of IGs to include in summary
1120
+ controls: List of specific control IDs to include
1121
+ exclude_controls: List of control IDs to exclude
1122
+
1123
+ Returns:
1124
+ Dictionary with assessment summary information
1125
+ """
1126
+ if implementation_groups is None:
1127
+ implementation_groups = list(self._assessment_registry.keys())
1128
+
1129
+ summary = {
1130
+ 'implementation_groups': implementation_groups,
1131
+ 'total_assessments': 0,
1132
+ 'regions': self.aws_factory.regions.copy(),
1133
+ 'supported_services': self.aws_factory.get_supported_services(),
1134
+ 'assessments_by_ig': {}
1135
+ }
1136
+
1137
+ total_assessments = 0
1138
+ for ig in implementation_groups:
1139
+ if ig in self._assessment_registry:
1140
+ ig_controls = self._filter_controls_for_ig(ig, controls, exclude_controls)
1141
+ summary['assessments_by_ig'][ig] = {
1142
+ 'count': len(ig_controls),
1143
+ 'rules': list(ig_controls.keys())
1144
+ }
1145
+ total_assessments += len(ig_controls)
1146
+
1147
+ summary['total_assessments'] = total_assessments
1148
+ return summary
1149
+
1150
+ def _filter_controls_for_ig(self, implementation_group: str,
1151
+ controls: Optional[List[str]] = None,
1152
+ exclude_controls: Optional[List[str]] = None) -> Dict[str, BaseConfigRuleAssessment]:
1153
+ """Filter controls for an Implementation Group based on criteria.
1154
+
1155
+ Args:
1156
+ implementation_group: IG to filter controls for
1157
+ controls: List of specific control IDs to include
1158
+ exclude_controls: List of control IDs to exclude
1159
+
1160
+ Returns:
1161
+ Dictionary of filtered control assessments
1162
+ """
1163
+ if implementation_group not in self._assessment_registry:
1164
+ return {}
1165
+
1166
+ all_assessments = self._assessment_registry[implementation_group]
1167
+
1168
+ # If specific controls are requested, filter by control ID
1169
+ if controls:
1170
+ filtered_assessments = {}
1171
+ for rule_name, assessment in all_assessments.items():
1172
+ control_id = self._get_control_id_for_rule(rule_name, implementation_group)
1173
+ if control_id in controls:
1174
+ filtered_assessments[rule_name] = assessment
1175
+ all_assessments = filtered_assessments
1176
+
1177
+ # Exclude specified controls
1178
+ if exclude_controls:
1179
+ filtered_assessments = {}
1180
+ for rule_name, assessment in all_assessments.items():
1181
+ control_id = self._get_control_id_for_rule(rule_name, implementation_group)
1182
+ if control_id not in exclude_controls:
1183
+ filtered_assessments[rule_name] = assessment
1184
+ all_assessments = filtered_assessments
1185
+
1186
+ return all_assessments
1187
+
1188
+ def get_error_summary(self) -> Optional[Dict[str, Any]]:
1189
+ """Get summary of errors encountered during assessment.
1190
+
1191
+ Returns:
1192
+ Error summary dictionary or None if error handling disabled
1193
+ """
1194
+ if not self.error_handler:
1195
+ return None
1196
+
1197
+ return self.error_handler.get_error_summary()
1198
+
1199
+ def get_troubleshooting_report(self) -> Optional[List[Dict[str, Any]]]:
1200
+ """Get troubleshooting report for critical errors.
1201
+
1202
+ Returns:
1203
+ List of troubleshooting information or None if error handling disabled
1204
+ """
1205
+ if not self.error_handler:
1206
+ return None
1207
+
1208
+ return self.error_handler.get_troubleshooting_report()
1209
+
1210
+ def get_audit_summary(self) -> Optional[Dict[str, Any]]:
1211
+ """Get audit trail summary for current session.
1212
+
1213
+ Returns:
1214
+ Audit summary dictionary or None if audit trail disabled
1215
+ """
1216
+ if not self.audit_trail:
1217
+ return None
1218
+
1219
+ return self.audit_trail.get_session_summary()
1220
+
1221
+ def export_audit_trail(self, output_path: str, format: str = "json") -> bool:
1222
+ """Export audit trail to file.
1223
+
1224
+ Args:
1225
+ output_path: Output file path
1226
+ format: Export format ("json" or "csv")
1227
+
1228
+ Returns:
1229
+ True if export successful, False otherwise
1230
+ """
1231
+ if not self.audit_trail:
1232
+ logger.warning("Audit trail not enabled, cannot export")
1233
+ return False
1234
+
1235
+ return self.audit_trail.export_session_events(output_path, format)
1236
+
1237
+ def _cleanup_resources(self):
1238
+ """Clean up resources after assessment completion."""
1239
+ try:
1240
+ # Stop resource monitoring
1241
+ if self.enable_resource_monitoring:
1242
+ self._stop_resource_monitoring()
1243
+
1244
+ # Clear client cache
1245
+ self._client_cache.clear()
1246
+
1247
+ # Clean up AWS factory resources
1248
+ if hasattr(self.aws_factory, 'cleanup'):
1249
+ self.aws_factory.cleanup()
1250
+
1251
+ # Force garbage collection
1252
+ gc.collect()
1253
+
1254
+ logger.debug("Assessment resources cleaned up")
1255
+
1256
+ except Exception as e:
1257
+ logger.warning(f"Resource cleanup failed: {e}")
1258
+
1259
+ def __enter__(self):
1260
+ """Context manager entry."""
1261
+ return self
1262
+
1263
+ def __exit__(self, exc_type, exc_val, exc_tb):
1264
+ """Context manager exit with resource cleanup."""
1265
+ self._cleanup_resources()
1266
+ return False