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,203 @@
|
|
|
1
|
+
"""Control 3.10: Encrypt Sensitive Data in Transit - Network enhancements."""
|
|
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 ElasticsearchNodeToNodeEncryptionCheckAssessment(BaseConfigRuleAssessment):
|
|
15
|
+
"""Assessment for elasticsearch-node-to-node-encryption-check AWS Config rule."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__(
|
|
19
|
+
rule_name="elasticsearch-node-to-node-encryption-check",
|
|
20
|
+
control_id="3.10",
|
|
21
|
+
resource_types=["AWS::Elasticsearch::Domain"]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
25
|
+
"""Get Elasticsearch domains."""
|
|
26
|
+
if resource_type != "AWS::Elasticsearch::Domain":
|
|
27
|
+
return []
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
es_client = aws_factory.get_client('es', region)
|
|
31
|
+
|
|
32
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
33
|
+
lambda: es_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 Elasticsearch 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 Elasticsearch domain has node-to-node encryption enabled."""
|
|
50
|
+
domain_name = resource.get('DomainName', 'unknown')
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
es_client = aws_factory.get_client('es', region)
|
|
54
|
+
|
|
55
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
56
|
+
lambda: es_client.describe_elasticsearch_domain(DomainName=domain_name)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
domain_status = response.get('DomainStatus', {})
|
|
60
|
+
node_to_node_encryption = domain_status.get('NodeToNodeEncryptionOptions', {})
|
|
61
|
+
encryption_enabled = node_to_node_encryption.get('Enabled', False)
|
|
62
|
+
|
|
63
|
+
if encryption_enabled:
|
|
64
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
65
|
+
evaluation_reason = f"Elasticsearch domain {domain_name} has node-to-node encryption enabled"
|
|
66
|
+
else:
|
|
67
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
68
|
+
evaluation_reason = f"Elasticsearch domain {domain_name} does not have node-to-node encryption enabled"
|
|
69
|
+
|
|
70
|
+
except ClientError as e:
|
|
71
|
+
compliance_status = ComplianceStatus.ERROR
|
|
72
|
+
evaluation_reason = f"Error checking node-to-node encryption for domain {domain_name}: {str(e)}"
|
|
73
|
+
|
|
74
|
+
return ComplianceResult(
|
|
75
|
+
resource_id=domain_name,
|
|
76
|
+
resource_type="AWS::Elasticsearch::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 AutoScalingLaunchConfigPublicIPDisabledAssessment(BaseConfigRuleAssessment):
|
|
85
|
+
"""Assessment for autoscaling-launch-config-public-ip-disabled AWS Config rule."""
|
|
86
|
+
|
|
87
|
+
def __init__(self):
|
|
88
|
+
super().__init__(
|
|
89
|
+
rule_name="autoscaling-launch-config-public-ip-disabled",
|
|
90
|
+
control_id="3.3",
|
|
91
|
+
resource_types=["AWS::AutoScaling::LaunchConfiguration"]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
95
|
+
"""Get Auto Scaling launch configurations."""
|
|
96
|
+
if resource_type != "AWS::AutoScaling::LaunchConfiguration":
|
|
97
|
+
return []
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
autoscaling_client = aws_factory.get_client('autoscaling', region)
|
|
101
|
+
|
|
102
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
103
|
+
lambda: autoscaling_client.describe_launch_configurations()
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
launch_configs = []
|
|
107
|
+
for config in response.get('LaunchConfigurations', []):
|
|
108
|
+
launch_configs.append({
|
|
109
|
+
'LaunchConfigurationName': config.get('LaunchConfigurationName'),
|
|
110
|
+
'AssociatePublicIpAddress': config.get('AssociatePublicIpAddress')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return launch_configs
|
|
114
|
+
|
|
115
|
+
except ClientError as e:
|
|
116
|
+
logger.error(f"Error retrieving launch configurations in region {region}: {e}")
|
|
117
|
+
raise
|
|
118
|
+
|
|
119
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
120
|
+
"""Evaluate if launch configuration has public IP assignment disabled."""
|
|
121
|
+
config_name = resource.get('LaunchConfigurationName', 'unknown')
|
|
122
|
+
associate_public_ip = resource.get('AssociatePublicIpAddress')
|
|
123
|
+
|
|
124
|
+
if associate_public_ip is False:
|
|
125
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
126
|
+
evaluation_reason = f"Launch configuration {config_name} has public IP assignment disabled"
|
|
127
|
+
elif associate_public_ip is True:
|
|
128
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
129
|
+
evaluation_reason = f"Launch configuration {config_name} has public IP assignment enabled"
|
|
130
|
+
else:
|
|
131
|
+
# None means it uses subnet default
|
|
132
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
133
|
+
evaluation_reason = f"Launch configuration {config_name} uses subnet default for public IP assignment"
|
|
134
|
+
|
|
135
|
+
return ComplianceResult(
|
|
136
|
+
resource_id=config_name,
|
|
137
|
+
resource_type="AWS::AutoScaling::LaunchConfiguration",
|
|
138
|
+
compliance_status=compliance_status,
|
|
139
|
+
evaluation_reason=evaluation_reason,
|
|
140
|
+
config_rule_name=self.rule_name,
|
|
141
|
+
region=region
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class EFSAccessPointEnforceRootDirectoryAssessment(BaseConfigRuleAssessment):
|
|
146
|
+
"""Assessment for efs-access-point-enforce-root-directory AWS Config rule."""
|
|
147
|
+
|
|
148
|
+
def __init__(self):
|
|
149
|
+
super().__init__(
|
|
150
|
+
rule_name="efs-access-point-enforce-root-directory",
|
|
151
|
+
control_id="3.3",
|
|
152
|
+
resource_types=["AWS::EFS::AccessPoint"]
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
156
|
+
"""Get EFS access points."""
|
|
157
|
+
if resource_type != "AWS::EFS::AccessPoint":
|
|
158
|
+
return []
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
efs_client = aws_factory.get_client('efs', region)
|
|
162
|
+
|
|
163
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
164
|
+
lambda: efs_client.describe_access_points()
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
access_points = []
|
|
168
|
+
for ap in response.get('AccessPoints', []):
|
|
169
|
+
access_points.append({
|
|
170
|
+
'AccessPointId': ap.get('AccessPointId'),
|
|
171
|
+
'FileSystemId': ap.get('FileSystemId'),
|
|
172
|
+
'RootDirectory': ap.get('RootDirectory', {})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
return access_points
|
|
176
|
+
|
|
177
|
+
except ClientError as e:
|
|
178
|
+
logger.error(f"Error retrieving EFS access points in region {region}: {e}")
|
|
179
|
+
raise
|
|
180
|
+
|
|
181
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
182
|
+
"""Evaluate if EFS access point enforces a root directory."""
|
|
183
|
+
access_point_id = resource.get('AccessPointId', 'unknown')
|
|
184
|
+
root_directory = resource.get('RootDirectory', {})
|
|
185
|
+
|
|
186
|
+
# Check if root directory is configured (not just "/")
|
|
187
|
+
root_path = root_directory.get('Path', '/')
|
|
188
|
+
|
|
189
|
+
if root_path != '/':
|
|
190
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
191
|
+
evaluation_reason = f"EFS access point {access_point_id} enforces root directory: {root_path}"
|
|
192
|
+
else:
|
|
193
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
194
|
+
evaluation_reason = f"EFS access point {access_point_id} does not enforce a specific root directory"
|
|
195
|
+
|
|
196
|
+
return ComplianceResult(
|
|
197
|
+
resource_id=access_point_id,
|
|
198
|
+
resource_type="AWS::EFS::AccessPoint",
|
|
199
|
+
compliance_status=compliance_status,
|
|
200
|
+
evaluation_reason=evaluation_reason,
|
|
201
|
+
config_rule_name=self.rule_name,
|
|
202
|
+
region=region
|
|
203
|
+
)
|