aws-cis-controls-assessment 1.1.3__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aws_cis_assessment/__init__.py +4 -4
- aws_cis_assessment/config/rules/cis_controls_ig1.yaml +365 -2
- aws_cis_assessment/controls/ig1/control_access_analyzer.py +198 -0
- aws_cis_assessment/controls/ig1/control_access_asset_mgmt.py +360 -0
- aws_cis_assessment/controls/ig1/control_access_control.py +323 -0
- aws_cis_assessment/controls/ig1/control_backup_security.py +579 -0
- aws_cis_assessment/controls/ig1/control_cloudfront_logging.py +215 -0
- aws_cis_assessment/controls/ig1/control_configuration_mgmt.py +407 -0
- aws_cis_assessment/controls/ig1/control_data_classification.py +255 -0
- aws_cis_assessment/controls/ig1/control_dynamodb_encryption.py +279 -0
- aws_cis_assessment/controls/ig1/control_ebs_encryption.py +177 -0
- aws_cis_assessment/controls/ig1/control_efs_encryption.py +243 -0
- aws_cis_assessment/controls/ig1/control_elb_logging.py +195 -0
- aws_cis_assessment/controls/ig1/control_guardduty.py +156 -0
- aws_cis_assessment/controls/ig1/control_inspector.py +184 -0
- aws_cis_assessment/controls/ig1/control_inventory.py +511 -0
- aws_cis_assessment/controls/ig1/control_macie.py +165 -0
- aws_cis_assessment/controls/ig1/control_messaging_encryption.py +419 -0
- aws_cis_assessment/controls/ig1/control_mfa.py +485 -0
- aws_cis_assessment/controls/ig1/control_network_security.py +194 -619
- aws_cis_assessment/controls/ig1/control_patch_management.py +626 -0
- aws_cis_assessment/controls/ig1/control_rds_encryption.py +228 -0
- aws_cis_assessment/controls/ig1/control_s3_encryption.py +383 -0
- aws_cis_assessment/controls/ig1/control_tls_ssl.py +556 -0
- aws_cis_assessment/controls/ig1/control_version_mgmt.py +329 -0
- aws_cis_assessment/controls/ig1/control_vpc_flow_logs.py +205 -0
- aws_cis_assessment/controls/ig1/control_waf_logging.py +226 -0
- aws_cis_assessment/core/models.py +20 -1
- aws_cis_assessment/core/scoring_engine.py +98 -1
- aws_cis_assessment/reporters/base_reporter.py +31 -1
- aws_cis_assessment/reporters/html_reporter.py +172 -11
- aws_cis_controls_assessment-1.2.0.dist-info/METADATA +320 -0
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/RECORD +39 -15
- docs/developer-guide.md +204 -5
- docs/user-guide.md +137 -4
- aws_cis_controls_assessment-1.1.3.dist-info/METADATA +0 -404
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/WHEEL +0 -0
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/entry_points.txt +0 -0
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CIS Control 2.1-2.3 - Patch Management Controls
|
|
3
|
+
Ensures systems are properly patched and patch compliance is maintained.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import List, Dict, Any
|
|
8
|
+
from botocore.exceptions import ClientError
|
|
9
|
+
|
|
10
|
+
from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
|
|
11
|
+
from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
|
|
12
|
+
from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SSMPatchManagerEnabledAssessment(BaseConfigRuleAssessment):
|
|
18
|
+
"""
|
|
19
|
+
CIS Control 2.1 - Establish and Maintain a Software Inventory
|
|
20
|
+
AWS Config Rule: ssm-patch-manager-enabled
|
|
21
|
+
|
|
22
|
+
Ensures AWS Systems Manager Patch Manager is configured with patch baselines
|
|
23
|
+
for automated patch management across EC2 instances.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
super().__init__(
|
|
28
|
+
rule_name="ssm-patch-manager-enabled",
|
|
29
|
+
control_id="2.1",
|
|
30
|
+
resource_types=["AWS::::Account"]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
34
|
+
"""Get SSM Patch Manager configuration for the account."""
|
|
35
|
+
if resource_type != "AWS::::Account":
|
|
36
|
+
return []
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
ssm_client = aws_factory.get_client('ssm', region)
|
|
40
|
+
|
|
41
|
+
# Get all patch baselines
|
|
42
|
+
baselines = []
|
|
43
|
+
paginator = ssm_client.get_paginator('describe_patch_baselines')
|
|
44
|
+
|
|
45
|
+
for page in paginator.paginate():
|
|
46
|
+
baselines.extend(page.get('BaselineIdentities', []))
|
|
47
|
+
|
|
48
|
+
# Get default patch baseline for each OS
|
|
49
|
+
default_baselines = {}
|
|
50
|
+
for os_type in ['WINDOWS', 'AMAZON_LINUX', 'AMAZON_LINUX_2', 'UBUNTU', 'REDHAT_ENTERPRISE_LINUX',
|
|
51
|
+
'SUSE', 'CENTOS', 'ORACLE_LINUX', 'DEBIAN', 'MACOS']:
|
|
52
|
+
try:
|
|
53
|
+
response = ssm_client.get_default_patch_baseline(OperatingSystem=os_type)
|
|
54
|
+
default_baselines[os_type] = response.get('BaselineId', '')
|
|
55
|
+
except ClientError:
|
|
56
|
+
# No default baseline for this OS type
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
# Check if any patch baselines are configured
|
|
60
|
+
has_baselines = len(baselines) > 0
|
|
61
|
+
has_default_baselines = len(default_baselines) > 0
|
|
62
|
+
|
|
63
|
+
return [{
|
|
64
|
+
'AccountId': aws_factory.get_account_info().get('account_id', 'unknown'),
|
|
65
|
+
'Region': region,
|
|
66
|
+
'TotalBaselines': len(baselines),
|
|
67
|
+
'Baselines': baselines[:10], # Limit to first 10 for display
|
|
68
|
+
'DefaultBaselines': default_baselines,
|
|
69
|
+
'HasBaselines': has_baselines,
|
|
70
|
+
'HasDefaultBaselines': has_default_baselines
|
|
71
|
+
}]
|
|
72
|
+
|
|
73
|
+
except ClientError as e:
|
|
74
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
75
|
+
if error_code == 'AccessDeniedException':
|
|
76
|
+
logger.warning(f"Access denied to check SSM Patch Manager in {region}")
|
|
77
|
+
else:
|
|
78
|
+
logger.error(f"Error checking SSM Patch Manager in {region}: {e}")
|
|
79
|
+
return []
|
|
80
|
+
|
|
81
|
+
def _evaluate_resource_compliance(
|
|
82
|
+
self,
|
|
83
|
+
resource: Dict[str, Any],
|
|
84
|
+
aws_factory: AWSClientFactory,
|
|
85
|
+
region: str
|
|
86
|
+
) -> ComplianceResult:
|
|
87
|
+
"""Evaluate if SSM Patch Manager is properly configured."""
|
|
88
|
+
account_id = resource.get('AccountId', 'unknown')
|
|
89
|
+
has_baselines = resource.get('HasBaselines', False)
|
|
90
|
+
has_default_baselines = resource.get('HasDefaultBaselines', False)
|
|
91
|
+
total_baselines = resource.get('TotalBaselines', 0)
|
|
92
|
+
default_baselines = resource.get('DefaultBaselines', {})
|
|
93
|
+
|
|
94
|
+
# Check if patch management is configured
|
|
95
|
+
is_compliant = has_baselines and has_default_baselines
|
|
96
|
+
|
|
97
|
+
if is_compliant:
|
|
98
|
+
os_list = ', '.join(default_baselines.keys())
|
|
99
|
+
evaluation_reason = (
|
|
100
|
+
f"SSM Patch Manager is configured in {region} with {total_baselines} patch baseline(s). "
|
|
101
|
+
f"Default baselines configured for: {os_list}"
|
|
102
|
+
)
|
|
103
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
104
|
+
else:
|
|
105
|
+
if not has_baselines:
|
|
106
|
+
evaluation_reason = f"No patch baselines configured in SSM Patch Manager in {region}."
|
|
107
|
+
elif not has_default_baselines:
|
|
108
|
+
evaluation_reason = (
|
|
109
|
+
f"SSM Patch Manager has {total_baselines} baseline(s) but no default baselines "
|
|
110
|
+
f"are configured for any operating systems in {region}."
|
|
111
|
+
)
|
|
112
|
+
else:
|
|
113
|
+
evaluation_reason = f"SSM Patch Manager is not properly configured in {region}."
|
|
114
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
115
|
+
|
|
116
|
+
return ComplianceResult(
|
|
117
|
+
resource_id=f"{account_id}-{region}",
|
|
118
|
+
resource_type="AWS::::Account",
|
|
119
|
+
compliance_status=compliance_status,
|
|
120
|
+
evaluation_reason=evaluation_reason,
|
|
121
|
+
config_rule_name=self.rule_name,
|
|
122
|
+
region=region
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
126
|
+
"""Get remediation steps for configuring SSM Patch Manager."""
|
|
127
|
+
return [
|
|
128
|
+
"1. Configure SSM Patch Manager with default patch baselines:",
|
|
129
|
+
" # Set default patch baseline for Amazon Linux 2",
|
|
130
|
+
" aws ssm register-default-patch-baseline \\",
|
|
131
|
+
" --baseline-id <baseline-id> \\",
|
|
132
|
+
" --region <region>",
|
|
133
|
+
"",
|
|
134
|
+
"2. Create a custom patch baseline (example for Amazon Linux 2):",
|
|
135
|
+
" aws ssm create-patch-baseline \\",
|
|
136
|
+
" --name 'AmazonLinux2-CustomBaseline' \\",
|
|
137
|
+
" --operating-system AMAZON_LINUX_2 \\",
|
|
138
|
+
" --approval-rules 'PatchRules=[{PatchFilterGroup={PatchFilters=[{Key=CLASSIFICATION,Values=[Security,Bugfix]},{Key=SEVERITY,Values=[Critical,Important]}]},ApprovalAfterDays=7}]' \\",
|
|
139
|
+
" --description 'Custom patch baseline for Amazon Linux 2' \\",
|
|
140
|
+
" --region <region>",
|
|
141
|
+
"",
|
|
142
|
+
"3. Register the custom baseline as default:",
|
|
143
|
+
" aws ssm register-default-patch-baseline \\",
|
|
144
|
+
" --baseline-id <baseline-id-from-step-2> \\",
|
|
145
|
+
" --region <region>",
|
|
146
|
+
"",
|
|
147
|
+
"4. Create patch baselines for all OS types in use:",
|
|
148
|
+
" # Windows",
|
|
149
|
+
" aws ssm create-patch-baseline \\",
|
|
150
|
+
" --name 'Windows-CustomBaseline' \\",
|
|
151
|
+
" --operating-system WINDOWS \\",
|
|
152
|
+
" --approval-rules 'PatchRules=[{PatchFilterGroup={PatchFilters=[{Key=CLASSIFICATION,Values=[SecurityUpdates,CriticalUpdates]},{Key=MSRC_SEVERITY,Values=[Critical,Important]}]},ApprovalAfterDays=7}]' \\",
|
|
153
|
+
" --region <region>",
|
|
154
|
+
"",
|
|
155
|
+
" # Ubuntu",
|
|
156
|
+
" aws ssm create-patch-baseline \\",
|
|
157
|
+
" --name 'Ubuntu-CustomBaseline' \\",
|
|
158
|
+
" --operating-system UBUNTU \\",
|
|
159
|
+
" --approval-rules 'PatchRules=[{PatchFilterGroup={PatchFilters=[{Key=PRIORITY,Values=[Required,Important]}]},ApprovalAfterDays=7}]' \\",
|
|
160
|
+
" --region <region>",
|
|
161
|
+
"",
|
|
162
|
+
"5. Console method:",
|
|
163
|
+
" - Navigate to AWS Systems Manager",
|
|
164
|
+
" - Click 'Patch Manager' in the left menu",
|
|
165
|
+
" - Click 'Configure patching'",
|
|
166
|
+
" - Select 'Patch baselines'",
|
|
167
|
+
" - Click 'Create patch baseline'",
|
|
168
|
+
" - Configure baseline settings:",
|
|
169
|
+
" * Name and description",
|
|
170
|
+
" * Operating system",
|
|
171
|
+
" * Approval rules (auto-approve after X days)",
|
|
172
|
+
" * Patch exceptions (if needed)",
|
|
173
|
+
" - Click 'Create patch baseline'",
|
|
174
|
+
" - Set as default: Actions > Set default patch baseline",
|
|
175
|
+
"",
|
|
176
|
+
"6. Create a maintenance window for automated patching:",
|
|
177
|
+
" aws ssm create-maintenance-window \\",
|
|
178
|
+
" --name 'PatchingWindow' \\",
|
|
179
|
+
" --schedule 'cron(0 2 ? * SUN *)' \\",
|
|
180
|
+
" --duration 4 \\",
|
|
181
|
+
" --cutoff 1 \\",
|
|
182
|
+
" --allow-unassociated-targets \\",
|
|
183
|
+
" --region <region>",
|
|
184
|
+
"",
|
|
185
|
+
"7. Register targets for the maintenance window:",
|
|
186
|
+
" aws ssm register-target-with-maintenance-window \\",
|
|
187
|
+
" --window-id <window-id> \\",
|
|
188
|
+
" --target-type INSTANCE \\",
|
|
189
|
+
" --targets 'Key=tag:Patch Group,Values=Production' \\",
|
|
190
|
+
" --region <region>",
|
|
191
|
+
"",
|
|
192
|
+
"8. Register a patch task:",
|
|
193
|
+
" aws ssm register-task-with-maintenance-window \\",
|
|
194
|
+
" --window-id <window-id> \\",
|
|
195
|
+
" --target-id <target-id> \\",
|
|
196
|
+
" --task-type RUN_COMMAND \\",
|
|
197
|
+
" --task-arn AWS-RunPatchBaseline \\",
|
|
198
|
+
" --service-role-arn <role-arn> \\",
|
|
199
|
+
" --task-invocation-parameters 'RunCommand={Parameters={Operation=[Install]}}' \\",
|
|
200
|
+
" --priority 1 \\",
|
|
201
|
+
" --max-concurrency 50% \\",
|
|
202
|
+
" --max-errors 10% \\",
|
|
203
|
+
" --region <region>",
|
|
204
|
+
"",
|
|
205
|
+
"9. Best practices:",
|
|
206
|
+
" - Create separate patch baselines for different environments (dev/staging/prod)",
|
|
207
|
+
" - Use patch groups to organize instances",
|
|
208
|
+
" - Set appropriate approval delays (7 days for production)",
|
|
209
|
+
" - Configure maintenance windows during off-peak hours",
|
|
210
|
+
" - Enable SNS notifications for patch operations",
|
|
211
|
+
" - Monitor patch compliance with AWS Config",
|
|
212
|
+
" - Test patches in non-production first",
|
|
213
|
+
"",
|
|
214
|
+
"10. Verify configuration:",
|
|
215
|
+
" # List all patch baselines",
|
|
216
|
+
" aws ssm describe-patch-baselines --region <region>",
|
|
217
|
+
"",
|
|
218
|
+
" # Get default baseline for an OS",
|
|
219
|
+
" aws ssm get-default-patch-baseline \\",
|
|
220
|
+
" --operating-system AMAZON_LINUX_2 \\",
|
|
221
|
+
" --region <region>",
|
|
222
|
+
"",
|
|
223
|
+
"Priority: HIGH - Patch management is critical for security",
|
|
224
|
+
"Effort: Medium - Requires baseline configuration and maintenance windows",
|
|
225
|
+
"",
|
|
226
|
+
"AWS Documentation:",
|
|
227
|
+
"https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-patch.html"
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class SSMPatchBaselineConfiguredAssessment(BaseConfigRuleAssessment):
|
|
233
|
+
"""
|
|
234
|
+
CIS Control 2.2 - Ensure Authorized Software is Currently Supported
|
|
235
|
+
AWS Config Rule: ssm-patch-baseline-configured
|
|
236
|
+
|
|
237
|
+
Ensures SSM patch baselines are properly configured with appropriate
|
|
238
|
+
approval rules and patch filters for security updates.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def __init__(self):
|
|
242
|
+
super().__init__(
|
|
243
|
+
rule_name="ssm-patch-baseline-configured",
|
|
244
|
+
control_id="2.2",
|
|
245
|
+
resource_types=["AWS::SSM::PatchBaseline"]
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
249
|
+
"""Get all SSM patch baselines in the region."""
|
|
250
|
+
if resource_type != "AWS::SSM::PatchBaseline":
|
|
251
|
+
return []
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
ssm_client = aws_factory.get_client('ssm', region)
|
|
255
|
+
|
|
256
|
+
baselines = []
|
|
257
|
+
paginator = ssm_client.get_paginator('describe_patch_baselines')
|
|
258
|
+
|
|
259
|
+
for page in paginator.paginate(Filters=[{'Key': 'OWNER', 'Values': ['Self']}]):
|
|
260
|
+
for baseline_identity in page.get('BaselineIdentities', []):
|
|
261
|
+
baseline_id = baseline_identity.get('BaselineId', '')
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
# Get detailed baseline information
|
|
265
|
+
response = ssm_client.get_patch_baseline(BaselineId=baseline_id)
|
|
266
|
+
|
|
267
|
+
baseline_name = response.get('Name', '')
|
|
268
|
+
operating_system = response.get('OperatingSystem', '')
|
|
269
|
+
approval_rules = response.get('ApprovalRules', {})
|
|
270
|
+
patch_groups = response.get('PatchGroups', [])
|
|
271
|
+
|
|
272
|
+
# Check if baseline has approval rules
|
|
273
|
+
has_approval_rules = bool(approval_rules.get('PatchRules', []))
|
|
274
|
+
|
|
275
|
+
baselines.append({
|
|
276
|
+
'BaselineId': baseline_id,
|
|
277
|
+
'BaselineName': baseline_name,
|
|
278
|
+
'OperatingSystem': operating_system,
|
|
279
|
+
'ApprovalRules': approval_rules,
|
|
280
|
+
'HasApprovalRules': has_approval_rules,
|
|
281
|
+
'PatchGroups': patch_groups,
|
|
282
|
+
'Description': response.get('Description', '')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
except ClientError as e:
|
|
286
|
+
logger.warning(f"Error getting baseline {baseline_id}: {e}")
|
|
287
|
+
continue
|
|
288
|
+
|
|
289
|
+
logger.debug(f"Found {len(baselines)} custom patch baselines in {region}")
|
|
290
|
+
return baselines
|
|
291
|
+
|
|
292
|
+
except ClientError as e:
|
|
293
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
294
|
+
if error_code == 'AccessDeniedException':
|
|
295
|
+
logger.warning(f"Access denied to list patch baselines in {region}")
|
|
296
|
+
else:
|
|
297
|
+
logger.error(f"Error retrieving patch baselines from {region}: {e}")
|
|
298
|
+
return []
|
|
299
|
+
|
|
300
|
+
def _evaluate_resource_compliance(
|
|
301
|
+
self,
|
|
302
|
+
resource: Dict[str, Any],
|
|
303
|
+
aws_factory: AWSClientFactory,
|
|
304
|
+
region: str
|
|
305
|
+
) -> ComplianceResult:
|
|
306
|
+
"""Evaluate if patch baseline is properly configured."""
|
|
307
|
+
baseline_id = resource.get('BaselineId', 'unknown')
|
|
308
|
+
baseline_name = resource.get('BaselineName', '')
|
|
309
|
+
operating_system = resource.get('OperatingSystem', '')
|
|
310
|
+
has_approval_rules = resource.get('HasApprovalRules', False)
|
|
311
|
+
approval_rules = resource.get('ApprovalRules', {})
|
|
312
|
+
|
|
313
|
+
# Check if baseline has proper approval rules
|
|
314
|
+
is_compliant = has_approval_rules
|
|
315
|
+
|
|
316
|
+
if is_compliant:
|
|
317
|
+
patch_rules = approval_rules.get('PatchRules', [])
|
|
318
|
+
rule_count = len(patch_rules)
|
|
319
|
+
evaluation_reason = (
|
|
320
|
+
f"Patch baseline '{baseline_name}' ({operating_system}) is properly configured "
|
|
321
|
+
f"with {rule_count} approval rule(s)."
|
|
322
|
+
)
|
|
323
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
324
|
+
else:
|
|
325
|
+
evaluation_reason = (
|
|
326
|
+
f"Patch baseline '{baseline_name}' ({operating_system}) does not have approval rules configured. "
|
|
327
|
+
f"Approval rules are required to automatically approve patches."
|
|
328
|
+
)
|
|
329
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
330
|
+
|
|
331
|
+
return ComplianceResult(
|
|
332
|
+
resource_id=baseline_id,
|
|
333
|
+
resource_type="AWS::SSM::PatchBaseline",
|
|
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 remediation steps for configuring patch baseline approval rules."""
|
|
342
|
+
return [
|
|
343
|
+
"1. Update an existing patch baseline with approval rules:",
|
|
344
|
+
" aws ssm update-patch-baseline \\",
|
|
345
|
+
" --baseline-id <baseline-id> \\",
|
|
346
|
+
" --approval-rules 'PatchRules=[{",
|
|
347
|
+
" PatchFilterGroup={",
|
|
348
|
+
" PatchFilters=[",
|
|
349
|
+
" {Key=CLASSIFICATION,Values=[Security,Bugfix]},",
|
|
350
|
+
" {Key=SEVERITY,Values=[Critical,Important]}",
|
|
351
|
+
" ]",
|
|
352
|
+
" },",
|
|
353
|
+
" ApprovalAfterDays=7,",
|
|
354
|
+
" ComplianceLevel=CRITICAL",
|
|
355
|
+
" }]' \\",
|
|
356
|
+
" --region <region>",
|
|
357
|
+
"",
|
|
358
|
+
"2. Console method:",
|
|
359
|
+
" - Navigate to AWS Systems Manager",
|
|
360
|
+
" - Click 'Patch Manager' > 'Patch baselines'",
|
|
361
|
+
" - Select the baseline",
|
|
362
|
+
" - Click 'Actions' > 'Modify patch baseline'",
|
|
363
|
+
" - Under 'Approval rules', click 'Add rule'",
|
|
364
|
+
" - Configure the rule:",
|
|
365
|
+
" * Product: Select OS/product",
|
|
366
|
+
" * Classification: Select patch types (Security, Bugfix, etc.)",
|
|
367
|
+
" * Severity: Select severity levels (Critical, Important, etc.)",
|
|
368
|
+
" * Auto-approval: Set days to wait before auto-approval",
|
|
369
|
+
" * Compliance reporting: Set compliance level",
|
|
370
|
+
" - Click 'Add rule'",
|
|
371
|
+
" - Click 'Save changes'",
|
|
372
|
+
"",
|
|
373
|
+
"3. Example approval rules for different OS types:",
|
|
374
|
+
" # Windows - Security and Critical Updates",
|
|
375
|
+
" PatchRules=[{",
|
|
376
|
+
" PatchFilterGroup={",
|
|
377
|
+
" PatchFilters=[",
|
|
378
|
+
" {Key=CLASSIFICATION,Values=[SecurityUpdates,CriticalUpdates]},",
|
|
379
|
+
" {Key=MSRC_SEVERITY,Values=[Critical,Important]}",
|
|
380
|
+
" ]",
|
|
381
|
+
" },",
|
|
382
|
+
" ApprovalAfterDays=7",
|
|
383
|
+
" }]",
|
|
384
|
+
"",
|
|
385
|
+
" # Linux - Security and Bugfix patches",
|
|
386
|
+
" PatchRules=[{",
|
|
387
|
+
" PatchFilterGroup={",
|
|
388
|
+
" PatchFilters=[",
|
|
389
|
+
" {Key=CLASSIFICATION,Values=[Security,Bugfix]},",
|
|
390
|
+
" {Key=SEVERITY,Values=[Critical,Important]}",
|
|
391
|
+
" ]",
|
|
392
|
+
" },",
|
|
393
|
+
" ApprovalAfterDays=7",
|
|
394
|
+
" }]",
|
|
395
|
+
"",
|
|
396
|
+
"4. Best practices for approval rules:",
|
|
397
|
+
" - Auto-approve security patches after 7 days for production",
|
|
398
|
+
" - Auto-approve immediately for non-production environments",
|
|
399
|
+
" - Include both Security and Bugfix classifications",
|
|
400
|
+
" - Focus on Critical and Important severity levels",
|
|
401
|
+
" - Set compliance level to CRITICAL for security patches",
|
|
402
|
+
" - Create separate rules for different patch types",
|
|
403
|
+
"",
|
|
404
|
+
"5. Verify baseline configuration:",
|
|
405
|
+
" aws ssm get-patch-baseline \\",
|
|
406
|
+
" --baseline-id <baseline-id> \\",
|
|
407
|
+
" --region <region>",
|
|
408
|
+
"",
|
|
409
|
+
"Priority: HIGH - Proper patch baseline configuration is essential",
|
|
410
|
+
"Effort: Low - Can be configured quickly via CLI or Console",
|
|
411
|
+
"",
|
|
412
|
+
"AWS Documentation:",
|
|
413
|
+
"https://docs.aws.amazon.com/systems-manager/latest/userguide/patch-manager-approval-rules.html"
|
|
414
|
+
]
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class EC2PatchComplianceStatusAssessment(BaseConfigRuleAssessment):
|
|
419
|
+
"""
|
|
420
|
+
CIS Control 2.3 - Address Unauthorized Software
|
|
421
|
+
AWS Config Rule: ec2-patch-compliance-status
|
|
422
|
+
|
|
423
|
+
Ensures EC2 instances managed by Systems Manager have compliant patch status
|
|
424
|
+
and are up to date with approved patches.
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
def __init__(self):
|
|
428
|
+
super().__init__(
|
|
429
|
+
rule_name="ec2-patch-compliance-status",
|
|
430
|
+
control_id="2.3",
|
|
431
|
+
resource_types=["AWS::EC2::Instance"]
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
435
|
+
"""Get patch compliance status for all SSM-managed EC2 instances."""
|
|
436
|
+
if resource_type != "AWS::EC2::Instance":
|
|
437
|
+
return []
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
ssm_client = aws_factory.get_client('ssm', region)
|
|
441
|
+
|
|
442
|
+
instances = []
|
|
443
|
+
paginator = ssm_client.get_paginator('describe_instance_patch_states')
|
|
444
|
+
|
|
445
|
+
for page in paginator.paginate():
|
|
446
|
+
for instance_state in page.get('InstancePatchStates', []):
|
|
447
|
+
instance_id = instance_state.get('InstanceId', '')
|
|
448
|
+
patch_group = instance_state.get('PatchGroup', '')
|
|
449
|
+
baseline_id = instance_state.get('BaselineId', '')
|
|
450
|
+
operation = instance_state.get('Operation', '')
|
|
451
|
+
operation_end_time = instance_state.get('OperationEndTime', '')
|
|
452
|
+
|
|
453
|
+
# Get patch counts
|
|
454
|
+
installed_count = instance_state.get('InstalledCount', 0)
|
|
455
|
+
installed_other_count = instance_state.get('InstalledOtherCount', 0)
|
|
456
|
+
installed_pending_reboot_count = instance_state.get('InstalledPendingRebootCount', 0)
|
|
457
|
+
installed_rejected_count = instance_state.get('InstalledRejectedCount', 0)
|
|
458
|
+
missing_count = instance_state.get('MissingCount', 0)
|
|
459
|
+
failed_count = instance_state.get('FailedCount', 0)
|
|
460
|
+
not_applicable_count = instance_state.get('NotApplicableCount', 0)
|
|
461
|
+
|
|
462
|
+
# Determine compliance status
|
|
463
|
+
is_compliant = (missing_count == 0 and failed_count == 0 and
|
|
464
|
+
installed_pending_reboot_count == 0)
|
|
465
|
+
|
|
466
|
+
instances.append({
|
|
467
|
+
'InstanceId': instance_id,
|
|
468
|
+
'PatchGroup': patch_group,
|
|
469
|
+
'BaselineId': baseline_id,
|
|
470
|
+
'Operation': operation,
|
|
471
|
+
'OperationEndTime': str(operation_end_time) if operation_end_time else '',
|
|
472
|
+
'InstalledCount': installed_count,
|
|
473
|
+
'InstalledPendingRebootCount': installed_pending_reboot_count,
|
|
474
|
+
'MissingCount': missing_count,
|
|
475
|
+
'FailedCount': failed_count,
|
|
476
|
+
'IsCompliant': is_compliant
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
logger.debug(f"Found {len(instances)} SSM-managed instances with patch state in {region}")
|
|
480
|
+
return instances
|
|
481
|
+
|
|
482
|
+
except ClientError as e:
|
|
483
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
484
|
+
if error_code == 'AccessDeniedException':
|
|
485
|
+
logger.warning(f"Access denied to check patch compliance in {region}")
|
|
486
|
+
else:
|
|
487
|
+
logger.error(f"Error retrieving patch compliance from {region}: {e}")
|
|
488
|
+
return []
|
|
489
|
+
|
|
490
|
+
def _evaluate_resource_compliance(
|
|
491
|
+
self,
|
|
492
|
+
resource: Dict[str, Any],
|
|
493
|
+
aws_factory: AWSClientFactory,
|
|
494
|
+
region: str
|
|
495
|
+
) -> ComplianceResult:
|
|
496
|
+
"""Evaluate if EC2 instance has compliant patch status."""
|
|
497
|
+
instance_id = resource.get('InstanceId', 'unknown')
|
|
498
|
+
is_compliant = resource.get('IsCompliant', False)
|
|
499
|
+
missing_count = resource.get('MissingCount', 0)
|
|
500
|
+
failed_count = resource.get('FailedCount', 0)
|
|
501
|
+
pending_reboot_count = resource.get('InstalledPendingRebootCount', 0)
|
|
502
|
+
installed_count = resource.get('InstalledCount', 0)
|
|
503
|
+
patch_group = resource.get('PatchGroup', 'None')
|
|
504
|
+
|
|
505
|
+
if is_compliant:
|
|
506
|
+
evaluation_reason = (
|
|
507
|
+
f"EC2 instance '{instance_id}' (Patch Group: {patch_group}) is patch compliant. "
|
|
508
|
+
f"Installed patches: {installed_count}, Missing: 0, Failed: 0"
|
|
509
|
+
)
|
|
510
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
511
|
+
else:
|
|
512
|
+
issues = []
|
|
513
|
+
if missing_count > 0:
|
|
514
|
+
issues.append(f"{missing_count} missing patch(es)")
|
|
515
|
+
if failed_count > 0:
|
|
516
|
+
issues.append(f"{failed_count} failed patch(es)")
|
|
517
|
+
if pending_reboot_count > 0:
|
|
518
|
+
issues.append(f"{pending_reboot_count} patch(es) pending reboot")
|
|
519
|
+
|
|
520
|
+
evaluation_reason = (
|
|
521
|
+
f"EC2 instance '{instance_id}' (Patch Group: {patch_group}) is not patch compliant. "
|
|
522
|
+
f"Issues: {', '.join(issues)}"
|
|
523
|
+
)
|
|
524
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
525
|
+
|
|
526
|
+
return ComplianceResult(
|
|
527
|
+
resource_id=instance_id,
|
|
528
|
+
resource_type="AWS::EC2::Instance",
|
|
529
|
+
compliance_status=compliance_status,
|
|
530
|
+
evaluation_reason=evaluation_reason,
|
|
531
|
+
config_rule_name=self.rule_name,
|
|
532
|
+
region=region
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
536
|
+
"""Get remediation steps for achieving patch compliance."""
|
|
537
|
+
return [
|
|
538
|
+
"1. Install missing patches on non-compliant instances:",
|
|
539
|
+
" aws ssm send-command \\",
|
|
540
|
+
" --document-name 'AWS-RunPatchBaseline' \\",
|
|
541
|
+
" --instance-ids <instance-id> \\",
|
|
542
|
+
" --parameters 'Operation=Install' \\",
|
|
543
|
+
" --region <region>",
|
|
544
|
+
"",
|
|
545
|
+
"2. Scan for patch compliance without installing:",
|
|
546
|
+
" aws ssm send-command \\",
|
|
547
|
+
" --document-name 'AWS-RunPatchBaseline' \\",
|
|
548
|
+
" --instance-ids <instance-id> \\",
|
|
549
|
+
" --parameters 'Operation=Scan' \\",
|
|
550
|
+
" --region <region>",
|
|
551
|
+
"",
|
|
552
|
+
"3. Check patch compliance status:",
|
|
553
|
+
" aws ssm describe-instance-patch-states \\",
|
|
554
|
+
" --instance-ids <instance-id> \\",
|
|
555
|
+
" --region <region>",
|
|
556
|
+
"",
|
|
557
|
+
"4. Get detailed patch information:",
|
|
558
|
+
" aws ssm describe-instance-patches \\",
|
|
559
|
+
" --instance-id <instance-id> \\",
|
|
560
|
+
" --region <region>",
|
|
561
|
+
"",
|
|
562
|
+
"5. Console method:",
|
|
563
|
+
" - Navigate to AWS Systems Manager",
|
|
564
|
+
" - Click 'Patch Manager' in the left menu",
|
|
565
|
+
" - Click 'Patch now' button",
|
|
566
|
+
" - Select 'Scan and install'",
|
|
567
|
+
" - Choose instances:",
|
|
568
|
+
" * Select instances manually, or",
|
|
569
|
+
" * Use tags to select instances, or",
|
|
570
|
+
" * Select by patch group",
|
|
571
|
+
" - Configure patching:",
|
|
572
|
+
" * Patching operation: Scan and install",
|
|
573
|
+
" * Reboot option: Reboot if needed / No reboot",
|
|
574
|
+
" * Concurrency: Number or percentage",
|
|
575
|
+
" * Error threshold: Number or percentage",
|
|
576
|
+
" - Click 'Patch now'",
|
|
577
|
+
"",
|
|
578
|
+
"6. Reboot instances with pending reboot patches:",
|
|
579
|
+
" aws ec2 reboot-instances \\",
|
|
580
|
+
" --instance-ids <instance-id> \\",
|
|
581
|
+
" --region <region>",
|
|
582
|
+
"",
|
|
583
|
+
"7. Set up automated patching with maintenance windows:",
|
|
584
|
+
" # Create maintenance window (if not exists)",
|
|
585
|
+
" aws ssm create-maintenance-window \\",
|
|
586
|
+
" --name 'Weekly-Patching' \\",
|
|
587
|
+
" --schedule 'cron(0 2 ? * SUN *)' \\",
|
|
588
|
+
" --duration 4 \\",
|
|
589
|
+
" --cutoff 1 \\",
|
|
590
|
+
" --region <region>",
|
|
591
|
+
"",
|
|
592
|
+
"8. Troubleshoot failed patches:",
|
|
593
|
+
" # View patch execution logs",
|
|
594
|
+
" aws ssm get-command-invocation \\",
|
|
595
|
+
" --command-id <command-id> \\",
|
|
596
|
+
" --instance-id <instance-id> \\",
|
|
597
|
+
" --region <region>",
|
|
598
|
+
"",
|
|
599
|
+
" # Common failure reasons:",
|
|
600
|
+
" - Insufficient disk space",
|
|
601
|
+
" - Network connectivity issues",
|
|
602
|
+
" - Package manager conflicts",
|
|
603
|
+
" - Missing dependencies",
|
|
604
|
+
"",
|
|
605
|
+
"9. Best practices:",
|
|
606
|
+
" - Schedule regular patching windows (weekly or monthly)",
|
|
607
|
+
" - Test patches in non-production first",
|
|
608
|
+
" - Allow automatic reboots during maintenance windows",
|
|
609
|
+
" - Monitor patch compliance with CloudWatch",
|
|
610
|
+
" - Set up SNS notifications for patch failures",
|
|
611
|
+
" - Use patch groups to organize instances",
|
|
612
|
+
" - Maintain at least 7-day approval delay for production",
|
|
613
|
+
"",
|
|
614
|
+
"10. Verify compliance after patching:",
|
|
615
|
+
" # Wait for patching to complete, then check status",
|
|
616
|
+
" aws ssm describe-instance-patch-states \\",
|
|
617
|
+
" --instance-ids <instance-id> \\",
|
|
618
|
+
" --query 'InstancePatchStates[0].{Missing:MissingCount,Failed:FailedCount,PendingReboot:InstalledPendingRebootCount}' \\",
|
|
619
|
+
" --region <region>",
|
|
620
|
+
"",
|
|
621
|
+
"Priority: CRITICAL - Unpatched systems are vulnerable to exploits",
|
|
622
|
+
"Effort: Medium - Requires patching execution and potential reboots",
|
|
623
|
+
"",
|
|
624
|
+
"AWS Documentation:",
|
|
625
|
+
"https://docs.aws.amazon.com/systems-manager/latest/userguide/patch-manager-compliance.html"
|
|
626
|
+
]
|