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