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.
Files changed (77) hide show
  1. aws_cis_assessment/__init__.py +11 -0
  2. aws_cis_assessment/cli/__init__.py +3 -0
  3. aws_cis_assessment/cli/examples.py +274 -0
  4. aws_cis_assessment/cli/main.py +1259 -0
  5. aws_cis_assessment/cli/utils.py +356 -0
  6. aws_cis_assessment/config/__init__.py +1 -0
  7. aws_cis_assessment/config/config_loader.py +328 -0
  8. aws_cis_assessment/config/rules/cis_controls_ig1.yaml +590 -0
  9. aws_cis_assessment/config/rules/cis_controls_ig2.yaml +412 -0
  10. aws_cis_assessment/config/rules/cis_controls_ig3.yaml +100 -0
  11. aws_cis_assessment/controls/__init__.py +1 -0
  12. aws_cis_assessment/controls/base_control.py +400 -0
  13. aws_cis_assessment/controls/ig1/__init__.py +239 -0
  14. aws_cis_assessment/controls/ig1/control_1_1.py +586 -0
  15. aws_cis_assessment/controls/ig1/control_2_2.py +231 -0
  16. aws_cis_assessment/controls/ig1/control_3_3.py +718 -0
  17. aws_cis_assessment/controls/ig1/control_3_4.py +235 -0
  18. aws_cis_assessment/controls/ig1/control_4_1.py +461 -0
  19. aws_cis_assessment/controls/ig1/control_access_keys.py +310 -0
  20. aws_cis_assessment/controls/ig1/control_advanced_security.py +512 -0
  21. aws_cis_assessment/controls/ig1/control_backup_recovery.py +510 -0
  22. aws_cis_assessment/controls/ig1/control_cloudtrail_logging.py +197 -0
  23. aws_cis_assessment/controls/ig1/control_critical_security.py +422 -0
  24. aws_cis_assessment/controls/ig1/control_data_protection.py +898 -0
  25. aws_cis_assessment/controls/ig1/control_iam_advanced.py +573 -0
  26. aws_cis_assessment/controls/ig1/control_iam_governance.py +493 -0
  27. aws_cis_assessment/controls/ig1/control_iam_policies.py +383 -0
  28. aws_cis_assessment/controls/ig1/control_instance_optimization.py +100 -0
  29. aws_cis_assessment/controls/ig1/control_network_enhancements.py +203 -0
  30. aws_cis_assessment/controls/ig1/control_network_security.py +672 -0
  31. aws_cis_assessment/controls/ig1/control_s3_enhancements.py +173 -0
  32. aws_cis_assessment/controls/ig1/control_s3_security.py +422 -0
  33. aws_cis_assessment/controls/ig1/control_vpc_security.py +235 -0
  34. aws_cis_assessment/controls/ig2/__init__.py +172 -0
  35. aws_cis_assessment/controls/ig2/control_3_10.py +698 -0
  36. aws_cis_assessment/controls/ig2/control_3_11.py +1330 -0
  37. aws_cis_assessment/controls/ig2/control_5_2.py +393 -0
  38. aws_cis_assessment/controls/ig2/control_advanced_encryption.py +355 -0
  39. aws_cis_assessment/controls/ig2/control_codebuild_security.py +263 -0
  40. aws_cis_assessment/controls/ig2/control_encryption_rest.py +382 -0
  41. aws_cis_assessment/controls/ig2/control_encryption_transit.py +382 -0
  42. aws_cis_assessment/controls/ig2/control_network_ha.py +467 -0
  43. aws_cis_assessment/controls/ig2/control_remaining_encryption.py +426 -0
  44. aws_cis_assessment/controls/ig2/control_remaining_rules.py +363 -0
  45. aws_cis_assessment/controls/ig2/control_service_logging.py +402 -0
  46. aws_cis_assessment/controls/ig3/__init__.py +49 -0
  47. aws_cis_assessment/controls/ig3/control_12_8.py +395 -0
  48. aws_cis_assessment/controls/ig3/control_13_1.py +467 -0
  49. aws_cis_assessment/controls/ig3/control_3_14.py +523 -0
  50. aws_cis_assessment/controls/ig3/control_7_1.py +359 -0
  51. aws_cis_assessment/core/__init__.py +1 -0
  52. aws_cis_assessment/core/accuracy_validator.py +425 -0
  53. aws_cis_assessment/core/assessment_engine.py +1266 -0
  54. aws_cis_assessment/core/audit_trail.py +491 -0
  55. aws_cis_assessment/core/aws_client_factory.py +313 -0
  56. aws_cis_assessment/core/error_handler.py +607 -0
  57. aws_cis_assessment/core/models.py +166 -0
  58. aws_cis_assessment/core/scoring_engine.py +459 -0
  59. aws_cis_assessment/reporters/__init__.py +8 -0
  60. aws_cis_assessment/reporters/base_reporter.py +454 -0
  61. aws_cis_assessment/reporters/csv_reporter.py +835 -0
  62. aws_cis_assessment/reporters/html_reporter.py +2162 -0
  63. aws_cis_assessment/reporters/json_reporter.py +561 -0
  64. aws_cis_controls_assessment-1.0.3.dist-info/METADATA +248 -0
  65. aws_cis_controls_assessment-1.0.3.dist-info/RECORD +77 -0
  66. aws_cis_controls_assessment-1.0.3.dist-info/WHEEL +5 -0
  67. aws_cis_controls_assessment-1.0.3.dist-info/entry_points.txt +2 -0
  68. aws_cis_controls_assessment-1.0.3.dist-info/licenses/LICENSE +21 -0
  69. aws_cis_controls_assessment-1.0.3.dist-info/top_level.txt +2 -0
  70. docs/README.md +94 -0
  71. docs/assessment-logic.md +766 -0
  72. docs/cli-reference.md +698 -0
  73. docs/config-rule-mappings.md +393 -0
  74. docs/developer-guide.md +858 -0
  75. docs/installation.md +299 -0
  76. docs/troubleshooting.md +634 -0
  77. docs/user-guide.md +487 -0
@@ -0,0 +1,426 @@
1
+ """Remaining Encryption Rules - AWS Config rule assessments."""
2
+
3
+ from typing import Dict, List, Any
4
+ import logging
5
+ from botocore.exceptions import ClientError
6
+
7
+ from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
8
+ from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
9
+ from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class OpenSearchEncryptedAtRestAssessment(BaseConfigRuleAssessment):
15
+ """Assessment for opensearch-encrypted-at-rest AWS Config rule."""
16
+
17
+ def __init__(self):
18
+ super().__init__(
19
+ rule_name="opensearch-encrypted-at-rest",
20
+ control_id="3.11",
21
+ resource_types=["AWS::OpenSearch::Domain"]
22
+ )
23
+
24
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
25
+ """Get OpenSearch domains."""
26
+ if resource_type != "AWS::OpenSearch::Domain":
27
+ return []
28
+
29
+ try:
30
+ opensearch_client = aws_factory.get_client('opensearch', region)
31
+
32
+ response = aws_factory.aws_api_call_with_retry(
33
+ lambda: opensearch_client.list_domain_names()
34
+ )
35
+
36
+ domains = []
37
+ for domain in response.get('DomainNames', []):
38
+ domains.append({
39
+ 'DomainName': domain.get('DomainName')
40
+ })
41
+
42
+ return domains
43
+
44
+ except ClientError as e:
45
+ logger.error(f"Error retrieving OpenSearch domains in region {region}: {e}")
46
+ raise
47
+
48
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
49
+ """Evaluate if OpenSearch domain has encryption at rest enabled."""
50
+ domain_name = resource.get('DomainName', 'unknown')
51
+
52
+ try:
53
+ opensearch_client = aws_factory.get_client('opensearch', region)
54
+
55
+ response = aws_factory.aws_api_call_with_retry(
56
+ lambda: opensearch_client.describe_domain(DomainName=domain_name)
57
+ )
58
+
59
+ domain_status = response.get('DomainStatus', {})
60
+ encryption_config = domain_status.get('EncryptionAtRestOptions', {})
61
+ encryption_enabled = encryption_config.get('Enabled', False)
62
+
63
+ if encryption_enabled:
64
+ compliance_status = ComplianceStatus.COMPLIANT
65
+ evaluation_reason = f"OpenSearch domain {domain_name} has encryption at rest enabled"
66
+ else:
67
+ compliance_status = ComplianceStatus.NON_COMPLIANT
68
+ evaluation_reason = f"OpenSearch domain {domain_name} does not have encryption at rest enabled"
69
+
70
+ except ClientError as e:
71
+ compliance_status = ComplianceStatus.ERROR
72
+ evaluation_reason = f"Error checking encryption for OpenSearch domain {domain_name}: {str(e)}"
73
+
74
+ return ComplianceResult(
75
+ resource_id=domain_name,
76
+ resource_type="AWS::OpenSearch::Domain",
77
+ compliance_status=compliance_status,
78
+ evaluation_reason=evaluation_reason,
79
+ config_rule_name=self.rule_name,
80
+ region=region
81
+ )
82
+
83
+
84
+ class OpenSearchNodeToNodeEncryptionCheckAssessment(BaseConfigRuleAssessment):
85
+ """Assessment for opensearch-node-to-node-encryption-check AWS Config rule."""
86
+
87
+ def __init__(self):
88
+ super().__init__(
89
+ rule_name="opensearch-node-to-node-encryption-check",
90
+ control_id="3.10",
91
+ resource_types=["AWS::OpenSearch::Domain"]
92
+ )
93
+
94
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
95
+ """Get OpenSearch domains."""
96
+ if resource_type != "AWS::OpenSearch::Domain":
97
+ return []
98
+
99
+ try:
100
+ opensearch_client = aws_factory.get_client('opensearch', region)
101
+
102
+ response = aws_factory.aws_api_call_with_retry(
103
+ lambda: opensearch_client.list_domain_names()
104
+ )
105
+
106
+ domains = []
107
+ for domain in response.get('DomainNames', []):
108
+ domains.append({
109
+ 'DomainName': domain.get('DomainName')
110
+ })
111
+
112
+ return domains
113
+
114
+ except ClientError as e:
115
+ logger.error(f"Error retrieving OpenSearch domains in region {region}: {e}")
116
+ raise
117
+
118
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
119
+ """Evaluate if OpenSearch domain has node-to-node encryption enabled."""
120
+ domain_name = resource.get('DomainName', 'unknown')
121
+
122
+ try:
123
+ opensearch_client = aws_factory.get_client('opensearch', region)
124
+
125
+ response = aws_factory.aws_api_call_with_retry(
126
+ lambda: opensearch_client.describe_domain(DomainName=domain_name)
127
+ )
128
+
129
+ domain_status = response.get('DomainStatus', {})
130
+ node_to_node_encryption = domain_status.get('NodeToNodeEncryptionOptions', {})
131
+ encryption_enabled = node_to_node_encryption.get('Enabled', False)
132
+
133
+ if encryption_enabled:
134
+ compliance_status = ComplianceStatus.COMPLIANT
135
+ evaluation_reason = f"OpenSearch domain {domain_name} has node-to-node encryption enabled"
136
+ else:
137
+ compliance_status = ComplianceStatus.NON_COMPLIANT
138
+ evaluation_reason = f"OpenSearch domain {domain_name} does not have node-to-node encryption enabled"
139
+
140
+ except ClientError as e:
141
+ compliance_status = ComplianceStatus.ERROR
142
+ evaluation_reason = f"Error checking node-to-node encryption for domain {domain_name}: {str(e)}"
143
+
144
+ return ComplianceResult(
145
+ resource_id=domain_name,
146
+ resource_type="AWS::OpenSearch::Domain",
147
+ compliance_status=compliance_status,
148
+ evaluation_reason=evaluation_reason,
149
+ config_rule_name=self.rule_name,
150
+ region=region
151
+ )
152
+
153
+
154
+ class RedshiftClusterKMSEnabledAssessment(BaseConfigRuleAssessment):
155
+ """Assessment for redshift-cluster-kms-enabled AWS Config rule."""
156
+
157
+ def __init__(self):
158
+ super().__init__(
159
+ rule_name="redshift-cluster-kms-enabled",
160
+ control_id="3.11",
161
+ resource_types=["AWS::Redshift::Cluster"]
162
+ )
163
+
164
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
165
+ """Get Redshift clusters."""
166
+ if resource_type != "AWS::Redshift::Cluster":
167
+ return []
168
+
169
+ try:
170
+ redshift_client = aws_factory.get_client('redshift', region)
171
+
172
+ response = aws_factory.aws_api_call_with_retry(
173
+ lambda: redshift_client.describe_clusters()
174
+ )
175
+
176
+ clusters = []
177
+ for cluster in response.get('Clusters', []):
178
+ clusters.append({
179
+ 'ClusterIdentifier': cluster.get('ClusterIdentifier'),
180
+ 'Encrypted': cluster.get('Encrypted', False),
181
+ 'KmsKeyId': cluster.get('KmsKeyId')
182
+ })
183
+
184
+ return clusters
185
+
186
+ except ClientError as e:
187
+ logger.error(f"Error retrieving Redshift clusters in region {region}: {e}")
188
+ raise
189
+
190
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
191
+ """Evaluate if Redshift cluster uses KMS encryption."""
192
+ cluster_id = resource.get('ClusterIdentifier', 'unknown')
193
+ encrypted = resource.get('Encrypted', False)
194
+ kms_key_id = resource.get('KmsKeyId')
195
+
196
+ if encrypted and kms_key_id:
197
+ compliance_status = ComplianceStatus.COMPLIANT
198
+ evaluation_reason = f"Redshift cluster {cluster_id} is encrypted with KMS key"
199
+ elif encrypted:
200
+ compliance_status = ComplianceStatus.NON_COMPLIANT
201
+ evaluation_reason = f"Redshift cluster {cluster_id} is encrypted but not using KMS"
202
+ else:
203
+ compliance_status = ComplianceStatus.NON_COMPLIANT
204
+ evaluation_reason = f"Redshift cluster {cluster_id} is not encrypted"
205
+
206
+ return ComplianceResult(
207
+ resource_id=cluster_id,
208
+ resource_type="AWS::Redshift::Cluster",
209
+ compliance_status=compliance_status,
210
+ evaluation_reason=evaluation_reason,
211
+ config_rule_name=self.rule_name,
212
+ region=region
213
+ )
214
+
215
+
216
+ class SageMakerEndpointConfigurationKMSKeyConfiguredAssessment(BaseConfigRuleAssessment):
217
+ """Assessment for sagemaker-endpoint-configuration-kms-key-configured AWS Config rule."""
218
+
219
+ def __init__(self):
220
+ super().__init__(
221
+ rule_name="sagemaker-endpoint-configuration-kms-key-configured",
222
+ control_id="3.11",
223
+ resource_types=["AWS::SageMaker::EndpointConfig"]
224
+ )
225
+
226
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
227
+ """Get SageMaker endpoint configurations."""
228
+ if resource_type != "AWS::SageMaker::EndpointConfig":
229
+ return []
230
+
231
+ try:
232
+ sagemaker_client = aws_factory.get_client('sagemaker', region)
233
+
234
+ response = aws_factory.aws_api_call_with_retry(
235
+ lambda: sagemaker_client.list_endpoint_configs()
236
+ )
237
+
238
+ configs = []
239
+ for config in response.get('EndpointConfigs', []):
240
+ configs.append({
241
+ 'EndpointConfigName': config.get('EndpointConfigName'),
242
+ 'EndpointConfigArn': config.get('EndpointConfigArn')
243
+ })
244
+
245
+ return configs
246
+
247
+ except ClientError as e:
248
+ logger.error(f"Error retrieving SageMaker endpoint configs in region {region}: {e}")
249
+ raise
250
+
251
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
252
+ """Evaluate if SageMaker endpoint configuration uses KMS encryption."""
253
+ config_name = resource.get('EndpointConfigName', 'unknown')
254
+
255
+ try:
256
+ sagemaker_client = aws_factory.get_client('sagemaker', region)
257
+
258
+ response = aws_factory.aws_api_call_with_retry(
259
+ lambda: sagemaker_client.describe_endpoint_config(EndpointConfigName=config_name)
260
+ )
261
+
262
+ kms_key_id = response.get('KmsKeyId')
263
+
264
+ if kms_key_id:
265
+ compliance_status = ComplianceStatus.COMPLIANT
266
+ evaluation_reason = f"SageMaker endpoint config {config_name} uses KMS encryption"
267
+ else:
268
+ compliance_status = ComplianceStatus.NON_COMPLIANT
269
+ evaluation_reason = f"SageMaker endpoint config {config_name} does not use KMS encryption"
270
+
271
+ except ClientError as e:
272
+ compliance_status = ComplianceStatus.ERROR
273
+ evaluation_reason = f"Error checking KMS config for endpoint config {config_name}: {str(e)}"
274
+
275
+ return ComplianceResult(
276
+ resource_id=config_name,
277
+ resource_type="AWS::SageMaker::EndpointConfig",
278
+ compliance_status=compliance_status,
279
+ evaluation_reason=evaluation_reason,
280
+ config_rule_name=self.rule_name,
281
+ region=region
282
+ )
283
+
284
+
285
+ class SageMakerNotebookInstanceKMSKeyConfiguredAssessment(BaseConfigRuleAssessment):
286
+ """Assessment for sagemaker-notebook-instance-kms-key-configured AWS Config rule."""
287
+
288
+ def __init__(self):
289
+ super().__init__(
290
+ rule_name="sagemaker-notebook-instance-kms-key-configured",
291
+ control_id="3.11",
292
+ resource_types=["AWS::SageMaker::NotebookInstance"]
293
+ )
294
+
295
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
296
+ """Get SageMaker notebook instances."""
297
+ if resource_type != "AWS::SageMaker::NotebookInstance":
298
+ return []
299
+
300
+ try:
301
+ sagemaker_client = aws_factory.get_client('sagemaker', region)
302
+
303
+ response = aws_factory.aws_api_call_with_retry(
304
+ lambda: sagemaker_client.list_notebook_instances()
305
+ )
306
+
307
+ instances = []
308
+ for instance in response.get('NotebookInstances', []):
309
+ instances.append({
310
+ 'NotebookInstanceName': instance.get('NotebookInstanceName'),
311
+ 'NotebookInstanceArn': instance.get('NotebookInstanceArn')
312
+ })
313
+
314
+ return instances
315
+
316
+ except ClientError as e:
317
+ logger.error(f"Error retrieving SageMaker notebook instances in region {region}: {e}")
318
+ raise
319
+
320
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
321
+ """Evaluate if SageMaker notebook instance uses KMS encryption."""
322
+ instance_name = resource.get('NotebookInstanceName', 'unknown')
323
+
324
+ try:
325
+ sagemaker_client = aws_factory.get_client('sagemaker', region)
326
+
327
+ response = aws_factory.aws_api_call_with_retry(
328
+ lambda: sagemaker_client.describe_notebook_instance(NotebookInstanceName=instance_name)
329
+ )
330
+
331
+ kms_key_id = response.get('KmsKeyId')
332
+
333
+ if kms_key_id:
334
+ compliance_status = ComplianceStatus.COMPLIANT
335
+ evaluation_reason = f"SageMaker notebook instance {instance_name} uses KMS encryption"
336
+ else:
337
+ compliance_status = ComplianceStatus.NON_COMPLIANT
338
+ evaluation_reason = f"SageMaker notebook instance {instance_name} does not use KMS encryption"
339
+
340
+ except ClientError as e:
341
+ compliance_status = ComplianceStatus.ERROR
342
+ evaluation_reason = f"Error checking KMS config for notebook instance {instance_name}: {str(e)}"
343
+
344
+ return ComplianceResult(
345
+ resource_id=instance_name,
346
+ resource_type="AWS::SageMaker::NotebookInstance",
347
+ compliance_status=compliance_status,
348
+ evaluation_reason=evaluation_reason,
349
+ config_rule_name=self.rule_name,
350
+ region=region
351
+ )
352
+
353
+
354
+ class CodeBuildProjectArtifactEncryptionAssessment(BaseConfigRuleAssessment):
355
+ """Assessment for codebuild-project-artifact-encryption AWS Config rule."""
356
+
357
+ def __init__(self):
358
+ super().__init__(
359
+ rule_name="codebuild-project-artifact-encryption",
360
+ control_id="3.11",
361
+ resource_types=["AWS::CodeBuild::Project"]
362
+ )
363
+
364
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
365
+ """Get CodeBuild projects."""
366
+ if resource_type != "AWS::CodeBuild::Project":
367
+ return []
368
+
369
+ try:
370
+ codebuild_client = aws_factory.get_client('codebuild', region)
371
+
372
+ response = aws_factory.aws_api_call_with_retry(
373
+ lambda: codebuild_client.list_projects()
374
+ )
375
+
376
+ projects = []
377
+ for project_name in response.get('projects', []):
378
+ projects.append({
379
+ 'name': project_name
380
+ })
381
+
382
+ return projects
383
+
384
+ except ClientError as e:
385
+ logger.error(f"Error retrieving CodeBuild projects in region {region}: {e}")
386
+ raise
387
+
388
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
389
+ """Evaluate if CodeBuild project has artifact encryption enabled."""
390
+ project_name = resource.get('name', 'unknown')
391
+
392
+ try:
393
+ codebuild_client = aws_factory.get_client('codebuild', region)
394
+
395
+ response = aws_factory.aws_api_call_with_retry(
396
+ lambda: codebuild_client.batch_get_projects(names=[project_name])
397
+ )
398
+
399
+ projects = response.get('projects', [])
400
+ if projects:
401
+ project = projects[0]
402
+ artifacts = project.get('artifacts', {})
403
+ encryption_disabled = artifacts.get('encryptionDisabled', False)
404
+
405
+ if not encryption_disabled:
406
+ compliance_status = ComplianceStatus.COMPLIANT
407
+ evaluation_reason = f"CodeBuild project {project_name} has artifact encryption enabled"
408
+ else:
409
+ compliance_status = ComplianceStatus.NON_COMPLIANT
410
+ evaluation_reason = f"CodeBuild project {project_name} has artifact encryption disabled"
411
+ else:
412
+ compliance_status = ComplianceStatus.ERROR
413
+ evaluation_reason = f"Could not retrieve details for CodeBuild project {project_name}"
414
+
415
+ except ClientError as e:
416
+ compliance_status = ComplianceStatus.ERROR
417
+ evaluation_reason = f"Error checking artifact encryption for project {project_name}: {str(e)}"
418
+
419
+ return ComplianceResult(
420
+ resource_id=project_name,
421
+ resource_type="AWS::CodeBuild::Project",
422
+ compliance_status=compliance_status,
423
+ evaluation_reason=evaluation_reason,
424
+ config_rule_name=self.rule_name,
425
+ region=region
426
+ )