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,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
|
+
)
|