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,359 @@
|
|
|
1
|
+
"""Control 7.1: Establish and Maintain a Vulnerability Management Process assessments."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Any
|
|
4
|
+
import logging
|
|
5
|
+
import json
|
|
6
|
+
from botocore.exceptions import ClientError
|
|
7
|
+
|
|
8
|
+
from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
|
|
9
|
+
from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
|
|
10
|
+
from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ECRPrivateImageScanningEnabledAssessment(BaseConfigRuleAssessment):
|
|
16
|
+
"""Assessment for ecr-private-image-scanning-enabled Config rule."""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
"""Initialize ECR private image scanning enabled assessment."""
|
|
20
|
+
super().__init__(
|
|
21
|
+
rule_name="ecr-private-image-scanning-enabled",
|
|
22
|
+
control_id="7.1",
|
|
23
|
+
resource_types=["AWS::ECR::Repository"]
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
27
|
+
"""Get all ECR repositories in the region."""
|
|
28
|
+
if resource_type != "AWS::ECR::Repository":
|
|
29
|
+
return []
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
ecr_client = aws_factory.get_client('ecr', region)
|
|
33
|
+
|
|
34
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
35
|
+
lambda: ecr_client.describe_repositories()
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
repositories = []
|
|
39
|
+
for repo in response.get('repositories', []):
|
|
40
|
+
repositories.append({
|
|
41
|
+
'repositoryName': repo.get('repositoryName'),
|
|
42
|
+
'repositoryArn': repo.get('repositoryArn'),
|
|
43
|
+
'repositoryUri': repo.get('repositoryUri'),
|
|
44
|
+
'registryId': repo.get('registryId'),
|
|
45
|
+
'imageScanningConfiguration': repo.get('imageScanningConfiguration', {}),
|
|
46
|
+
'createdAt': repo.get('createdAt'),
|
|
47
|
+
'imageTagMutability': repo.get('imageTagMutability')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
logger.debug(f"Found {len(repositories)} ECR repositories in region {region}")
|
|
51
|
+
return repositories
|
|
52
|
+
|
|
53
|
+
except ClientError as e:
|
|
54
|
+
logger.error(f"Error retrieving ECR repositories in region {region}: {e}")
|
|
55
|
+
raise
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.error(f"Unexpected error retrieving ECR repositories in region {region}: {e}")
|
|
58
|
+
raise
|
|
59
|
+
|
|
60
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
61
|
+
"""Evaluate if ECR repository has image scanning enabled."""
|
|
62
|
+
repo_name = resource.get('repositoryName', 'unknown')
|
|
63
|
+
repo_arn = resource.get('repositoryArn', 'unknown')
|
|
64
|
+
scanning_config = resource.get('imageScanningConfiguration', {})
|
|
65
|
+
|
|
66
|
+
# Check if scan on push is enabled
|
|
67
|
+
scan_on_push = scanning_config.get('scanOnPush', False)
|
|
68
|
+
|
|
69
|
+
if scan_on_push:
|
|
70
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
71
|
+
evaluation_reason = f"ECR repository {repo_name} has image scanning enabled (scanOnPush: true)"
|
|
72
|
+
else:
|
|
73
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
74
|
+
evaluation_reason = f"ECR repository {repo_name} does not have image scanning enabled (scanOnPush: false)"
|
|
75
|
+
|
|
76
|
+
return ComplianceResult(
|
|
77
|
+
resource_id=repo_arn,
|
|
78
|
+
resource_type="AWS::ECR::Repository",
|
|
79
|
+
compliance_status=compliance_status,
|
|
80
|
+
evaluation_reason=evaluation_reason,
|
|
81
|
+
config_rule_name=self.rule_name,
|
|
82
|
+
region=region
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
86
|
+
"""Get specific remediation steps for ECR image scanning."""
|
|
87
|
+
return [
|
|
88
|
+
"Identify ECR repositories without image scanning enabled",
|
|
89
|
+
"For each non-compliant repository:",
|
|
90
|
+
" 1. Enable scan on push for the repository",
|
|
91
|
+
" 2. Consider enabling enhanced scanning for more comprehensive vulnerability detection",
|
|
92
|
+
" 3. Set up notifications for scan results",
|
|
93
|
+
"Use AWS CLI: aws ecr put-image-scanning-configuration --repository-name <repo-name> --image-scanning-configuration scanOnPush=true",
|
|
94
|
+
"Enable enhanced scanning: aws ecr put-registry-scanning-configuration --scan-type ENHANCED",
|
|
95
|
+
"Set up EventBridge rules to receive scan completion notifications",
|
|
96
|
+
"Review scan results regularly and remediate identified vulnerabilities",
|
|
97
|
+
"Consider implementing automated workflows to block deployment of vulnerable images",
|
|
98
|
+
"Document vulnerability management process for container images"
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class GuardDutyEnabledCentralizedAssessment(BaseConfigRuleAssessment):
|
|
103
|
+
"""Assessment for guardduty-enabled-centralized Config rule."""
|
|
104
|
+
|
|
105
|
+
def __init__(self):
|
|
106
|
+
"""Initialize GuardDuty enabled centralized assessment."""
|
|
107
|
+
super().__init__(
|
|
108
|
+
rule_name="guardduty-enabled-centralized",
|
|
109
|
+
control_id="7.1",
|
|
110
|
+
resource_types=["AWS::::Account"]
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
114
|
+
"""Get account-level resource for GuardDuty assessment."""
|
|
115
|
+
if resource_type != "AWS::::Account":
|
|
116
|
+
return []
|
|
117
|
+
|
|
118
|
+
# Return a single account resource for this region
|
|
119
|
+
account_info = aws_factory.get_account_info()
|
|
120
|
+
return [{
|
|
121
|
+
'accountId': account_info.get('account_id', 'unknown'),
|
|
122
|
+
'region': region
|
|
123
|
+
}]
|
|
124
|
+
|
|
125
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
126
|
+
"""Evaluate if GuardDuty is enabled in the account/region."""
|
|
127
|
+
account_id = resource.get('accountId', 'unknown')
|
|
128
|
+
resource_id = f"account-{account_id}-{region}"
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
guardduty_client = aws_factory.get_client('guardduty', region)
|
|
132
|
+
|
|
133
|
+
# List detectors to see if GuardDuty is enabled
|
|
134
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
135
|
+
lambda: guardduty_client.list_detectors()
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
detector_ids = response.get('DetectorIds', [])
|
|
139
|
+
|
|
140
|
+
if not detector_ids:
|
|
141
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
142
|
+
evaluation_reason = f"GuardDuty is not enabled in account {account_id} region {region}"
|
|
143
|
+
else:
|
|
144
|
+
# Check if any detector is enabled
|
|
145
|
+
enabled_detectors = []
|
|
146
|
+
|
|
147
|
+
for detector_id in detector_ids:
|
|
148
|
+
try:
|
|
149
|
+
detector_response = aws_factory.aws_api_call_with_retry(
|
|
150
|
+
lambda: guardduty_client.get_detector(DetectorId=detector_id)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
status = detector_response.get('Status', 'DISABLED')
|
|
154
|
+
if status == 'ENABLED':
|
|
155
|
+
enabled_detectors.append(detector_id)
|
|
156
|
+
|
|
157
|
+
except ClientError as e:
|
|
158
|
+
logger.warning(f"Could not get detector {detector_id} details: {e}")
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
if enabled_detectors:
|
|
162
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
163
|
+
evaluation_reason = f"GuardDuty is enabled in account {account_id} region {region} with {len(enabled_detectors)} active detector(s)"
|
|
164
|
+
else:
|
|
165
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
166
|
+
evaluation_reason = f"GuardDuty detectors exist but are disabled in account {account_id} region {region}"
|
|
167
|
+
|
|
168
|
+
except ClientError as e:
|
|
169
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
170
|
+
if error_code in ['AccessDenied', 'UnauthorizedOperation']:
|
|
171
|
+
compliance_status = ComplianceStatus.ERROR
|
|
172
|
+
evaluation_reason = f"Insufficient permissions to check GuardDuty status in account {account_id} region {region}"
|
|
173
|
+
else:
|
|
174
|
+
compliance_status = ComplianceStatus.ERROR
|
|
175
|
+
evaluation_reason = f"Error checking GuardDuty status in account {account_id} region {region}: {str(e)}"
|
|
176
|
+
except Exception as e:
|
|
177
|
+
compliance_status = ComplianceStatus.ERROR
|
|
178
|
+
evaluation_reason = f"Unexpected error checking GuardDuty status in account {account_id} region {region}: {str(e)}"
|
|
179
|
+
|
|
180
|
+
return ComplianceResult(
|
|
181
|
+
resource_id=resource_id,
|
|
182
|
+
resource_type="AWS::::Account",
|
|
183
|
+
compliance_status=compliance_status,
|
|
184
|
+
evaluation_reason=evaluation_reason,
|
|
185
|
+
config_rule_name=self.rule_name,
|
|
186
|
+
region=region
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
190
|
+
"""Get specific remediation steps for GuardDuty enablement."""
|
|
191
|
+
return [
|
|
192
|
+
"Enable GuardDuty in all AWS regions where resources are deployed",
|
|
193
|
+
"For each region without GuardDuty:",
|
|
194
|
+
" 1. Create a GuardDuty detector",
|
|
195
|
+
" 2. Enable the detector",
|
|
196
|
+
" 3. Configure finding export to S3 or other destinations",
|
|
197
|
+
" 4. Set up notifications for high-severity findings",
|
|
198
|
+
"Use AWS CLI: aws guardduty create-detector --enable --region <region>",
|
|
199
|
+
"Enable S3 protection: aws guardduty update-detector --detector-id <detector-id> --data-sources S3Logs={Enable=true}",
|
|
200
|
+
"Enable Kubernetes protection: aws guardduty update-detector --detector-id <detector-id> --data-sources Kubernetes={AuditLogs={Enable=true}}",
|
|
201
|
+
"Set up centralized logging by configuring finding export",
|
|
202
|
+
"Create EventBridge rules to route findings to security teams",
|
|
203
|
+
"Consider enabling GuardDuty Malware Protection for EC2 instances",
|
|
204
|
+
"Implement automated response workflows for critical findings",
|
|
205
|
+
"Regularly review and tune GuardDuty findings to reduce false positives"
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class EC2ManagedInstancePatchComplianceAssessment(BaseConfigRuleAssessment):
|
|
210
|
+
"""Assessment for ec2-managedinstance-patch-compliance-status-check Config rule."""
|
|
211
|
+
|
|
212
|
+
def __init__(self):
|
|
213
|
+
"""Initialize EC2 managed instance patch compliance assessment."""
|
|
214
|
+
super().__init__(
|
|
215
|
+
rule_name="ec2-managedinstance-patch-compliance-status-check",
|
|
216
|
+
control_id="7.1",
|
|
217
|
+
resource_types=["AWS::EC2::Instance"]
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
221
|
+
"""Get all EC2 instances that are managed by Systems Manager."""
|
|
222
|
+
if resource_type != "AWS::EC2::Instance":
|
|
223
|
+
return []
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
ec2_client = aws_factory.get_client('ec2', region)
|
|
227
|
+
ssm_client = aws_factory.get_client('ssm', region)
|
|
228
|
+
|
|
229
|
+
# First get all EC2 instances
|
|
230
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
231
|
+
lambda: ec2_client.describe_instances()
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
all_instances = []
|
|
235
|
+
for reservation in response.get('Reservations', []):
|
|
236
|
+
for instance in reservation.get('Instances', []):
|
|
237
|
+
if instance.get('State', {}).get('Name') == 'running':
|
|
238
|
+
all_instances.append({
|
|
239
|
+
'InstanceId': instance.get('InstanceId'),
|
|
240
|
+
'InstanceType': instance.get('InstanceType'),
|
|
241
|
+
'Platform': instance.get('Platform', 'Linux'),
|
|
242
|
+
'LaunchTime': instance.get('LaunchTime'),
|
|
243
|
+
'Tags': instance.get('Tags', [])
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
# Filter to only instances managed by Systems Manager
|
|
247
|
+
managed_instances = []
|
|
248
|
+
if all_instances:
|
|
249
|
+
try:
|
|
250
|
+
# Get managed instances from Systems Manager
|
|
251
|
+
ssm_response = aws_factory.aws_api_call_with_retry(
|
|
252
|
+
lambda: ssm_client.describe_instance_information()
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
managed_instance_ids = set()
|
|
256
|
+
for instance_info in ssm_response.get('InstanceInformationList', []):
|
|
257
|
+
managed_instance_ids.add(instance_info.get('InstanceId'))
|
|
258
|
+
|
|
259
|
+
# Filter EC2 instances to only managed ones
|
|
260
|
+
for instance in all_instances:
|
|
261
|
+
if instance['InstanceId'] in managed_instance_ids:
|
|
262
|
+
managed_instances.append(instance)
|
|
263
|
+
|
|
264
|
+
except ClientError as e:
|
|
265
|
+
logger.warning(f"Could not get Systems Manager managed instances: {e}")
|
|
266
|
+
# If we can't access SSM, we'll evaluate all running instances
|
|
267
|
+
managed_instances = all_instances
|
|
268
|
+
|
|
269
|
+
logger.debug(f"Found {len(managed_instances)} managed EC2 instances in region {region}")
|
|
270
|
+
return managed_instances
|
|
271
|
+
|
|
272
|
+
except ClientError as e:
|
|
273
|
+
logger.error(f"Error retrieving EC2 instances in region {region}: {e}")
|
|
274
|
+
raise
|
|
275
|
+
except Exception as e:
|
|
276
|
+
logger.error(f"Unexpected error retrieving EC2 instances in region {region}: {e}")
|
|
277
|
+
raise
|
|
278
|
+
|
|
279
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
280
|
+
"""Evaluate if EC2 instance has compliant patch status."""
|
|
281
|
+
instance_id = resource.get('InstanceId', 'unknown')
|
|
282
|
+
instance_type = resource.get('InstanceType', 'unknown')
|
|
283
|
+
platform = resource.get('Platform', 'Linux')
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
ssm_client = aws_factory.get_client('ssm', region)
|
|
287
|
+
|
|
288
|
+
# Get patch compliance information for this instance
|
|
289
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
290
|
+
lambda: ssm_client.describe_instance_patch_states(
|
|
291
|
+
InstanceIds=[instance_id]
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
patch_states = response.get('InstancePatchStates', [])
|
|
296
|
+
|
|
297
|
+
if not patch_states:
|
|
298
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
299
|
+
evaluation_reason = f"EC2 instance {instance_id} ({platform}) has no patch compliance information available"
|
|
300
|
+
else:
|
|
301
|
+
patch_state = patch_states[0]
|
|
302
|
+
operation_end_time = patch_state.get('OperationEndTime')
|
|
303
|
+
failed_count = patch_state.get('FailedCount', 0)
|
|
304
|
+
missing_count = patch_state.get('MissingCount', 0)
|
|
305
|
+
installed_count = patch_state.get('InstalledCount', 0)
|
|
306
|
+
not_applicable_count = patch_state.get('NotApplicableCount', 0)
|
|
307
|
+
|
|
308
|
+
# Consider compliant if no failed or missing patches
|
|
309
|
+
if failed_count == 0 and missing_count == 0:
|
|
310
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
311
|
+
evaluation_reason = f"EC2 instance {instance_id} ({platform}) is patch compliant - {installed_count} installed, {not_applicable_count} not applicable"
|
|
312
|
+
else:
|
|
313
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
314
|
+
evaluation_reason = f"EC2 instance {instance_id} ({platform}) is not patch compliant - {missing_count} missing, {failed_count} failed patches"
|
|
315
|
+
|
|
316
|
+
except ClientError as e:
|
|
317
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
318
|
+
if error_code in ['AccessDenied', 'UnauthorizedOperation']:
|
|
319
|
+
compliance_status = ComplianceStatus.ERROR
|
|
320
|
+
evaluation_reason = f"Insufficient permissions to check patch compliance for EC2 instance {instance_id}"
|
|
321
|
+
elif error_code == 'InvalidInstanceId.NotFound':
|
|
322
|
+
compliance_status = ComplianceStatus.ERROR
|
|
323
|
+
evaluation_reason = f"EC2 instance {instance_id} not found in Systems Manager (may not be managed)"
|
|
324
|
+
else:
|
|
325
|
+
compliance_status = ComplianceStatus.ERROR
|
|
326
|
+
evaluation_reason = f"Error checking patch compliance for EC2 instance {instance_id}: {str(e)}"
|
|
327
|
+
except Exception as e:
|
|
328
|
+
compliance_status = ComplianceStatus.ERROR
|
|
329
|
+
evaluation_reason = f"Unexpected error checking patch compliance for EC2 instance {instance_id}: {str(e)}"
|
|
330
|
+
|
|
331
|
+
return ComplianceResult(
|
|
332
|
+
resource_id=instance_id,
|
|
333
|
+
resource_type="AWS::EC2::Instance",
|
|
334
|
+
compliance_status=compliance_status,
|
|
335
|
+
evaluation_reason=evaluation_reason,
|
|
336
|
+
config_rule_name=self.rule_name,
|
|
337
|
+
region=region
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
341
|
+
"""Get specific remediation steps for EC2 patch compliance."""
|
|
342
|
+
return [
|
|
343
|
+
"Ensure EC2 instances are managed by AWS Systems Manager",
|
|
344
|
+
"For instances not managed by Systems Manager:",
|
|
345
|
+
" 1. Install SSM Agent (pre-installed on Amazon Linux, Windows, Ubuntu)",
|
|
346
|
+
" 2. Attach IAM role with AmazonSSMManagedInstanceCore policy",
|
|
347
|
+
" 3. Verify instance appears in Systems Manager console",
|
|
348
|
+
"For non-compliant patch status:",
|
|
349
|
+
" 1. Create or update patch baselines for your operating systems",
|
|
350
|
+
" 2. Create maintenance windows for patch installation",
|
|
351
|
+
" 3. Run patch scans to identify missing patches",
|
|
352
|
+
" 4. Install missing patches during maintenance windows",
|
|
353
|
+
"Use AWS CLI: aws ssm send-command --document-name 'AWS-RunPatchBaseline' --instance-ids <instance-id> --parameters 'Operation=Scan'",
|
|
354
|
+
"Install patches: aws ssm send-command --document-name 'AWS-RunPatchBaseline' --instance-ids <instance-id> --parameters 'Operation=Install'",
|
|
355
|
+
"Set up automated patching with maintenance windows",
|
|
356
|
+
"Monitor patch compliance using Systems Manager Compliance",
|
|
357
|
+
"Create CloudWatch alarms for patch compliance failures",
|
|
358
|
+
"Implement patch testing procedures before production deployment"
|
|
359
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core components for AWS CIS assessment engine."""
|