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.
Files changed (40) hide show
  1. aws_cis_assessment/__init__.py +4 -4
  2. aws_cis_assessment/config/rules/cis_controls_ig1.yaml +365 -2
  3. aws_cis_assessment/controls/ig1/control_access_analyzer.py +198 -0
  4. aws_cis_assessment/controls/ig1/control_access_asset_mgmt.py +360 -0
  5. aws_cis_assessment/controls/ig1/control_access_control.py +323 -0
  6. aws_cis_assessment/controls/ig1/control_backup_security.py +579 -0
  7. aws_cis_assessment/controls/ig1/control_cloudfront_logging.py +215 -0
  8. aws_cis_assessment/controls/ig1/control_configuration_mgmt.py +407 -0
  9. aws_cis_assessment/controls/ig1/control_data_classification.py +255 -0
  10. aws_cis_assessment/controls/ig1/control_dynamodb_encryption.py +279 -0
  11. aws_cis_assessment/controls/ig1/control_ebs_encryption.py +177 -0
  12. aws_cis_assessment/controls/ig1/control_efs_encryption.py +243 -0
  13. aws_cis_assessment/controls/ig1/control_elb_logging.py +195 -0
  14. aws_cis_assessment/controls/ig1/control_guardduty.py +156 -0
  15. aws_cis_assessment/controls/ig1/control_inspector.py +184 -0
  16. aws_cis_assessment/controls/ig1/control_inventory.py +511 -0
  17. aws_cis_assessment/controls/ig1/control_macie.py +165 -0
  18. aws_cis_assessment/controls/ig1/control_messaging_encryption.py +419 -0
  19. aws_cis_assessment/controls/ig1/control_mfa.py +485 -0
  20. aws_cis_assessment/controls/ig1/control_network_security.py +194 -619
  21. aws_cis_assessment/controls/ig1/control_patch_management.py +626 -0
  22. aws_cis_assessment/controls/ig1/control_rds_encryption.py +228 -0
  23. aws_cis_assessment/controls/ig1/control_s3_encryption.py +383 -0
  24. aws_cis_assessment/controls/ig1/control_tls_ssl.py +556 -0
  25. aws_cis_assessment/controls/ig1/control_version_mgmt.py +329 -0
  26. aws_cis_assessment/controls/ig1/control_vpc_flow_logs.py +205 -0
  27. aws_cis_assessment/controls/ig1/control_waf_logging.py +226 -0
  28. aws_cis_assessment/core/models.py +20 -1
  29. aws_cis_assessment/core/scoring_engine.py +98 -1
  30. aws_cis_assessment/reporters/base_reporter.py +31 -1
  31. aws_cis_assessment/reporters/html_reporter.py +163 -0
  32. aws_cis_controls_assessment-1.2.0.dist-info/METADATA +320 -0
  33. {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/RECORD +39 -15
  34. docs/developer-guide.md +204 -5
  35. docs/user-guide.md +137 -4
  36. aws_cis_controls_assessment-1.1.4.dist-info/METADATA +0 -404
  37. {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/WHEEL +0 -0
  38. {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/entry_points.txt +0 -0
  39. {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/licenses/LICENSE +0 -0
  40. {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
+ ]