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,556 @@
1
+ """
2
+ CIS Control 2.8-2.10, 2.12, 4.9 - TLS/SSL Encryption Controls
3
+ Ensures data in transit is encrypted using TLS/SSL protocols.
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 ALBHTTPToHTTPSRedirectionAssessment(BaseConfigRuleAssessment):
18
+ """
19
+ CIS Control 2.8 - Encrypt Sensitive Data in Transit
20
+ AWS Config Rule: alb-http-to-https-redirection
21
+
22
+ Ensures Application Load Balancers redirect HTTP traffic to HTTPS.
23
+ """
24
+
25
+ def __init__(self):
26
+ super().__init__(
27
+ rule_name="alb-http-to-https-redirection",
28
+ control_id="2.8",
29
+ resource_types=["AWS::ElasticLoadBalancingV2::LoadBalancer"]
30
+ )
31
+
32
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
33
+ """Get all ALBs and check for HTTP to HTTPS redirection."""
34
+ if resource_type != "AWS::ElasticLoadBalancingV2::LoadBalancer":
35
+ return []
36
+
37
+ try:
38
+ elbv2_client = aws_factory.get_client('elbv2', region)
39
+
40
+ load_balancers = []
41
+ paginator = elbv2_client.get_paginator('describe_load_balancers')
42
+
43
+ for page in paginator.paginate():
44
+ for lb in page.get('LoadBalancers', []):
45
+ lb_arn = lb.get('LoadBalancerArn', '')
46
+ lb_name = lb.get('LoadBalancerName', '')
47
+ lb_type = lb.get('Type', '')
48
+
49
+ # Only check Application Load Balancers
50
+ if lb_type != 'application':
51
+ continue
52
+
53
+ # Get listeners for this ALB
54
+ listeners_response = elbv2_client.describe_listeners(LoadBalancerArn=lb_arn)
55
+ listeners = listeners_response.get('Listeners', [])
56
+
57
+ http_listeners = []
58
+ has_http_redirect = False
59
+
60
+ for listener in listeners:
61
+ protocol = listener.get('Protocol', '')
62
+ listener_arn = listener.get('ListenerArn', '')
63
+
64
+ if protocol == 'HTTP':
65
+ # Check if this HTTP listener has redirect action
66
+ default_actions = listener.get('DefaultActions', [])
67
+ for action in default_actions:
68
+ if action.get('Type') == 'redirect':
69
+ redirect_config = action.get('RedirectConfig', {})
70
+ if redirect_config.get('Protocol') == 'HTTPS':
71
+ has_http_redirect = True
72
+ break
73
+
74
+ http_listeners.append({
75
+ 'ListenerArn': listener_arn,
76
+ 'Port': listener.get('Port', 0),
77
+ 'HasRedirect': has_http_redirect
78
+ })
79
+
80
+ if http_listeners: # Only include ALBs with HTTP listeners
81
+ load_balancers.append({
82
+ 'LoadBalancerArn': lb_arn,
83
+ 'LoadBalancerName': lb_name,
84
+ 'HTTPListeners': http_listeners,
85
+ 'HasHTTPRedirect': has_http_redirect
86
+ })
87
+
88
+ logger.debug(f"Found {len(load_balancers)} ALBs with HTTP listeners in {region}")
89
+ return load_balancers
90
+
91
+ except ClientError as e:
92
+ logger.error(f"Error retrieving ALBs from {region}: {e}")
93
+ return []
94
+
95
+ def _evaluate_resource_compliance(
96
+ self,
97
+ resource: Dict[str, Any],
98
+ aws_factory: AWSClientFactory,
99
+ region: str
100
+ ) -> ComplianceResult:
101
+ """Evaluate if ALB redirects HTTP to HTTPS."""
102
+ lb_arn = resource.get('LoadBalancerArn', 'unknown')
103
+ lb_name = resource.get('LoadBalancerName', '')
104
+ has_http_redirect = resource.get('HasHTTPRedirect', False)
105
+ http_listeners = resource.get('HTTPListeners', [])
106
+
107
+ if has_http_redirect:
108
+ evaluation_reason = (
109
+ f"ALB '{lb_name}' has HTTP to HTTPS redirection configured on {len(http_listeners)} listener(s)."
110
+ )
111
+ compliance_status = ComplianceStatus.COMPLIANT
112
+ else:
113
+ evaluation_reason = (
114
+ f"ALB '{lb_name}' has {len(http_listeners)} HTTP listener(s) without HTTPS redirection. "
115
+ f"HTTP traffic should be redirected to HTTPS."
116
+ )
117
+ compliance_status = ComplianceStatus.NON_COMPLIANT
118
+
119
+ return ComplianceResult(
120
+ resource_id=lb_arn,
121
+ resource_type="AWS::ElasticLoadBalancingV2::LoadBalancer",
122
+ compliance_status=compliance_status,
123
+ evaluation_reason=evaluation_reason,
124
+ config_rule_name=self.rule_name,
125
+ region=region
126
+ )
127
+
128
+ def _get_rule_remediation_steps(self) -> List[str]:
129
+ """Get remediation steps for configuring HTTP to HTTPS redirection."""
130
+ return [
131
+ "1. Configure HTTP to HTTPS redirection via Console:",
132
+ " - Navigate to EC2 > Load Balancers",
133
+ " - Select the ALB",
134
+ " - Click 'Listeners' tab",
135
+ " - Select HTTP:80 listener",
136
+ " - Click 'Edit'",
137
+ " - Remove existing default action",
138
+ " - Add action: 'Redirect to...'",
139
+ " - Protocol: HTTPS",
140
+ " - Port: 443",
141
+ " - Status code: 301 (Permanent) or 302 (Temporary)",
142
+ " - Click 'Save'",
143
+ "",
144
+ "2. Configure via AWS CLI:",
145
+ " aws elbv2 modify-listener \\",
146
+ " --listener-arn <listener-arn> \\",
147
+ " --default-actions Type=redirect,RedirectConfig='{Protocol=HTTPS,Port=443,StatusCode=HTTP_301}' \\",
148
+ " --region <region>",
149
+ "",
150
+ "3. Create new HTTP listener with redirect:",
151
+ " aws elbv2 create-listener \\",
152
+ " --load-balancer-arn <lb-arn> \\",
153
+ " --protocol HTTP \\",
154
+ " --port 80 \\",
155
+ " --default-actions Type=redirect,RedirectConfig='{Protocol=HTTPS,Port=443,StatusCode=HTTP_301}' \\",
156
+ " --region <region>",
157
+ "",
158
+ "4. Best practices:",
159
+ " - Use 301 (permanent) redirect for production",
160
+ " - Ensure HTTPS listener (443) exists",
161
+ " - Use valid SSL/TLS certificate",
162
+ " - Enable HTTP/2 for better performance",
163
+ " - Consider HSTS headers for additional security",
164
+ "",
165
+ "Priority: HIGH - Unencrypted HTTP traffic exposes data",
166
+ "Effort: Low - Simple listener configuration change",
167
+ "",
168
+ "AWS Documentation:",
169
+ "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-update-rules.html"
170
+ ]
171
+
172
+
173
+ class ELBTLSHTTPSListenersOnlyAssessment(BaseConfigRuleAssessment):
174
+ """
175
+ CIS Control 2.9 - Encrypt Sensitive Data in Transit
176
+ AWS Config Rule: elb-tls-https-listeners-only
177
+
178
+ Ensures ELBs only use HTTPS/TLS listeners for encrypted communication.
179
+ """
180
+
181
+ def __init__(self):
182
+ super().__init__(
183
+ rule_name="elb-tls-https-listeners-only",
184
+ control_id="2.9",
185
+ resource_types=["AWS::ElasticLoadBalancingV2::LoadBalancer"]
186
+ )
187
+
188
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
189
+ """Get all ELBs and check listener protocols."""
190
+ if resource_type != "AWS::ElasticLoadBalancingV2::LoadBalancer":
191
+ return []
192
+
193
+ try:
194
+ elbv2_client = aws_factory.get_client('elbv2', region)
195
+
196
+ load_balancers = []
197
+ paginator = elbv2_client.get_paginator('describe_load_balancers')
198
+
199
+ for page in paginator.paginate():
200
+ for lb in page.get('LoadBalancers', []):
201
+ lb_arn = lb.get('LoadBalancerArn', '')
202
+ lb_name = lb.get('LoadBalancerName', '')
203
+
204
+ # Get listeners
205
+ listeners_response = elbv2_client.describe_listeners(LoadBalancerArn=lb_arn)
206
+ listeners = listeners_response.get('Listeners', [])
207
+
208
+ listener_protocols = []
209
+ has_non_tls = False
210
+
211
+ for listener in listeners:
212
+ protocol = listener.get('Protocol', '')
213
+ listener_protocols.append(protocol)
214
+
215
+ if protocol not in ['HTTPS', 'TLS']:
216
+ has_non_tls = True
217
+
218
+ load_balancers.append({
219
+ 'LoadBalancerArn': lb_arn,
220
+ 'LoadBalancerName': lb_name,
221
+ 'ListenerProtocols': listener_protocols,
222
+ 'HasNonTLS': has_non_tls,
223
+ 'OnlyTLS': not has_non_tls and len(listener_protocols) > 0
224
+ })
225
+
226
+ logger.debug(f"Found {len(load_balancers)} ELBs in {region}")
227
+ return load_balancers
228
+
229
+ except ClientError as e:
230
+ logger.error(f"Error retrieving ELBs from {region}: {e}")
231
+ return []
232
+
233
+ def _evaluate_resource_compliance(
234
+ self,
235
+ resource: Dict[str, Any],
236
+ aws_factory: AWSClientFactory,
237
+ region: str
238
+ ) -> ComplianceResult:
239
+ """Evaluate if ELB only uses TLS/HTTPS listeners."""
240
+ lb_arn = resource.get('LoadBalancerArn', 'unknown')
241
+ lb_name = resource.get('LoadBalancerName', '')
242
+ only_tls = resource.get('OnlyTLS', False)
243
+ protocols = resource.get('ListenerProtocols', [])
244
+
245
+ if only_tls:
246
+ evaluation_reason = (
247
+ f"ELB '{lb_name}' only uses secure protocols: {', '.join(protocols)}"
248
+ )
249
+ compliance_status = ComplianceStatus.COMPLIANT
250
+ else:
251
+ non_tls = [p for p in protocols if p not in ['HTTPS', 'TLS']]
252
+ evaluation_reason = (
253
+ f"ELB '{lb_name}' has non-TLS listeners: {', '.join(non_tls)}. "
254
+ f"Only HTTPS/TLS listeners should be used."
255
+ )
256
+ compliance_status = ComplianceStatus.NON_COMPLIANT
257
+
258
+ return ComplianceResult(
259
+ resource_id=lb_arn,
260
+ resource_type="AWS::ElasticLoadBalancingV2::LoadBalancer",
261
+ compliance_status=compliance_status,
262
+ evaluation_reason=evaluation_reason,
263
+ config_rule_name=self.rule_name,
264
+ region=region
265
+ )
266
+
267
+ def _get_rule_remediation_steps(self) -> List[str]:
268
+ """Get remediation steps for using only TLS/HTTPS listeners."""
269
+ return [
270
+ "1. Replace HTTP listeners with HTTPS:",
271
+ " - Delete HTTP listener or configure redirect (see alb-http-to-https-redirection)",
272
+ " - Create HTTPS listener with SSL certificate",
273
+ "",
274
+ "2. Create HTTPS listener via CLI:",
275
+ " aws elbv2 create-listener \\",
276
+ " --load-balancer-arn <lb-arn> \\",
277
+ " --protocol HTTPS \\",
278
+ " --port 443 \\",
279
+ " --certificates CertificateArn=<cert-arn> \\",
280
+ " --default-actions Type=forward,TargetGroupArn=<target-group-arn> \\",
281
+ " --region <region>",
282
+ "",
283
+ "3. Configure SSL/TLS policy:",
284
+ " aws elbv2 modify-listener \\",
285
+ " --listener-arn <listener-arn> \\",
286
+ " --ssl-policy ELBSecurityPolicy-TLS-1-2-2017-01 \\",
287
+ " --region <region>",
288
+ "",
289
+ "4. Best practices:",
290
+ " - Use TLS 1.2 or higher",
291
+ " - Use strong cipher suites",
292
+ " - Obtain certificates from AWS Certificate Manager",
293
+ " - Enable automatic certificate renewal",
294
+ "",
295
+ "Priority: HIGH - Unencrypted listeners expose data in transit",
296
+ "Effort: Medium - Requires SSL certificate and listener reconfiguration",
297
+ "",
298
+ "AWS Documentation:",
299
+ "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html"
300
+ ]
301
+
302
+
303
+
304
+ class RDSSSLConnectionRequiredAssessment(BaseConfigRuleAssessment):
305
+ """
306
+ CIS Control 2.10 - Encrypt Sensitive Data in Transit
307
+ AWS Config Rule: rds-ssl-connection-required
308
+
309
+ Ensures RDS instances require SSL/TLS connections.
310
+ """
311
+
312
+ def __init__(self):
313
+ super().__init__(
314
+ rule_name="rds-ssl-connection-required",
315
+ control_id="2.10",
316
+ resource_types=["AWS::RDS::DBInstance"]
317
+ )
318
+
319
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
320
+ """Get RDS instances and check SSL requirement."""
321
+ if resource_type != "AWS::RDS::DBInstance":
322
+ return []
323
+
324
+ try:
325
+ rds_client = aws_factory.get_client('rds', region)
326
+
327
+ instances = []
328
+ paginator = rds_client.get_paginator('describe_db_instances')
329
+
330
+ for page in paginator.paginate():
331
+ for db in page.get('DBInstances', []):
332
+ db_id = db.get('DBInstanceIdentifier', '')
333
+ engine = db.get('Engine', '')
334
+
335
+ # Check parameter group for SSL requirement
336
+ param_groups = db.get('DBParameterGroups', [])
337
+ ssl_required = False
338
+
339
+ for pg in param_groups:
340
+ pg_name = pg.get('DBParameterGroupName', '')
341
+ try:
342
+ params = rds_client.describe_db_parameters(
343
+ DBParameterGroupName=pg_name,
344
+ Source='user'
345
+ )
346
+ for param in params.get('Parameters', []):
347
+ param_name = param.get('ParameterName', '')
348
+ param_value = param.get('ParameterValue', '')
349
+
350
+ # Check engine-specific SSL parameters
351
+ if engine.startswith('postgres') and param_name == 'rds.force_ssl' and param_value == '1':
352
+ ssl_required = True
353
+ elif engine.startswith('mysql') and param_name == 'require_secure_transport' and param_value == '1':
354
+ ssl_required = True
355
+ except ClientError:
356
+ pass
357
+
358
+ instances.append({
359
+ 'DBInstanceIdentifier': db_id,
360
+ 'Engine': engine,
361
+ 'SSLRequired': ssl_required
362
+ })
363
+
364
+ return instances
365
+ except ClientError as e:
366
+ logger.error(f"Error retrieving RDS instances: {e}")
367
+ return []
368
+
369
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
370
+ """Evaluate if RDS requires SSL."""
371
+ db_id = resource.get('DBInstanceIdentifier', 'unknown')
372
+ ssl_required = resource.get('SSLRequired', False)
373
+ engine = resource.get('Engine', '')
374
+
375
+ if ssl_required:
376
+ return ComplianceResult(
377
+ resource_id=db_id,
378
+ resource_type="AWS::RDS::DBInstance",
379
+ compliance_status=ComplianceStatus.COMPLIANT,
380
+ evaluation_reason=f"RDS instance '{db_id}' ({engine}) requires SSL connections.",
381
+ config_rule_name=self.rule_name,
382
+ region=region
383
+ )
384
+ else:
385
+ return ComplianceResult(
386
+ resource_id=db_id,
387
+ resource_type="AWS::RDS::DBInstance",
388
+ compliance_status=ComplianceStatus.NON_COMPLIANT,
389
+ evaluation_reason=f"RDS instance '{db_id}' ({engine}) does not require SSL connections.",
390
+ config_rule_name=self.rule_name,
391
+ region=region
392
+ )
393
+
394
+ def _get_rule_remediation_steps(self) -> List[str]:
395
+ return [
396
+ "1. For PostgreSQL, set rds.force_ssl=1 in parameter group",
397
+ "2. For MySQL, set require_secure_transport=1",
398
+ "3. Reboot instance to apply changes",
399
+ "Priority: HIGH",
400
+ "Effort: Low",
401
+ "AWS Documentation: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html"
402
+ ]
403
+
404
+
405
+ class APIGatewaySSLEnabledAssessment(BaseConfigRuleAssessment):
406
+ """
407
+ CIS Control 2.12 - Encrypt Sensitive Data in Transit
408
+ AWS Config Rule: api-gateway-ssl-enabled
409
+
410
+ Ensures API Gateway stages use SSL/TLS certificates.
411
+ """
412
+
413
+ def __init__(self):
414
+ super().__init__(
415
+ rule_name="api-gateway-ssl-enabled",
416
+ control_id="2.12",
417
+ resource_types=["AWS::ApiGateway::Stage"]
418
+ )
419
+
420
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
421
+ """Get API Gateway stages and check SSL configuration."""
422
+ if resource_type != "AWS::ApiGateway::Stage":
423
+ return []
424
+
425
+ try:
426
+ apigw_client = aws_factory.get_client('apigateway', region)
427
+
428
+ stages = []
429
+ apis = apigw_client.get_rest_apis().get('items', [])
430
+
431
+ for api in apis:
432
+ api_id = api.get('id', '')
433
+ api_name = api.get('name', '')
434
+
435
+ api_stages = apigw_client.get_stages(restApiId=api_id).get('item', [])
436
+
437
+ for stage in api_stages:
438
+ stage_name = stage.get('stageName', '')
439
+ client_cert_id = stage.get('clientCertificateId', '')
440
+
441
+ stages.append({
442
+ 'RestApiId': api_id,
443
+ 'ApiName': api_name,
444
+ 'StageName': stage_name,
445
+ 'HasClientCertificate': bool(client_cert_id),
446
+ 'ClientCertificateId': client_cert_id
447
+ })
448
+
449
+ return stages
450
+ except ClientError as e:
451
+ logger.error(f"Error retrieving API Gateway stages: {e}")
452
+ return []
453
+
454
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
455
+ """Evaluate if API Gateway stage has SSL enabled."""
456
+ api_id = resource.get('RestApiId', 'unknown')
457
+ stage_name = resource.get('StageName', '')
458
+ has_cert = resource.get('HasClientCertificate', False)
459
+
460
+ # API Gateway always uses HTTPS, but client certificates provide additional security
461
+ return ComplianceResult(
462
+ resource_id=f"{api_id}/{stage_name}",
463
+ resource_type="AWS::ApiGateway::Stage",
464
+ compliance_status=ComplianceStatus.COMPLIANT if has_cert else ComplianceStatus.NON_COMPLIANT,
465
+ evaluation_reason=f"API Gateway stage '{stage_name}' {'has' if has_cert else 'does not have'} client certificate configured.",
466
+ config_rule_name=self.rule_name,
467
+ region=region
468
+ )
469
+
470
+ def _get_rule_remediation_steps(self) -> List[str]:
471
+ return [
472
+ "1. Generate client certificate in API Gateway",
473
+ "2. Associate certificate with stage",
474
+ "3. Configure backend to validate certificate",
475
+ "Priority: MEDIUM",
476
+ "Effort: Medium",
477
+ "AWS Documentation: https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-client-side-ssl-authentication.html"
478
+ ]
479
+
480
+
481
+ class RedshiftRequireTLSSSLAssessment(BaseConfigRuleAssessment):
482
+ """
483
+ CIS Control 4.9 - Encrypt Sensitive Data in Transit
484
+ AWS Config Rule: redshift-require-tls-ssl
485
+
486
+ Ensures Redshift clusters require SSL/TLS connections.
487
+ """
488
+
489
+ def __init__(self):
490
+ super().__init__(
491
+ rule_name="redshift-require-tls-ssl",
492
+ control_id="4.9",
493
+ resource_types=["AWS::Redshift::Cluster"]
494
+ )
495
+
496
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
497
+ """Get Redshift clusters and check SSL requirement."""
498
+ if resource_type != "AWS::Redshift::Cluster":
499
+ return []
500
+
501
+ try:
502
+ redshift_client = aws_factory.get_client('redshift', region)
503
+
504
+ clusters = []
505
+ paginator = redshift_client.get_paginator('describe_clusters')
506
+
507
+ for page in paginator.paginate():
508
+ for cluster in page.get('Clusters', []):
509
+ cluster_id = cluster.get('ClusterIdentifier', '')
510
+ param_group_name = cluster.get('ClusterParameterGroups', [{}])[0].get('ParameterGroupName', '')
511
+
512
+ ssl_required = False
513
+ if param_group_name:
514
+ try:
515
+ params = redshift_client.describe_cluster_parameters(
516
+ ParameterGroupName=param_group_name
517
+ )
518
+ for param in params.get('Parameters', []):
519
+ if param.get('ParameterName') == 'require_ssl' and param.get('ParameterValue') == 'true':
520
+ ssl_required = True
521
+ except ClientError:
522
+ pass
523
+
524
+ clusters.append({
525
+ 'ClusterIdentifier': cluster_id,
526
+ 'ParameterGroupName': param_group_name,
527
+ 'SSLRequired': ssl_required
528
+ })
529
+
530
+ return clusters
531
+ except ClientError as e:
532
+ logger.error(f"Error retrieving Redshift clusters: {e}")
533
+ return []
534
+
535
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
536
+ """Evaluate if Redshift requires SSL."""
537
+ cluster_id = resource.get('ClusterIdentifier', 'unknown')
538
+ ssl_required = resource.get('SSLRequired', False)
539
+
540
+ return ComplianceResult(
541
+ resource_id=cluster_id,
542
+ resource_type="AWS::Redshift::Cluster",
543
+ compliance_status=ComplianceStatus.COMPLIANT if ssl_required else ComplianceStatus.NON_COMPLIANT,
544
+ evaluation_reason=f"Redshift cluster '{cluster_id}' {'requires' if ssl_required else 'does not require'} SSL connections.",
545
+ config_rule_name=self.rule_name,
546
+ region=region
547
+ )
548
+
549
+ def _get_rule_remediation_steps(self) -> List[str]:
550
+ return [
551
+ "1. Modify cluster parameter group to set require_ssl=true",
552
+ "2. Reboot cluster to apply changes",
553
+ "Priority: HIGH",
554
+ "Effort: Low",
555
+ "AWS Documentation: https://docs.aws.amazon.com/redshift/latest/mgmt/connecting-ssl-support.html"
556
+ ]