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,382 @@
1
+ """Control 3.10: Encrypt Sensitive Data in Transit 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 ELBACMCertificateRequiredAssessment(BaseConfigRuleAssessment):
15
+ """Assessment for elb-acm-certificate-required Config rule."""
16
+
17
+ def __init__(self):
18
+ """Initialize ELB ACM certificate assessment."""
19
+ super().__init__(
20
+ rule_name="elb-acm-certificate-required",
21
+ control_id="3.10",
22
+ resource_types=["AWS::ElasticLoadBalancing::LoadBalancer"]
23
+ )
24
+
25
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
26
+ """Get all Classic Load Balancers in the region."""
27
+ if resource_type != "AWS::ElasticLoadBalancing::LoadBalancer":
28
+ return []
29
+
30
+ try:
31
+ elb_client = aws_factory.get_client('elb', region)
32
+
33
+ response = aws_factory.aws_api_call_with_retry(
34
+ lambda: elb_client.describe_load_balancers()
35
+ )
36
+
37
+ load_balancers = []
38
+ for lb in response.get('LoadBalancerDescriptions', []):
39
+ load_balancers.append({
40
+ 'LoadBalancerName': lb.get('LoadBalancerName'),
41
+ 'DNSName': lb.get('DNSName'),
42
+ 'Scheme': lb.get('Scheme'),
43
+ 'ListenerDescriptions': lb.get('ListenerDescriptions', []),
44
+ 'VPCId': lb.get('VPCId')
45
+ })
46
+
47
+ logger.debug(f"Found {len(load_balancers)} Classic Load Balancers in region {region}")
48
+ return load_balancers
49
+
50
+ except ClientError as e:
51
+ logger.error(f"Error retrieving Classic Load Balancers in region {region}: {e}")
52
+ raise
53
+ except Exception as e:
54
+ logger.error(f"Unexpected error retrieving Classic Load Balancers in region {region}: {e}")
55
+ raise
56
+
57
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
58
+ """Evaluate if Classic Load Balancer uses ACM certificates for HTTPS/SSL listeners."""
59
+ lb_name = resource.get('LoadBalancerName', 'unknown')
60
+ listener_descriptions = resource.get('ListenerDescriptions', [])
61
+
62
+ https_listeners = []
63
+ non_acm_listeners = []
64
+
65
+ for listener_desc in listener_descriptions:
66
+ listener = listener_desc.get('Listener', {})
67
+ protocol = listener.get('Protocol', '')
68
+ ssl_certificate_id = listener.get('SSLCertificateId', '')
69
+
70
+ if protocol in ['HTTPS', 'SSL']:
71
+ https_listeners.append({
72
+ 'Protocol': protocol,
73
+ 'LoadBalancerPort': listener.get('LoadBalancerPort'),
74
+ 'SSLCertificateId': ssl_certificate_id
75
+ })
76
+
77
+ # Check if certificate is from ACM (ACM ARNs contain 'acm')
78
+ if ssl_certificate_id and 'acm' not in ssl_certificate_id.lower():
79
+ non_acm_listeners.append({
80
+ 'Protocol': protocol,
81
+ 'LoadBalancerPort': listener.get('LoadBalancerPort'),
82
+ 'SSLCertificateId': ssl_certificate_id
83
+ })
84
+
85
+ if not https_listeners:
86
+ compliance_status = ComplianceStatus.NOT_APPLICABLE
87
+ evaluation_reason = f"Load Balancer {lb_name} has no HTTPS/SSL listeners"
88
+ elif non_acm_listeners:
89
+ compliance_status = ComplianceStatus.NON_COMPLIANT
90
+ listener_details = [f"{l['Protocol']}:{l['LoadBalancerPort']}" for l in non_acm_listeners]
91
+ evaluation_reason = f"Load Balancer {lb_name} has {len(non_acm_listeners)} HTTPS/SSL listener(s) not using ACM certificates: {', '.join(listener_details)}"
92
+ else:
93
+ compliance_status = ComplianceStatus.COMPLIANT
94
+ evaluation_reason = f"Load Balancer {lb_name} has {len(https_listeners)} HTTPS/SSL listener(s) using ACM certificates"
95
+
96
+ return ComplianceResult(
97
+ resource_id=lb_name,
98
+ resource_type="AWS::ElasticLoadBalancing::LoadBalancer",
99
+ compliance_status=compliance_status,
100
+ evaluation_reason=evaluation_reason,
101
+ config_rule_name=self.rule_name,
102
+ region=region
103
+ )
104
+
105
+ def _get_rule_remediation_steps(self) -> List[str]:
106
+ """Get specific remediation steps for ELB ACM certificates."""
107
+ return [
108
+ "Identify Classic Load Balancers with HTTPS/SSL listeners not using ACM certificates",
109
+ "For each non-compliant load balancer:",
110
+ " 1. Request or import a certificate in AWS Certificate Manager",
111
+ " 2. Update the load balancer listener to use the ACM certificate",
112
+ " 3. Test the SSL/TLS configuration",
113
+ " 4. Remove the old certificate if it's no longer needed",
114
+ "Use AWS CLI: aws acm request-certificate --domain-name <domain>",
115
+ "Use AWS CLI: aws elb set-load-balancer-listener-ssl-certificate --load-balancer-name <name> --load-balancer-port <port> --ssl-certificate-id <acm-arn>",
116
+ "Enable automatic certificate renewal for ACM certificates",
117
+ "Monitor certificate expiration dates and renewal status"
118
+ ]
119
+
120
+
121
+ class ELBv2ACMCertificateRequiredAssessment(BaseConfigRuleAssessment):
122
+ """Assessment for elbv2-acm-certificate-required Config rule."""
123
+
124
+ def __init__(self):
125
+ """Initialize ELBv2 ACM certificate assessment."""
126
+ super().__init__(
127
+ rule_name="elbv2-acm-certificate-required",
128
+ control_id="3.10",
129
+ resource_types=["AWS::ElasticLoadBalancingV2::LoadBalancer"]
130
+ )
131
+
132
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
133
+ """Get all Application/Network Load Balancers in the region."""
134
+ if resource_type != "AWS::ElasticLoadBalancingV2::LoadBalancer":
135
+ return []
136
+
137
+ try:
138
+ elbv2_client = aws_factory.get_client('elbv2', region)
139
+
140
+ response = aws_factory.aws_api_call_with_retry(
141
+ lambda: elbv2_client.describe_load_balancers()
142
+ )
143
+
144
+ load_balancers = []
145
+ for lb in response.get('LoadBalancers', []):
146
+ load_balancers.append({
147
+ 'LoadBalancerArn': lb.get('LoadBalancerArn'),
148
+ 'LoadBalancerName': lb.get('LoadBalancerName'),
149
+ 'DNSName': lb.get('DNSName'),
150
+ 'Scheme': lb.get('Scheme'),
151
+ 'Type': lb.get('Type'),
152
+ 'VpcId': lb.get('VpcId')
153
+ })
154
+
155
+ logger.debug(f"Found {len(load_balancers)} Application/Network Load Balancers in region {region}")
156
+ return load_balancers
157
+
158
+ except ClientError as e:
159
+ logger.error(f"Error retrieving Application/Network Load Balancers in region {region}: {e}")
160
+ raise
161
+ except Exception as e:
162
+ logger.error(f"Unexpected error retrieving Application/Network Load Balancers in region {region}: {e}")
163
+ raise
164
+
165
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
166
+ """Evaluate if ALB/NLB uses ACM certificates for HTTPS/TLS listeners."""
167
+ lb_arn = resource.get('LoadBalancerArn', 'unknown')
168
+ lb_name = resource.get('LoadBalancerName', 'unknown')
169
+ lb_type = resource.get('Type', 'unknown')
170
+
171
+ try:
172
+ elbv2_client = aws_factory.get_client('elbv2', region)
173
+
174
+ # Get listeners for the load balancer
175
+ listeners_response = aws_factory.aws_api_call_with_retry(
176
+ lambda: elbv2_client.describe_listeners(LoadBalancerArn=lb_arn)
177
+ )
178
+
179
+ https_listeners = []
180
+ non_acm_listeners = []
181
+
182
+ for listener in listeners_response.get('Listeners', []):
183
+ protocol = listener.get('Protocol', '')
184
+ port = listener.get('Port')
185
+ certificates = listener.get('Certificates', [])
186
+
187
+ if protocol in ['HTTPS', 'TLS']:
188
+ https_listeners.append({
189
+ 'Protocol': protocol,
190
+ 'Port': port,
191
+ 'Certificates': certificates
192
+ })
193
+
194
+ # Check if any certificate is not from ACM
195
+ for cert in certificates:
196
+ cert_arn = cert.get('CertificateArn', '')
197
+ if cert_arn and 'acm' not in cert_arn.lower():
198
+ non_acm_listeners.append({
199
+ 'Protocol': protocol,
200
+ 'Port': port,
201
+ 'CertificateArn': cert_arn
202
+ })
203
+
204
+ if not https_listeners:
205
+ compliance_status = ComplianceStatus.NOT_APPLICABLE
206
+ evaluation_reason = f"Load Balancer {lb_name} has no HTTPS/TLS listeners"
207
+ elif non_acm_listeners:
208
+ compliance_status = ComplianceStatus.NON_COMPLIANT
209
+ listener_details = [f"{l['Protocol']}:{l['Port']}" for l in non_acm_listeners]
210
+ evaluation_reason = f"Load Balancer {lb_name} has {len(non_acm_listeners)} HTTPS/TLS listener(s) not using ACM certificates: {', '.join(listener_details)}"
211
+ else:
212
+ compliance_status = ComplianceStatus.COMPLIANT
213
+ evaluation_reason = f"Load Balancer {lb_name} has {len(https_listeners)} HTTPS/TLS listener(s) using ACM certificates"
214
+
215
+ except ClientError as e:
216
+ error_code = e.response.get('Error', {}).get('Code', '')
217
+ if error_code in ['AccessDenied', 'LoadBalancerNotFound']:
218
+ compliance_status = ComplianceStatus.ERROR
219
+ evaluation_reason = f"Cannot access load balancer {lb_name}: {error_code}"
220
+ else:
221
+ compliance_status = ComplianceStatus.ERROR
222
+ evaluation_reason = f"Error checking load balancer {lb_name}: {str(e)}"
223
+ except Exception as e:
224
+ compliance_status = ComplianceStatus.ERROR
225
+ evaluation_reason = f"Unexpected error checking load balancer {lb_name}: {str(e)}"
226
+
227
+ return ComplianceResult(
228
+ resource_id=lb_arn,
229
+ resource_type="AWS::ElasticLoadBalancingV2::LoadBalancer",
230
+ compliance_status=compliance_status,
231
+ evaluation_reason=evaluation_reason,
232
+ config_rule_name=self.rule_name,
233
+ region=region
234
+ )
235
+
236
+ def _get_rule_remediation_steps(self) -> List[str]:
237
+ """Get specific remediation steps for ELBv2 ACM certificates."""
238
+ return [
239
+ "Identify Application/Network Load Balancers with HTTPS/TLS listeners not using ACM certificates",
240
+ "For each non-compliant load balancer:",
241
+ " 1. Request or import a certificate in AWS Certificate Manager",
242
+ " 2. Update the listener to use the ACM certificate",
243
+ " 3. Test the SSL/TLS configuration",
244
+ " 4. Remove the old certificate if it's no longer needed",
245
+ "Use AWS CLI: aws acm request-certificate --domain-name <domain>",
246
+ "Use AWS CLI: aws elbv2 modify-listener --listener-arn <arn> --certificates CertificateArn=<acm-arn>",
247
+ "Enable automatic certificate renewal for ACM certificates",
248
+ "Monitor certificate expiration dates and renewal status"
249
+ ]
250
+
251
+
252
+ class OpenSearchHTTPSRequiredAssessment(BaseConfigRuleAssessment):
253
+ """Assessment for opensearch-https-required Config rule."""
254
+
255
+ def __init__(self):
256
+ """Initialize OpenSearch HTTPS assessment."""
257
+ super().__init__(
258
+ rule_name="opensearch-https-required",
259
+ control_id="3.10",
260
+ resource_types=["AWS::OpenSearch::Domain"]
261
+ )
262
+
263
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
264
+ """Get all OpenSearch domains in the region."""
265
+ if resource_type != "AWS::OpenSearch::Domain":
266
+ return []
267
+
268
+ try:
269
+ opensearch_client = aws_factory.get_client('opensearch', region)
270
+
271
+ response = aws_factory.aws_api_call_with_retry(
272
+ lambda: opensearch_client.list_domain_names()
273
+ )
274
+
275
+ domains = []
276
+ for domain in response.get('DomainNames', []):
277
+ domain_name = domain.get('DomainName')
278
+ if domain_name:
279
+ domains.append({
280
+ 'DomainName': domain_name,
281
+ 'EngineType': domain.get('EngineType', 'OpenSearch')
282
+ })
283
+
284
+ logger.debug(f"Found {len(domains)} OpenSearch domains in region {region}")
285
+ return domains
286
+
287
+ except ClientError as e:
288
+ # Try Elasticsearch service if OpenSearch is not available
289
+ if e.response.get('Error', {}).get('Code') in ['UnknownOperationException', 'InvalidAction']:
290
+ try:
291
+ es_client = aws_factory.get_client('es', region)
292
+ response = aws_factory.aws_api_call_with_retry(
293
+ lambda: es_client.list_domain_names()
294
+ )
295
+
296
+ domains = []
297
+ for domain in response.get('DomainNames', []):
298
+ domain_name = domain.get('DomainName')
299
+ if domain_name:
300
+ domains.append({
301
+ 'DomainName': domain_name,
302
+ 'EngineType': 'Elasticsearch'
303
+ })
304
+
305
+ logger.debug(f"Found {len(domains)} Elasticsearch domains in region {region}")
306
+ return domains
307
+ except ClientError as es_error:
308
+ logger.error(f"Error retrieving Elasticsearch domains in region {region}: {es_error}")
309
+ raise es_error
310
+ else:
311
+ logger.error(f"Error retrieving OpenSearch domains in region {region}: {e}")
312
+ raise
313
+ except Exception as e:
314
+ logger.error(f"Unexpected error retrieving OpenSearch domains in region {region}: {e}")
315
+ raise
316
+
317
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
318
+ """Evaluate if OpenSearch domain requires HTTPS."""
319
+ domain_name = resource.get('DomainName', 'unknown')
320
+ engine_type = resource.get('EngineType', 'OpenSearch')
321
+
322
+ try:
323
+ if engine_type == 'Elasticsearch':
324
+ client = aws_factory.get_client('es', region)
325
+ response = aws_factory.aws_api_call_with_retry(
326
+ lambda: client.describe_elasticsearch_domain(DomainName=domain_name)
327
+ )
328
+ domain_config = response.get('DomainStatus', {})
329
+ else:
330
+ client = aws_factory.get_client('opensearch', region)
331
+ response = aws_factory.aws_api_call_with_retry(
332
+ lambda: client.describe_domain(DomainName=domain_name)
333
+ )
334
+ domain_config = response.get('DomainStatus', {})
335
+
336
+ # Check domain endpoint options
337
+ domain_endpoint_options = domain_config.get('DomainEndpointOptions', {})
338
+ enforce_https = domain_endpoint_options.get('EnforceHTTPS', False)
339
+
340
+ if enforce_https:
341
+ compliance_status = ComplianceStatus.COMPLIANT
342
+ evaluation_reason = f"{engine_type} domain {domain_name} enforces HTTPS"
343
+ else:
344
+ compliance_status = ComplianceStatus.NON_COMPLIANT
345
+ evaluation_reason = f"{engine_type} domain {domain_name} does not enforce HTTPS"
346
+
347
+ except ClientError as e:
348
+ error_code = e.response.get('Error', {}).get('Code', '')
349
+ if error_code in ['AccessDenied', 'ResourceNotFoundException']:
350
+ compliance_status = ComplianceStatus.ERROR
351
+ evaluation_reason = f"Cannot access {engine_type} domain {domain_name}: {error_code}"
352
+ else:
353
+ compliance_status = ComplianceStatus.ERROR
354
+ evaluation_reason = f"Error checking {engine_type} domain {domain_name}: {str(e)}"
355
+ except Exception as e:
356
+ compliance_status = ComplianceStatus.ERROR
357
+ evaluation_reason = f"Unexpected error checking {engine_type} domain {domain_name}: {str(e)}"
358
+
359
+ return ComplianceResult(
360
+ resource_id=domain_name,
361
+ resource_type="AWS::OpenSearch::Domain",
362
+ compliance_status=compliance_status,
363
+ evaluation_reason=evaluation_reason,
364
+ config_rule_name=self.rule_name,
365
+ region=region
366
+ )
367
+
368
+ def _get_rule_remediation_steps(self) -> List[str]:
369
+ """Get specific remediation steps for OpenSearch HTTPS enforcement."""
370
+ return [
371
+ "Identify OpenSearch/Elasticsearch domains that do not enforce HTTPS",
372
+ "For each non-compliant domain:",
373
+ " 1. Plan for a maintenance window as this requires domain update",
374
+ " 2. Update domain configuration to enforce HTTPS",
375
+ " 3. Update applications to use HTTPS endpoints only",
376
+ " 4. Test connectivity and functionality",
377
+ " 5. Monitor domain health after the change",
378
+ "Use AWS CLI: aws opensearch update-domain-config --domain-name <name> --domain-endpoint-options EnforceHTTPS=true",
379
+ "Use AWS CLI: aws es update-elasticsearch-domain-config --domain-name <name> --domain-endpoint-options EnforceHTTPS=true",
380
+ "Consider enabling node-to-node encryption for additional security",
381
+ "Update security groups and NACLs to allow only HTTPS traffic"
382
+ ]