aws-cis-controls-assessment 1.1.4__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 +163 -0
- aws_cis_controls_assessment-1.2.0.dist-info/METADATA +320 -0
- {aws_cis_controls_assessment-1.1.4.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.4.dist-info/METADATA +0 -404
- {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/WHEEL +0 -0
- {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/entry_points.txt +0 -0
- {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CIS Control 2.13, 2.14, 2.11 - Messaging and Data Event Encryption
|
|
3
|
+
Ensures SNS topics, SQS queues use KMS encryption and CloudTrail logs S3 data events.
|
|
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 SNSEncryptedKMSAssessment(BaseConfigRuleAssessment):
|
|
18
|
+
"""
|
|
19
|
+
CIS Control 2.13 - Encrypt Sensitive Data at Rest
|
|
20
|
+
AWS Config Rule: sns-encrypted-kms
|
|
21
|
+
|
|
22
|
+
Ensures SNS topics are encrypted with customer-managed KMS keys.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
super().__init__(
|
|
27
|
+
rule_name="sns-encrypted-kms",
|
|
28
|
+
control_id="2.13",
|
|
29
|
+
resource_types=["AWS::SNS::Topic"]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
33
|
+
"""Get all SNS topics and their encryption configuration."""
|
|
34
|
+
if resource_type != "AWS::SNS::Topic":
|
|
35
|
+
return []
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
sns_client = aws_factory.get_client('sns', region)
|
|
39
|
+
topics = []
|
|
40
|
+
|
|
41
|
+
# List all topics
|
|
42
|
+
paginator = sns_client.get_paginator('list_topics')
|
|
43
|
+
for page in paginator.paginate():
|
|
44
|
+
for topic in page.get('Topics', []):
|
|
45
|
+
topic_arn = topic.get('TopicArn', '')
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
# Get topic attributes
|
|
49
|
+
attrs_response = sns_client.get_topic_attributes(TopicArn=topic_arn)
|
|
50
|
+
attributes = attrs_response.get('Attributes', {})
|
|
51
|
+
|
|
52
|
+
kms_key_id = attributes.get('KmsMasterKeyId', '')
|
|
53
|
+
encrypted_with_kms = bool(kms_key_id)
|
|
54
|
+
|
|
55
|
+
topics.append({
|
|
56
|
+
'TopicArn': topic_arn,
|
|
57
|
+
'TopicName': topic_arn.split(':')[-1],
|
|
58
|
+
'KmsMasterKeyId': kms_key_id,
|
|
59
|
+
'EncryptedWithKMS': encrypted_with_kms
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
except ClientError as e:
|
|
63
|
+
logger.warning(f"Error getting attributes for topic {topic_arn}: {e}")
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
logger.debug(f"Found {len(topics)} SNS topics in {region}")
|
|
67
|
+
return topics
|
|
68
|
+
|
|
69
|
+
except ClientError as e:
|
|
70
|
+
logger.error(f"Error retrieving SNS topics in {region}: {e}")
|
|
71
|
+
return []
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(f"Unexpected error retrieving SNS topics in {region}: {e}")
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
def _evaluate_resource_compliance(
|
|
77
|
+
self,
|
|
78
|
+
resource: Dict[str, Any],
|
|
79
|
+
aws_factory: AWSClientFactory,
|
|
80
|
+
region: str
|
|
81
|
+
) -> ComplianceResult:
|
|
82
|
+
"""Evaluate if SNS topic is encrypted with KMS."""
|
|
83
|
+
topic_arn = resource.get('TopicArn', 'unknown')
|
|
84
|
+
topic_name = resource.get('TopicName', 'unknown')
|
|
85
|
+
encrypted_with_kms = resource.get('EncryptedWithKMS', False)
|
|
86
|
+
kms_key_id = resource.get('KmsMasterKeyId', '')
|
|
87
|
+
|
|
88
|
+
if encrypted_with_kms:
|
|
89
|
+
evaluation_reason = (
|
|
90
|
+
f"SNS topic '{topic_name}' is encrypted with KMS key: {kms_key_id}"
|
|
91
|
+
)
|
|
92
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
93
|
+
else:
|
|
94
|
+
evaluation_reason = (
|
|
95
|
+
f"SNS topic '{topic_name}' is not encrypted with a KMS key"
|
|
96
|
+
)
|
|
97
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
98
|
+
|
|
99
|
+
return ComplianceResult(
|
|
100
|
+
resource_id=topic_arn,
|
|
101
|
+
resource_type="AWS::SNS::Topic",
|
|
102
|
+
compliance_status=compliance_status,
|
|
103
|
+
evaluation_reason=evaluation_reason,
|
|
104
|
+
config_rule_name=self.rule_name,
|
|
105
|
+
region=region
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
109
|
+
"""Get remediation steps for enabling SNS KMS encryption."""
|
|
110
|
+
return [
|
|
111
|
+
"1. Enable KMS encryption on an existing SNS topic:",
|
|
112
|
+
" aws sns set-topic-attributes \\",
|
|
113
|
+
" --topic-arn <topic-arn> \\",
|
|
114
|
+
" --attribute-name KmsMasterKeyId \\",
|
|
115
|
+
" --attribute-value <kms-key-id>",
|
|
116
|
+
"",
|
|
117
|
+
"2. Create a new encrypted SNS topic:",
|
|
118
|
+
" aws sns create-topic \\",
|
|
119
|
+
" --name <topic-name> \\",
|
|
120
|
+
" --attributes KmsMasterKeyId=<kms-key-id>",
|
|
121
|
+
"",
|
|
122
|
+
"3. Console method:",
|
|
123
|
+
" - Navigate to SNS service",
|
|
124
|
+
" - Select the topic",
|
|
125
|
+
" - Click 'Edit'",
|
|
126
|
+
" - Expand 'Encryption'",
|
|
127
|
+
" - Select 'Enable encryption'",
|
|
128
|
+
" - Choose your KMS key",
|
|
129
|
+
" - Click 'Save changes'",
|
|
130
|
+
"",
|
|
131
|
+
"4. Verify encryption:",
|
|
132
|
+
" aws sns get-topic-attributes \\",
|
|
133
|
+
" --topic-arn <topic-arn> \\",
|
|
134
|
+
" --query 'Attributes.KmsMasterKeyId'",
|
|
135
|
+
"",
|
|
136
|
+
"Priority: HIGH - Encryption protects message data at rest",
|
|
137
|
+
"Effort: Low - Single API call per topic",
|
|
138
|
+
"",
|
|
139
|
+
"AWS Documentation:",
|
|
140
|
+
"https://docs.aws.amazon.com/sns/latest/dg/sns-server-side-encryption.html"
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class SQSQueueEncryptedAssessment(BaseConfigRuleAssessment):
|
|
145
|
+
"""
|
|
146
|
+
CIS Control 2.14 - Encrypt Sensitive Data at Rest
|
|
147
|
+
AWS Config Rule: sqs-queue-encrypted
|
|
148
|
+
|
|
149
|
+
Ensures SQS queues are encrypted at rest.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
def __init__(self):
|
|
153
|
+
super().__init__(
|
|
154
|
+
rule_name="sqs-queue-encrypted",
|
|
155
|
+
control_id="2.14",
|
|
156
|
+
resource_types=["AWS::SQS::Queue"]
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
160
|
+
"""Get all SQS queues and their encryption configuration."""
|
|
161
|
+
if resource_type != "AWS::SQS::Queue":
|
|
162
|
+
return []
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
sqs_client = aws_factory.get_client('sqs', region)
|
|
166
|
+
queues = []
|
|
167
|
+
|
|
168
|
+
# List all queues
|
|
169
|
+
response = sqs_client.list_queues()
|
|
170
|
+
queue_urls = response.get('QueueUrls', [])
|
|
171
|
+
|
|
172
|
+
for queue_url in queue_urls:
|
|
173
|
+
try:
|
|
174
|
+
# Get queue attributes
|
|
175
|
+
attrs_response = sqs_client.get_queue_attributes(
|
|
176
|
+
QueueUrl=queue_url,
|
|
177
|
+
AttributeNames=['All']
|
|
178
|
+
)
|
|
179
|
+
attributes = attrs_response.get('Attributes', {})
|
|
180
|
+
|
|
181
|
+
kms_key_id = attributes.get('KmsMasterKeyId', '')
|
|
182
|
+
encrypted = bool(kms_key_id)
|
|
183
|
+
queue_name = queue_url.split('/')[-1]
|
|
184
|
+
|
|
185
|
+
queues.append({
|
|
186
|
+
'QueueUrl': queue_url,
|
|
187
|
+
'QueueName': queue_name,
|
|
188
|
+
'KmsMasterKeyId': kms_key_id,
|
|
189
|
+
'Encrypted': encrypted
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
except ClientError as e:
|
|
193
|
+
logger.warning(f"Error getting attributes for queue {queue_url}: {e}")
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
logger.debug(f"Found {len(queues)} SQS queues in {region}")
|
|
197
|
+
return queues
|
|
198
|
+
|
|
199
|
+
except ClientError as e:
|
|
200
|
+
logger.error(f"Error retrieving SQS queues in {region}: {e}")
|
|
201
|
+
return []
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"Unexpected error retrieving SQS queues in {region}: {e}")
|
|
204
|
+
return []
|
|
205
|
+
|
|
206
|
+
def _evaluate_resource_compliance(
|
|
207
|
+
self,
|
|
208
|
+
resource: Dict[str, Any],
|
|
209
|
+
aws_factory: AWSClientFactory,
|
|
210
|
+
region: str
|
|
211
|
+
) -> ComplianceResult:
|
|
212
|
+
"""Evaluate if SQS queue is encrypted."""
|
|
213
|
+
queue_url = resource.get('QueueUrl', 'unknown')
|
|
214
|
+
queue_name = resource.get('QueueName', 'unknown')
|
|
215
|
+
encrypted = resource.get('Encrypted', False)
|
|
216
|
+
kms_key_id = resource.get('KmsMasterKeyId', '')
|
|
217
|
+
|
|
218
|
+
if encrypted:
|
|
219
|
+
evaluation_reason = (
|
|
220
|
+
f"SQS queue '{queue_name}' is encrypted with KMS key: {kms_key_id}"
|
|
221
|
+
)
|
|
222
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
223
|
+
else:
|
|
224
|
+
evaluation_reason = (
|
|
225
|
+
f"SQS queue '{queue_name}' is not encrypted"
|
|
226
|
+
)
|
|
227
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
228
|
+
|
|
229
|
+
return ComplianceResult(
|
|
230
|
+
resource_id=queue_url,
|
|
231
|
+
resource_type="AWS::SQS::Queue",
|
|
232
|
+
compliance_status=compliance_status,
|
|
233
|
+
evaluation_reason=evaluation_reason,
|
|
234
|
+
config_rule_name=self.rule_name,
|
|
235
|
+
region=region
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
239
|
+
"""Get remediation steps for enabling SQS encryption."""
|
|
240
|
+
return [
|
|
241
|
+
"1. Enable encryption on an existing SQS queue:",
|
|
242
|
+
" aws sqs set-queue-attributes \\",
|
|
243
|
+
" --queue-url <queue-url> \\",
|
|
244
|
+
" --attributes KmsMasterKeyId=<kms-key-id>",
|
|
245
|
+
"",
|
|
246
|
+
"2. Create a new encrypted SQS queue:",
|
|
247
|
+
" aws sqs create-queue \\",
|
|
248
|
+
" --queue-name <queue-name> \\",
|
|
249
|
+
" --attributes KmsMasterKeyId=<kms-key-id>",
|
|
250
|
+
"",
|
|
251
|
+
"3. Console method:",
|
|
252
|
+
" - Navigate to SQS service",
|
|
253
|
+
" - Select the queue",
|
|
254
|
+
" - Click 'Edit'",
|
|
255
|
+
" - Expand 'Encryption'",
|
|
256
|
+
" - Select 'Enabled'",
|
|
257
|
+
" - Choose your KMS key",
|
|
258
|
+
" - Click 'Save'",
|
|
259
|
+
"",
|
|
260
|
+
"4. Verify encryption:",
|
|
261
|
+
" aws sqs get-queue-attributes \\",
|
|
262
|
+
" --queue-url <queue-url> \\",
|
|
263
|
+
" --attribute-names KmsMasterKeyId",
|
|
264
|
+
"",
|
|
265
|
+
"Priority: HIGH - Encryption protects message data at rest",
|
|
266
|
+
"Effort: Low - Single API call per queue",
|
|
267
|
+
"",
|
|
268
|
+
"AWS Documentation:",
|
|
269
|
+
"https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-server-side-encryption.html"
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class CloudTrailS3DataEventsEnabledAssessment(BaseConfigRuleAssessment):
|
|
274
|
+
"""
|
|
275
|
+
CIS Control 2.11 - Audit Log Management
|
|
276
|
+
AWS Config Rule: cloudtrail-s3-dataevents-enabled
|
|
277
|
+
|
|
278
|
+
Ensures CloudTrail logs S3 data events for monitoring object-level operations.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
def __init__(self):
|
|
282
|
+
super().__init__(
|
|
283
|
+
rule_name="cloudtrail-s3-dataevents-enabled",
|
|
284
|
+
control_id="2.11",
|
|
285
|
+
resource_types=["AWS::CloudTrail::Trail"]
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
289
|
+
"""Get all CloudTrail trails and their event selector configuration."""
|
|
290
|
+
if resource_type != "AWS::CloudTrail::Trail":
|
|
291
|
+
return []
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
cloudtrail_client = aws_factory.get_client('cloudtrail', region)
|
|
295
|
+
trails = []
|
|
296
|
+
|
|
297
|
+
# List all trails
|
|
298
|
+
response = cloudtrail_client.list_trails()
|
|
299
|
+
trail_list = response.get('Trails', [])
|
|
300
|
+
|
|
301
|
+
for trail_info in trail_list:
|
|
302
|
+
trail_arn = trail_info.get('TrailARN', '')
|
|
303
|
+
trail_name = trail_info.get('Name', '')
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
# Get event selectors
|
|
307
|
+
selectors_response = cloudtrail_client.get_event_selectors(TrailName=trail_name)
|
|
308
|
+
event_selectors = selectors_response.get('EventSelectors', [])
|
|
309
|
+
|
|
310
|
+
# Check if S3 data events are enabled
|
|
311
|
+
s3_data_events_enabled = False
|
|
312
|
+
for selector in event_selectors:
|
|
313
|
+
data_resources = selector.get('DataResources', [])
|
|
314
|
+
for resource in data_resources:
|
|
315
|
+
if resource.get('Type') == 'AWS::S3::Object':
|
|
316
|
+
s3_data_events_enabled = True
|
|
317
|
+
break
|
|
318
|
+
if s3_data_events_enabled:
|
|
319
|
+
break
|
|
320
|
+
|
|
321
|
+
trails.append({
|
|
322
|
+
'TrailARN': trail_arn,
|
|
323
|
+
'TrailName': trail_name,
|
|
324
|
+
'S3DataEventsEnabled': s3_data_events_enabled,
|
|
325
|
+
'EventSelectors': event_selectors
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
except ClientError as e:
|
|
329
|
+
logger.warning(f"Error getting event selectors for trail {trail_name}: {e}")
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
logger.debug(f"Found {len(trails)} CloudTrail trails in {region}")
|
|
333
|
+
return trails
|
|
334
|
+
|
|
335
|
+
except ClientError as e:
|
|
336
|
+
logger.error(f"Error retrieving CloudTrail trails in {region}: {e}")
|
|
337
|
+
return []
|
|
338
|
+
except Exception as e:
|
|
339
|
+
logger.error(f"Unexpected error retrieving CloudTrail trails in {region}: {e}")
|
|
340
|
+
return []
|
|
341
|
+
|
|
342
|
+
def _evaluate_resource_compliance(
|
|
343
|
+
self,
|
|
344
|
+
resource: Dict[str, Any],
|
|
345
|
+
aws_factory: AWSClientFactory,
|
|
346
|
+
region: str
|
|
347
|
+
) -> ComplianceResult:
|
|
348
|
+
"""Evaluate if CloudTrail trail logs S3 data events."""
|
|
349
|
+
trail_arn = resource.get('TrailARN', 'unknown')
|
|
350
|
+
trail_name = resource.get('TrailName', 'unknown')
|
|
351
|
+
s3_data_events_enabled = resource.get('S3DataEventsEnabled', False)
|
|
352
|
+
|
|
353
|
+
if s3_data_events_enabled:
|
|
354
|
+
evaluation_reason = (
|
|
355
|
+
f"CloudTrail trail '{trail_name}' has S3 data events logging enabled"
|
|
356
|
+
)
|
|
357
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
358
|
+
else:
|
|
359
|
+
evaluation_reason = (
|
|
360
|
+
f"CloudTrail trail '{trail_name}' does not have S3 data events logging enabled"
|
|
361
|
+
)
|
|
362
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
363
|
+
|
|
364
|
+
return ComplianceResult(
|
|
365
|
+
resource_id=trail_arn,
|
|
366
|
+
resource_type="AWS::CloudTrail::Trail",
|
|
367
|
+
compliance_status=compliance_status,
|
|
368
|
+
evaluation_reason=evaluation_reason,
|
|
369
|
+
config_rule_name=self.rule_name,
|
|
370
|
+
region=region
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
374
|
+
"""Get remediation steps for enabling CloudTrail S3 data events."""
|
|
375
|
+
return [
|
|
376
|
+
"1. Enable S3 data events on an existing CloudTrail trail:",
|
|
377
|
+
" aws cloudtrail put-event-selectors \\",
|
|
378
|
+
" --trail-name <trail-name> \\",
|
|
379
|
+
" --event-selectors '[{",
|
|
380
|
+
' "ReadWriteType": "All",',
|
|
381
|
+
' "IncludeManagementEvents": true,',
|
|
382
|
+
' "DataResources": [{',
|
|
383
|
+
' "Type": "AWS::S3::Object",',
|
|
384
|
+
' "Values": ["arn:aws:s3:::<bucket-name>/*"]',
|
|
385
|
+
" }]",
|
|
386
|
+
" }]'",
|
|
387
|
+
"",
|
|
388
|
+
"2. Enable for all S3 buckets:",
|
|
389
|
+
" aws cloudtrail put-event-selectors \\",
|
|
390
|
+
" --trail-name <trail-name> \\",
|
|
391
|
+
" --event-selectors '[{",
|
|
392
|
+
' "ReadWriteType": "All",',
|
|
393
|
+
' "IncludeManagementEvents": true,',
|
|
394
|
+
' "DataResources": [{',
|
|
395
|
+
' "Type": "AWS::S3::Object",',
|
|
396
|
+
' "Values": ["arn:aws:s3:::*/*"]',
|
|
397
|
+
" }]",
|
|
398
|
+
" }]'",
|
|
399
|
+
"",
|
|
400
|
+
"3. Console method:",
|
|
401
|
+
" - Navigate to CloudTrail service",
|
|
402
|
+
" - Select the trail",
|
|
403
|
+
" - Click 'Edit'",
|
|
404
|
+
" - Under 'Data events', click 'Add data event type'",
|
|
405
|
+
" - Select 'S3'",
|
|
406
|
+
" - Choose 'All current and future S3 buckets' or specific buckets",
|
|
407
|
+
" - Select 'Read' and 'Write' events",
|
|
408
|
+
" - Click 'Save changes'",
|
|
409
|
+
"",
|
|
410
|
+
"4. Verify configuration:",
|
|
411
|
+
" aws cloudtrail get-event-selectors \\",
|
|
412
|
+
" --trail-name <trail-name>",
|
|
413
|
+
"",
|
|
414
|
+
"Priority: MEDIUM - Important for S3 audit logging",
|
|
415
|
+
"Effort: Low - Single API call, but note additional CloudTrail costs",
|
|
416
|
+
"",
|
|
417
|
+
"AWS Documentation:",
|
|
418
|
+
"https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html"
|
|
419
|
+
]
|