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.
- aws_cis_assessment/__init__.py +11 -0
- aws_cis_assessment/cli/__init__.py +3 -0
- aws_cis_assessment/cli/examples.py +274 -0
- aws_cis_assessment/cli/main.py +1259 -0
- aws_cis_assessment/cli/utils.py +356 -0
- aws_cis_assessment/config/__init__.py +1 -0
- aws_cis_assessment/config/config_loader.py +328 -0
- aws_cis_assessment/config/rules/cis_controls_ig1.yaml +590 -0
- aws_cis_assessment/config/rules/cis_controls_ig2.yaml +412 -0
- aws_cis_assessment/config/rules/cis_controls_ig3.yaml +100 -0
- aws_cis_assessment/controls/__init__.py +1 -0
- aws_cis_assessment/controls/base_control.py +400 -0
- aws_cis_assessment/controls/ig1/__init__.py +239 -0
- aws_cis_assessment/controls/ig1/control_1_1.py +586 -0
- aws_cis_assessment/controls/ig1/control_2_2.py +231 -0
- aws_cis_assessment/controls/ig1/control_3_3.py +718 -0
- aws_cis_assessment/controls/ig1/control_3_4.py +235 -0
- aws_cis_assessment/controls/ig1/control_4_1.py +461 -0
- aws_cis_assessment/controls/ig1/control_access_keys.py +310 -0
- aws_cis_assessment/controls/ig1/control_advanced_security.py +512 -0
- aws_cis_assessment/controls/ig1/control_backup_recovery.py +510 -0
- aws_cis_assessment/controls/ig1/control_cloudtrail_logging.py +197 -0
- aws_cis_assessment/controls/ig1/control_critical_security.py +422 -0
- aws_cis_assessment/controls/ig1/control_data_protection.py +898 -0
- aws_cis_assessment/controls/ig1/control_iam_advanced.py +573 -0
- aws_cis_assessment/controls/ig1/control_iam_governance.py +493 -0
- aws_cis_assessment/controls/ig1/control_iam_policies.py +383 -0
- aws_cis_assessment/controls/ig1/control_instance_optimization.py +100 -0
- aws_cis_assessment/controls/ig1/control_network_enhancements.py +203 -0
- aws_cis_assessment/controls/ig1/control_network_security.py +672 -0
- aws_cis_assessment/controls/ig1/control_s3_enhancements.py +173 -0
- aws_cis_assessment/controls/ig1/control_s3_security.py +422 -0
- aws_cis_assessment/controls/ig1/control_vpc_security.py +235 -0
- aws_cis_assessment/controls/ig2/__init__.py +172 -0
- aws_cis_assessment/controls/ig2/control_3_10.py +698 -0
- aws_cis_assessment/controls/ig2/control_3_11.py +1330 -0
- aws_cis_assessment/controls/ig2/control_5_2.py +393 -0
- aws_cis_assessment/controls/ig2/control_advanced_encryption.py +355 -0
- aws_cis_assessment/controls/ig2/control_codebuild_security.py +263 -0
- aws_cis_assessment/controls/ig2/control_encryption_rest.py +382 -0
- aws_cis_assessment/controls/ig2/control_encryption_transit.py +382 -0
- aws_cis_assessment/controls/ig2/control_network_ha.py +467 -0
- aws_cis_assessment/controls/ig2/control_remaining_encryption.py +426 -0
- aws_cis_assessment/controls/ig2/control_remaining_rules.py +363 -0
- aws_cis_assessment/controls/ig2/control_service_logging.py +402 -0
- aws_cis_assessment/controls/ig3/__init__.py +49 -0
- aws_cis_assessment/controls/ig3/control_12_8.py +395 -0
- aws_cis_assessment/controls/ig3/control_13_1.py +467 -0
- aws_cis_assessment/controls/ig3/control_3_14.py +523 -0
- aws_cis_assessment/controls/ig3/control_7_1.py +359 -0
- aws_cis_assessment/core/__init__.py +1 -0
- aws_cis_assessment/core/accuracy_validator.py +425 -0
- aws_cis_assessment/core/assessment_engine.py +1266 -0
- aws_cis_assessment/core/audit_trail.py +491 -0
- aws_cis_assessment/core/aws_client_factory.py +313 -0
- aws_cis_assessment/core/error_handler.py +607 -0
- aws_cis_assessment/core/models.py +166 -0
- aws_cis_assessment/core/scoring_engine.py +459 -0
- aws_cis_assessment/reporters/__init__.py +8 -0
- aws_cis_assessment/reporters/base_reporter.py +454 -0
- aws_cis_assessment/reporters/csv_reporter.py +835 -0
- aws_cis_assessment/reporters/html_reporter.py +2162 -0
- aws_cis_assessment/reporters/json_reporter.py +561 -0
- aws_cis_controls_assessment-1.0.3.dist-info/METADATA +248 -0
- aws_cis_controls_assessment-1.0.3.dist-info/RECORD +77 -0
- aws_cis_controls_assessment-1.0.3.dist-info/WHEEL +5 -0
- aws_cis_controls_assessment-1.0.3.dist-info/entry_points.txt +2 -0
- aws_cis_controls_assessment-1.0.3.dist-info/licenses/LICENSE +21 -0
- aws_cis_controls_assessment-1.0.3.dist-info/top_level.txt +2 -0
- docs/README.md +94 -0
- docs/assessment-logic.md +766 -0
- docs/cli-reference.md +698 -0
- docs/config-rule-mappings.md +393 -0
- docs/developer-guide.md +858 -0
- docs/installation.md +299 -0
- docs/troubleshooting.md +634 -0
- 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
|