aws-cis-controls-assessment 1.1.4__py3-none-any.whl → 1.2.2__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 +4 -4
- aws_cis_assessment/config/rules/cis_controls_ig1.yaml +365 -2
- aws_cis_assessment/controls/base_control.py +106 -24
- aws_cis_assessment/controls/ig1/__init__.py +144 -15
- aws_cis_assessment/controls/ig1/control_4_1.py +4 -4
- aws_cis_assessment/controls/ig1/control_access_analyzer.py +198 -0
- aws_cis_assessment/controls/ig1/control_access_asset_mgmt.py +360 -0
- aws_cis_assessment/controls/ig1/control_access_control.py +323 -0
- aws_cis_assessment/controls/ig1/control_backup_security.py +579 -0
- aws_cis_assessment/controls/ig1/control_cloudfront_logging.py +215 -0
- aws_cis_assessment/controls/ig1/control_configuration_mgmt.py +407 -0
- aws_cis_assessment/controls/ig1/control_data_classification.py +255 -0
- aws_cis_assessment/controls/ig1/control_dynamodb_encryption.py +279 -0
- aws_cis_assessment/controls/ig1/control_ebs_encryption.py +177 -0
- aws_cis_assessment/controls/ig1/control_efs_encryption.py +243 -0
- aws_cis_assessment/controls/ig1/control_elb_logging.py +195 -0
- aws_cis_assessment/controls/ig1/control_guardduty.py +156 -0
- aws_cis_assessment/controls/ig1/control_inspector.py +184 -0
- aws_cis_assessment/controls/ig1/control_inventory.py +511 -0
- aws_cis_assessment/controls/ig1/control_macie.py +165 -0
- aws_cis_assessment/controls/ig1/control_messaging_encryption.py +419 -0
- aws_cis_assessment/controls/ig1/control_mfa.py +485 -0
- aws_cis_assessment/controls/ig1/control_network_security.py +194 -619
- aws_cis_assessment/controls/ig1/control_patch_management.py +626 -0
- aws_cis_assessment/controls/ig1/control_rds_encryption.py +228 -0
- aws_cis_assessment/controls/ig1/control_s3_encryption.py +383 -0
- aws_cis_assessment/controls/ig1/control_tls_ssl.py +556 -0
- aws_cis_assessment/controls/ig1/control_version_mgmt.py +337 -0
- aws_cis_assessment/controls/ig1/control_vpc_flow_logs.py +205 -0
- aws_cis_assessment/controls/ig1/control_waf_logging.py +226 -0
- aws_cis_assessment/core/assessment_engine.py +160 -11
- aws_cis_assessment/core/aws_client_factory.py +17 -5
- aws_cis_assessment/core/models.py +20 -1
- aws_cis_assessment/core/scoring_engine.py +102 -1
- aws_cis_assessment/reporters/base_reporter.py +58 -13
- aws_cis_assessment/reporters/html_reporter.py +186 -9
- aws_cis_controls_assessment-1.2.2.dist-info/METADATA +320 -0
- {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.2.dist-info}/RECORD +44 -20
- docs/developer-guide.md +204 -5
- docs/user-guide.md +137 -4
- aws_cis_controls_assessment-1.1.4.dist-info/METADATA +0 -404
- {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.2.dist-info}/WHEEL +0 -0
- {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.2.dist-info}/entry_points.txt +0 -0
- {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.2.dist-info}/licenses/LICENSE +0 -0
- {aws_cis_controls_assessment-1.1.4.dist-info → aws_cis_controls_assessment-1.2.2.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 3.10 - Encrypt Sensitive Data in Transit
|
|
20
|
+
AWS Config Rule: alb-http-to-https-redirection-check
|
|
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-check",
|
|
28
|
+
control_id="3.10",
|
|
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
|
+
]
|