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
docs/developer-guide.md
ADDED
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
# Developer Guide
|
|
2
|
+
|
|
3
|
+
This guide covers extending and customizing the AWS CIS Controls Compliance Assessment Framework - a production-ready, enterprise-grade solution with 136 implemented rules (131 CIS Controls + 5 bonus security enhancements).
|
|
4
|
+
|
|
5
|
+
## Production Framework Status
|
|
6
|
+
|
|
7
|
+
**✅ Complete Implementation**
|
|
8
|
+
- 100% CIS Controls coverage across all Implementation Groups
|
|
9
|
+
- 136 total rules implemented (131 CIS + 5 bonus)
|
|
10
|
+
- Production-tested architecture with comprehensive error handling
|
|
11
|
+
- Enterprise-grade performance and scalability
|
|
12
|
+
- Ready for immediate deployment and customization
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
1. [Architecture Overview](#architecture-overview)
|
|
17
|
+
2. [Development Setup](#development-setup)
|
|
18
|
+
3. [Adding New Controls](#adding-new-controls)
|
|
19
|
+
4. [Creating Custom Assessments](#creating-custom-assessments)
|
|
20
|
+
5. [Extending Reporters](#extending-reporters)
|
|
21
|
+
6. [Testing Framework](#testing-framework)
|
|
22
|
+
7. [Contributing Guidelines](#contributing-guidelines)
|
|
23
|
+
8. [API Reference](#api-reference)
|
|
24
|
+
|
|
25
|
+
## Architecture Overview
|
|
26
|
+
|
|
27
|
+
### Core Components
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
aws_cis_assessment/
|
|
31
|
+
├── core/ # Core assessment engine
|
|
32
|
+
│ ├── assessment_engine.py # Main orchestration
|
|
33
|
+
│ ├── aws_client_factory.py # AWS service clients
|
|
34
|
+
│ ├── scoring_engine.py # Compliance scoring
|
|
35
|
+
│ └── models.py # Data models
|
|
36
|
+
├── controls/ # Control implementations
|
|
37
|
+
│ ├── ig1/ # IG1 control assessments
|
|
38
|
+
│ ├── ig2/ # IG2 control assessments
|
|
39
|
+
│ └── ig3/ # IG3 control assessments
|
|
40
|
+
├── config/ # Configuration management
|
|
41
|
+
│ ├── config_loader.py # YAML config loader
|
|
42
|
+
│ └── rules/ # CIS control definitions
|
|
43
|
+
├── reporters/ # Report generators
|
|
44
|
+
│ ├── json_reporter.py # JSON output
|
|
45
|
+
│ ├── html_reporter.py # HTML reports
|
|
46
|
+
│ └── csv_reporter.py # CSV export
|
|
47
|
+
└── cli/ # Command-line interface
|
|
48
|
+
├── main.py # CLI entry point
|
|
49
|
+
└── utils.py # CLI utilities
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Key Design Patterns
|
|
53
|
+
|
|
54
|
+
1. **Strategy Pattern**: Different assessment implementations for each control
|
|
55
|
+
2. **Factory Pattern**: AWS client creation and management
|
|
56
|
+
3. **Template Method**: Base assessment framework with customizable steps
|
|
57
|
+
4. **Observer Pattern**: Progress reporting and callbacks
|
|
58
|
+
5. **Builder Pattern**: Report generation with multiple formats
|
|
59
|
+
|
|
60
|
+
## Development Setup
|
|
61
|
+
|
|
62
|
+
### Prerequisites
|
|
63
|
+
|
|
64
|
+
- Python 3.8+
|
|
65
|
+
- Git
|
|
66
|
+
- AWS CLI (for testing)
|
|
67
|
+
- Virtual environment tool
|
|
68
|
+
|
|
69
|
+
### Setup Development Environment
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Clone the repository
|
|
73
|
+
git clone https://github.com/your-org/aws-cis-assessment.git
|
|
74
|
+
cd aws-cis-assessment
|
|
75
|
+
|
|
76
|
+
# Create virtual environment
|
|
77
|
+
python -m venv venv
|
|
78
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
79
|
+
|
|
80
|
+
# Install development dependencies
|
|
81
|
+
pip install -r requirements-dev.txt
|
|
82
|
+
|
|
83
|
+
# Install in development mode
|
|
84
|
+
pip install -e .
|
|
85
|
+
|
|
86
|
+
# Install pre-commit hooks
|
|
87
|
+
pre-commit install
|
|
88
|
+
|
|
89
|
+
# Run tests to verify setup
|
|
90
|
+
pytest
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Development Dependencies
|
|
94
|
+
|
|
95
|
+
```txt
|
|
96
|
+
# requirements-dev.txt
|
|
97
|
+
pytest>=7.0.0
|
|
98
|
+
pytest-cov>=4.0.0
|
|
99
|
+
pytest-mock>=3.10.0
|
|
100
|
+
black>=22.0.0
|
|
101
|
+
flake8>=5.0.0
|
|
102
|
+
mypy>=1.0.0
|
|
103
|
+
pre-commit>=2.20.0
|
|
104
|
+
hypothesis>=6.70.0
|
|
105
|
+
boto3-stubs[essential]>=1.26.0
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Code Style and Quality
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Format code
|
|
112
|
+
black aws_cis_assessment/
|
|
113
|
+
|
|
114
|
+
# Lint code
|
|
115
|
+
flake8 aws_cis_assessment/
|
|
116
|
+
|
|
117
|
+
# Type checking
|
|
118
|
+
mypy aws_cis_assessment/
|
|
119
|
+
|
|
120
|
+
# Run all quality checks
|
|
121
|
+
pre-commit run --all-files
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Adding New Controls
|
|
125
|
+
|
|
126
|
+
### Step 1: Define Control Configuration
|
|
127
|
+
|
|
128
|
+
Add the control to the appropriate YAML file in `aws_cis_assessment/config/rules/`:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
# cis_controls_ig1.yaml
|
|
132
|
+
controls:
|
|
133
|
+
"1.5": # New control ID
|
|
134
|
+
title: "Maintain Asset Inventory Information"
|
|
135
|
+
weight: 1.0
|
|
136
|
+
config_rules:
|
|
137
|
+
- name: "ec2-instance-detailed-monitoring-enabled"
|
|
138
|
+
resource_types: ["AWS::EC2::Instance"]
|
|
139
|
+
parameters:
|
|
140
|
+
detailedMonitoringEnabled: true
|
|
141
|
+
description: "Ensures EC2 instances have detailed monitoring enabled"
|
|
142
|
+
remediation_guidance: "Enable detailed monitoring for EC2 instances to improve asset visibility"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Step 2: Create Assessment Implementation
|
|
146
|
+
|
|
147
|
+
Create a new assessment class in the appropriate IG directory:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
# aws_cis_assessment/controls/ig1/control_1_5.py
|
|
151
|
+
from typing import List, Dict, Any
|
|
152
|
+
from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
|
|
153
|
+
from aws_cis_assessment.core.models import ComplianceResult
|
|
154
|
+
from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
|
|
155
|
+
|
|
156
|
+
class EC2DetailedMonitoringEnabledAssessment(BaseConfigRuleAssessment):
|
|
157
|
+
"""
|
|
158
|
+
CIS Control 1.5: Maintain Asset Inventory Information
|
|
159
|
+
AWS Config Rule: ec2-instance-detailed-monitoring-enabled
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __init__(self):
|
|
163
|
+
super().__init__(
|
|
164
|
+
rule_name="ec2-instance-detailed-monitoring-enabled",
|
|
165
|
+
control_id="1.5",
|
|
166
|
+
resource_types=["AWS::EC2::Instance"],
|
|
167
|
+
parameters={"detailedMonitoringEnabled": True}
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any],
|
|
171
|
+
aws_factory: AWSClientFactory) -> ComplianceResult:
|
|
172
|
+
"""Evaluate compliance for individual EC2 instance."""
|
|
173
|
+
instance_id = resource['InstanceId']
|
|
174
|
+
region = resource.get('Region', 'us-east-1')
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
ec2_client = aws_factory.get_client('ec2', region)
|
|
178
|
+
|
|
179
|
+
# Check if detailed monitoring is enabled
|
|
180
|
+
response = ec2_client.describe_instances(InstanceIds=[instance_id])
|
|
181
|
+
|
|
182
|
+
for reservation in response['Reservations']:
|
|
183
|
+
for instance in reservation['Instances']:
|
|
184
|
+
monitoring_state = instance.get('Monitoring', {}).get('State', 'disabled')
|
|
185
|
+
|
|
186
|
+
if monitoring_state == 'enabled':
|
|
187
|
+
return ComplianceResult(
|
|
188
|
+
resource_id=instance_id,
|
|
189
|
+
resource_type="AWS::EC2::Instance",
|
|
190
|
+
compliance_status="COMPLIANT",
|
|
191
|
+
evaluation_reason="Detailed monitoring is enabled",
|
|
192
|
+
config_rule_name=self.rule_name,
|
|
193
|
+
region=region,
|
|
194
|
+
timestamp=self._get_current_timestamp()
|
|
195
|
+
)
|
|
196
|
+
else:
|
|
197
|
+
return ComplianceResult(
|
|
198
|
+
resource_id=instance_id,
|
|
199
|
+
resource_type="AWS::EC2::Instance",
|
|
200
|
+
compliance_status="NON_COMPLIANT",
|
|
201
|
+
evaluation_reason=f"Detailed monitoring is {monitoring_state}",
|
|
202
|
+
config_rule_name=self.rule_name,
|
|
203
|
+
region=region,
|
|
204
|
+
timestamp=self._get_current_timestamp(),
|
|
205
|
+
remediation_guidance="Enable detailed monitoring: aws ec2 monitor-instances --instance-ids " + instance_id
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
return self._create_error_result(instance_id, region, str(e))
|
|
210
|
+
|
|
211
|
+
return self._create_not_applicable_result(instance_id, region)
|
|
212
|
+
|
|
213
|
+
def _get_resources(self, aws_factory: AWSClientFactory,
|
|
214
|
+
resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
215
|
+
"""Discover EC2 instances in the region."""
|
|
216
|
+
try:
|
|
217
|
+
ec2_client = aws_factory.get_client('ec2', region)
|
|
218
|
+
|
|
219
|
+
response = ec2_client.describe_instances(
|
|
220
|
+
Filters=[
|
|
221
|
+
{'Name': 'instance-state-name', 'Values': ['running', 'stopped']}
|
|
222
|
+
]
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
instances = []
|
|
226
|
+
for reservation in response['Reservations']:
|
|
227
|
+
for instance in reservation['Instances']:
|
|
228
|
+
instances.append({
|
|
229
|
+
'InstanceId': instance['InstanceId'],
|
|
230
|
+
'InstanceType': instance['InstanceType'],
|
|
231
|
+
'State': instance['State']['Name'],
|
|
232
|
+
'Region': region
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
return instances
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
self.logger.error(f"Failed to discover EC2 instances in {region}: {str(e)}")
|
|
239
|
+
return []
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Step 3: Register the Assessment
|
|
243
|
+
|
|
244
|
+
Add the assessment to the control registry:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
# aws_cis_assessment/controls/ig1/__init__.py
|
|
248
|
+
from .control_1_5 import EC2DetailedMonitoringEnabledAssessment
|
|
249
|
+
|
|
250
|
+
# Add to the registry
|
|
251
|
+
CONTROL_ASSESSMENTS = {
|
|
252
|
+
# ... existing assessments
|
|
253
|
+
"1.5": [EC2DetailedMonitoringEnabledAssessment],
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Step 4: Add Tests
|
|
258
|
+
|
|
259
|
+
Create comprehensive tests for the new control:
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
# tests/test_control_1_5_assessments.py
|
|
263
|
+
import pytest
|
|
264
|
+
from unittest.mock import Mock, patch
|
|
265
|
+
from aws_cis_assessment.controls.ig1.control_1_5 import EC2DetailedMonitoringEnabledAssessment
|
|
266
|
+
|
|
267
|
+
class TestEC2DetailedMonitoringEnabledAssessment:
|
|
268
|
+
|
|
269
|
+
def setup_method(self):
|
|
270
|
+
self.assessment = EC2DetailedMonitoringEnabledAssessment()
|
|
271
|
+
self.mock_aws_factory = Mock()
|
|
272
|
+
|
|
273
|
+
@patch('boto3.client')
|
|
274
|
+
def test_compliant_instance(self, mock_boto_client):
|
|
275
|
+
"""Test instance with detailed monitoring enabled."""
|
|
276
|
+
# Mock EC2 response
|
|
277
|
+
mock_ec2 = Mock()
|
|
278
|
+
mock_ec2.describe_instances.return_value = {
|
|
279
|
+
'Reservations': [{
|
|
280
|
+
'Instances': [{
|
|
281
|
+
'InstanceId': 'i-1234567890abcdef0',
|
|
282
|
+
'Monitoring': {'State': 'enabled'}
|
|
283
|
+
}]
|
|
284
|
+
}]
|
|
285
|
+
}
|
|
286
|
+
mock_boto_client.return_value = mock_ec2
|
|
287
|
+
self.mock_aws_factory.get_client.return_value = mock_ec2
|
|
288
|
+
|
|
289
|
+
# Test resource
|
|
290
|
+
resource = {
|
|
291
|
+
'InstanceId': 'i-1234567890abcdef0',
|
|
292
|
+
'Region': 'us-east-1'
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
result = self.assessment._evaluate_resource_compliance(
|
|
296
|
+
resource, self.mock_aws_factory
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
assert result.compliance_status == 'COMPLIANT'
|
|
300
|
+
assert result.resource_id == 'i-1234567890abcdef0'
|
|
301
|
+
assert 'enabled' in result.evaluation_reason
|
|
302
|
+
|
|
303
|
+
@patch('boto3.client')
|
|
304
|
+
def test_non_compliant_instance(self, mock_boto_client):
|
|
305
|
+
"""Test instance with detailed monitoring disabled."""
|
|
306
|
+
# Mock EC2 response
|
|
307
|
+
mock_ec2 = Mock()
|
|
308
|
+
mock_ec2.describe_instances.return_value = {
|
|
309
|
+
'Reservations': [{
|
|
310
|
+
'Instances': [{
|
|
311
|
+
'InstanceId': 'i-1234567890abcdef0',
|
|
312
|
+
'Monitoring': {'State': 'disabled'}
|
|
313
|
+
}]
|
|
314
|
+
}]
|
|
315
|
+
}
|
|
316
|
+
mock_boto_client.return_value = mock_ec2
|
|
317
|
+
self.mock_aws_factory.get_client.return_value = mock_ec2
|
|
318
|
+
|
|
319
|
+
# Test resource
|
|
320
|
+
resource = {
|
|
321
|
+
'InstanceId': 'i-1234567890abcdef0',
|
|
322
|
+
'Region': 'us-east-1'
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
result = self.assessment._evaluate_resource_compliance(
|
|
326
|
+
resource, self.mock_aws_factory
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
assert result.compliance_status == 'NON_COMPLIANT'
|
|
330
|
+
assert result.resource_id == 'i-1234567890abcdef0'
|
|
331
|
+
assert 'disabled' in result.evaluation_reason
|
|
332
|
+
assert result.remediation_guidance is not None
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Creating Custom Assessments
|
|
336
|
+
|
|
337
|
+
### Base Assessment Class
|
|
338
|
+
|
|
339
|
+
All assessments inherit from `BaseConfigRuleAssessment`:
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
from abc import ABC, abstractmethod
|
|
343
|
+
from typing import List, Dict, Any
|
|
344
|
+
from aws_cis_assessment.core.models import ComplianceResult
|
|
345
|
+
from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
|
|
346
|
+
|
|
347
|
+
class BaseConfigRuleAssessment(ABC):
|
|
348
|
+
"""Base class for all Config rule assessments."""
|
|
349
|
+
|
|
350
|
+
def __init__(self, rule_name: str, control_id: str,
|
|
351
|
+
resource_types: List[str], parameters: Dict[str, Any]):
|
|
352
|
+
self.rule_name = rule_name
|
|
353
|
+
self.control_id = control_id
|
|
354
|
+
self.resource_types = resource_types
|
|
355
|
+
self.parameters = parameters
|
|
356
|
+
self.logger = logging.getLogger(self.__class__.__name__)
|
|
357
|
+
|
|
358
|
+
@abstractmethod
|
|
359
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any],
|
|
360
|
+
aws_factory: AWSClientFactory) -> ComplianceResult:
|
|
361
|
+
"""Evaluate compliance for individual resource."""
|
|
362
|
+
pass
|
|
363
|
+
|
|
364
|
+
@abstractmethod
|
|
365
|
+
def _get_resources(self, aws_factory: AWSClientFactory,
|
|
366
|
+
resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
367
|
+
"""Discover resources of specified type in region."""
|
|
368
|
+
pass
|
|
369
|
+
|
|
370
|
+
def evaluate_compliance(self, aws_factory: AWSClientFactory,
|
|
371
|
+
region: str) -> List[ComplianceResult]:
|
|
372
|
+
"""Evaluate compliance for all applicable resources."""
|
|
373
|
+
all_results = []
|
|
374
|
+
|
|
375
|
+
for resource_type in self.resource_types:
|
|
376
|
+
try:
|
|
377
|
+
resources = self._get_resources(aws_factory, resource_type, region)
|
|
378
|
+
|
|
379
|
+
for resource in resources:
|
|
380
|
+
result = self._evaluate_resource_compliance(resource, aws_factory)
|
|
381
|
+
all_results.append(result)
|
|
382
|
+
|
|
383
|
+
except Exception as e:
|
|
384
|
+
self.logger.error(f"Failed to evaluate {resource_type} in {region}: {str(e)}")
|
|
385
|
+
# Create error result
|
|
386
|
+
error_result = ComplianceResult(
|
|
387
|
+
resource_id=f"ERROR-{resource_type}",
|
|
388
|
+
resource_type=resource_type,
|
|
389
|
+
compliance_status="ERROR",
|
|
390
|
+
evaluation_reason=str(e),
|
|
391
|
+
config_rule_name=self.rule_name,
|
|
392
|
+
region=region,
|
|
393
|
+
timestamp=self._get_current_timestamp()
|
|
394
|
+
)
|
|
395
|
+
all_results.append(error_result)
|
|
396
|
+
|
|
397
|
+
return all_results
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Custom Assessment Example
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
class CustomS3BucketAssessment(BaseConfigRuleAssessment):
|
|
404
|
+
"""Custom assessment for S3 bucket compliance."""
|
|
405
|
+
|
|
406
|
+
def __init__(self):
|
|
407
|
+
super().__init__(
|
|
408
|
+
rule_name="custom-s3-bucket-check",
|
|
409
|
+
control_id="custom.1",
|
|
410
|
+
resource_types=["AWS::S3::Bucket"],
|
|
411
|
+
parameters={"requireEncryption": True, "requireVersioning": True}
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any],
|
|
415
|
+
aws_factory: AWSClientFactory) -> ComplianceResult:
|
|
416
|
+
bucket_name = resource['Name']
|
|
417
|
+
region = resource.get('Region', 'us-east-1')
|
|
418
|
+
|
|
419
|
+
try:
|
|
420
|
+
s3_client = aws_factory.get_client('s3', region)
|
|
421
|
+
|
|
422
|
+
# Check encryption
|
|
423
|
+
encryption_compliant = self._check_bucket_encryption(s3_client, bucket_name)
|
|
424
|
+
|
|
425
|
+
# Check versioning
|
|
426
|
+
versioning_compliant = self._check_bucket_versioning(s3_client, bucket_name)
|
|
427
|
+
|
|
428
|
+
if encryption_compliant and versioning_compliant:
|
|
429
|
+
return ComplianceResult(
|
|
430
|
+
resource_id=bucket_name,
|
|
431
|
+
resource_type="AWS::S3::Bucket",
|
|
432
|
+
compliance_status="COMPLIANT",
|
|
433
|
+
evaluation_reason="Bucket has encryption and versioning enabled",
|
|
434
|
+
config_rule_name=self.rule_name,
|
|
435
|
+
region=region,
|
|
436
|
+
timestamp=self._get_current_timestamp()
|
|
437
|
+
)
|
|
438
|
+
else:
|
|
439
|
+
issues = []
|
|
440
|
+
if not encryption_compliant:
|
|
441
|
+
issues.append("encryption disabled")
|
|
442
|
+
if not versioning_compliant:
|
|
443
|
+
issues.append("versioning disabled")
|
|
444
|
+
|
|
445
|
+
return ComplianceResult(
|
|
446
|
+
resource_id=bucket_name,
|
|
447
|
+
resource_type="AWS::S3::Bucket",
|
|
448
|
+
compliance_status="NON_COMPLIANT",
|
|
449
|
+
evaluation_reason=f"Bucket issues: {', '.join(issues)}",
|
|
450
|
+
config_rule_name=self.rule_name,
|
|
451
|
+
region=region,
|
|
452
|
+
timestamp=self._get_current_timestamp(),
|
|
453
|
+
remediation_guidance=self._get_remediation_guidance(issues)
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
except Exception as e:
|
|
457
|
+
return self._create_error_result(bucket_name, region, str(e))
|
|
458
|
+
|
|
459
|
+
def _check_bucket_encryption(self, s3_client, bucket_name: str) -> bool:
|
|
460
|
+
"""Check if bucket has encryption enabled."""
|
|
461
|
+
try:
|
|
462
|
+
response = s3_client.get_bucket_encryption(Bucket=bucket_name)
|
|
463
|
+
return 'ServerSideEncryptionConfiguration' in response
|
|
464
|
+
except s3_client.exceptions.NoSuchBucket:
|
|
465
|
+
return False
|
|
466
|
+
except Exception:
|
|
467
|
+
# If we can't check, assume non-compliant
|
|
468
|
+
return False
|
|
469
|
+
|
|
470
|
+
def _check_bucket_versioning(self, s3_client, bucket_name: str) -> bool:
|
|
471
|
+
"""Check if bucket has versioning enabled."""
|
|
472
|
+
try:
|
|
473
|
+
response = s3_client.get_bucket_versioning(Bucket=bucket_name)
|
|
474
|
+
return response.get('Status') == 'Enabled'
|
|
475
|
+
except Exception:
|
|
476
|
+
return False
|
|
477
|
+
|
|
478
|
+
def _get_resources(self, aws_factory: AWSClientFactory,
|
|
479
|
+
resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
480
|
+
"""Discover S3 buckets."""
|
|
481
|
+
try:
|
|
482
|
+
s3_client = aws_factory.get_client('s3', region)
|
|
483
|
+
response = s3_client.list_buckets()
|
|
484
|
+
|
|
485
|
+
buckets = []
|
|
486
|
+
for bucket in response['Buckets']:
|
|
487
|
+
# Get bucket region
|
|
488
|
+
try:
|
|
489
|
+
bucket_region = s3_client.get_bucket_location(
|
|
490
|
+
Bucket=bucket['Name']
|
|
491
|
+
)['LocationConstraint'] or 'us-east-1'
|
|
492
|
+
|
|
493
|
+
if bucket_region == region:
|
|
494
|
+
buckets.append({
|
|
495
|
+
'Name': bucket['Name'],
|
|
496
|
+
'CreationDate': bucket['CreationDate'],
|
|
497
|
+
'Region': region
|
|
498
|
+
})
|
|
499
|
+
except Exception:
|
|
500
|
+
# Skip buckets we can't access
|
|
501
|
+
continue
|
|
502
|
+
|
|
503
|
+
return buckets
|
|
504
|
+
|
|
505
|
+
except Exception as e:
|
|
506
|
+
self.logger.error(f"Failed to discover S3 buckets in {region}: {str(e)}")
|
|
507
|
+
return []
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
## Extending Reporters
|
|
511
|
+
|
|
512
|
+
### Base Reporter Class
|
|
513
|
+
|
|
514
|
+
All reporters inherit from `BaseReporter`:
|
|
515
|
+
|
|
516
|
+
```python
|
|
517
|
+
from abc import ABC, abstractmethod
|
|
518
|
+
from typing import Dict, Any
|
|
519
|
+
from aws_cis_assessment.core.models import AssessmentResult, ComplianceSummary
|
|
520
|
+
|
|
521
|
+
class BaseReporter(ABC):
|
|
522
|
+
"""Base class for all report generators."""
|
|
523
|
+
|
|
524
|
+
@abstractmethod
|
|
525
|
+
def generate_report(self, assessment_result: AssessmentResult,
|
|
526
|
+
compliance_summary: ComplianceSummary,
|
|
527
|
+
output_path: str) -> str:
|
|
528
|
+
"""Generate report and return content."""
|
|
529
|
+
pass
|
|
530
|
+
|
|
531
|
+
def _format_compliance_percentage(self, percentage: float) -> str:
|
|
532
|
+
"""Format compliance percentage consistently."""
|
|
533
|
+
return f"{percentage:.1f}%"
|
|
534
|
+
|
|
535
|
+
def _get_compliance_status_color(self, percentage: float) -> str:
|
|
536
|
+
"""Get color code for compliance percentage."""
|
|
537
|
+
if percentage >= 90:
|
|
538
|
+
return "green"
|
|
539
|
+
elif percentage >= 70:
|
|
540
|
+
return "yellow"
|
|
541
|
+
else:
|
|
542
|
+
return "red"
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Custom Reporter Example
|
|
546
|
+
|
|
547
|
+
```python
|
|
548
|
+
class XMLReporter(BaseReporter):
|
|
549
|
+
"""Generate XML format reports."""
|
|
550
|
+
|
|
551
|
+
def generate_report(self, assessment_result: AssessmentResult,
|
|
552
|
+
compliance_summary: ComplianceSummary,
|
|
553
|
+
output_path: str) -> str:
|
|
554
|
+
"""Generate XML report."""
|
|
555
|
+
|
|
556
|
+
xml_content = self._build_xml_content(assessment_result, compliance_summary)
|
|
557
|
+
|
|
558
|
+
# Write to file
|
|
559
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
560
|
+
f.write(xml_content)
|
|
561
|
+
|
|
562
|
+
return xml_content
|
|
563
|
+
|
|
564
|
+
def _build_xml_content(self, assessment_result: AssessmentResult,
|
|
565
|
+
compliance_summary: ComplianceSummary) -> str:
|
|
566
|
+
"""Build XML content."""
|
|
567
|
+
from xml.etree.ElementTree import Element, SubElement, tostring
|
|
568
|
+
from xml.dom import minidom
|
|
569
|
+
|
|
570
|
+
# Root element
|
|
571
|
+
root = Element('cis_assessment_report')
|
|
572
|
+
root.set('version', '1.0')
|
|
573
|
+
root.set('timestamp', assessment_result.timestamp.isoformat())
|
|
574
|
+
|
|
575
|
+
# Metadata
|
|
576
|
+
metadata = SubElement(root, 'metadata')
|
|
577
|
+
SubElement(metadata, 'account_id').text = assessment_result.account_id
|
|
578
|
+
SubElement(metadata, 'regions').text = ','.join(assessment_result.regions_assessed)
|
|
579
|
+
SubElement(metadata, 'duration').text = str(assessment_result.assessment_duration)
|
|
580
|
+
|
|
581
|
+
# Compliance summary
|
|
582
|
+
summary = SubElement(root, 'compliance_summary')
|
|
583
|
+
SubElement(summary, 'overall_compliance').text = str(compliance_summary.overall_compliance_percentage)
|
|
584
|
+
SubElement(summary, 'ig1_compliance').text = str(compliance_summary.ig1_compliance_percentage)
|
|
585
|
+
SubElement(summary, 'ig2_compliance').text = str(compliance_summary.ig2_compliance_percentage)
|
|
586
|
+
SubElement(summary, 'ig3_compliance').text = str(compliance_summary.ig3_compliance_percentage)
|
|
587
|
+
|
|
588
|
+
# Detailed results
|
|
589
|
+
results = SubElement(root, 'detailed_results')
|
|
590
|
+
|
|
591
|
+
for ig_name, ig_score in assessment_result.ig_scores.items():
|
|
592
|
+
ig_element = SubElement(results, 'implementation_group')
|
|
593
|
+
ig_element.set('name', ig_name)
|
|
594
|
+
ig_element.set('compliance', f"{ig_score.compliance_percentage:.1f}")
|
|
595
|
+
|
|
596
|
+
for control_id, control_score in ig_score.control_scores.items():
|
|
597
|
+
control_element = SubElement(ig_element, 'control')
|
|
598
|
+
control_element.set('id', control_id)
|
|
599
|
+
control_element.set('compliance', f"{control_score.compliance_percentage:.1f}")
|
|
600
|
+
|
|
601
|
+
# Add findings
|
|
602
|
+
for finding in control_score.findings:
|
|
603
|
+
finding_element = SubElement(control_element, 'finding')
|
|
604
|
+
finding_element.set('resource_id', finding.resource_id)
|
|
605
|
+
finding_element.set('status', finding.compliance_status)
|
|
606
|
+
finding_element.text = finding.evaluation_reason
|
|
607
|
+
|
|
608
|
+
# Pretty print XML
|
|
609
|
+
rough_string = tostring(root, 'unicode')
|
|
610
|
+
reparsed = minidom.parseString(rough_string)
|
|
611
|
+
return reparsed.toprettyxml(indent=" ")
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
## Testing Framework
|
|
615
|
+
|
|
616
|
+
### Unit Tests
|
|
617
|
+
|
|
618
|
+
Use pytest for unit testing:
|
|
619
|
+
|
|
620
|
+
```python
|
|
621
|
+
# tests/test_custom_assessment.py
|
|
622
|
+
import pytest
|
|
623
|
+
from unittest.mock import Mock, patch
|
|
624
|
+
from aws_cis_assessment.controls.custom.custom_s3_assessment import CustomS3BucketAssessment
|
|
625
|
+
|
|
626
|
+
class TestCustomS3BucketAssessment:
|
|
627
|
+
|
|
628
|
+
def setup_method(self):
|
|
629
|
+
self.assessment = CustomS3BucketAssessment()
|
|
630
|
+
self.mock_aws_factory = Mock()
|
|
631
|
+
|
|
632
|
+
@pytest.fixture
|
|
633
|
+
def mock_s3_client(self):
|
|
634
|
+
mock_client = Mock()
|
|
635
|
+
self.mock_aws_factory.get_client.return_value = mock_client
|
|
636
|
+
return mock_client
|
|
637
|
+
|
|
638
|
+
def test_compliant_bucket(self, mock_s3_client):
|
|
639
|
+
"""Test bucket with encryption and versioning enabled."""
|
|
640
|
+
# Mock responses
|
|
641
|
+
mock_s3_client.get_bucket_encryption.return_value = {
|
|
642
|
+
'ServerSideEncryptionConfiguration': {
|
|
643
|
+
'Rules': [{'ApplyServerSideEncryptionByDefault': {'SSEAlgorithm': 'AES256'}}]
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
mock_s3_client.get_bucket_versioning.return_value = {'Status': 'Enabled'}
|
|
647
|
+
|
|
648
|
+
resource = {'Name': 'test-bucket', 'Region': 'us-east-1'}
|
|
649
|
+
|
|
650
|
+
result = self.assessment._evaluate_resource_compliance(resource, self.mock_aws_factory)
|
|
651
|
+
|
|
652
|
+
assert result.compliance_status == 'COMPLIANT'
|
|
653
|
+
assert result.resource_id == 'test-bucket'
|
|
654
|
+
assert 'encryption and versioning enabled' in result.evaluation_reason
|
|
655
|
+
|
|
656
|
+
def test_non_compliant_bucket(self, mock_s3_client):
|
|
657
|
+
"""Test bucket missing encryption."""
|
|
658
|
+
# Mock responses
|
|
659
|
+
mock_s3_client.get_bucket_encryption.side_effect = Exception("No encryption")
|
|
660
|
+
mock_s3_client.get_bucket_versioning.return_value = {'Status': 'Enabled'}
|
|
661
|
+
|
|
662
|
+
resource = {'Name': 'test-bucket', 'Region': 'us-east-1'}
|
|
663
|
+
|
|
664
|
+
result = self.assessment._evaluate_resource_compliance(resource, self.mock_aws_factory)
|
|
665
|
+
|
|
666
|
+
assert result.compliance_status == 'NON_COMPLIANT'
|
|
667
|
+
assert 'encryption disabled' in result.evaluation_reason
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### Property-Based Tests
|
|
671
|
+
|
|
672
|
+
Use Hypothesis for property-based testing:
|
|
673
|
+
|
|
674
|
+
```python
|
|
675
|
+
# tests/test_assessment_properties.py
|
|
676
|
+
from hypothesis import given, strategies as st
|
|
677
|
+
from aws_cis_assessment.core.scoring_engine import ScoringEngine
|
|
678
|
+
from aws_cis_assessment.core.models import ComplianceResult
|
|
679
|
+
|
|
680
|
+
class TestAssessmentProperties:
|
|
681
|
+
|
|
682
|
+
@given(st.lists(st.sampled_from(['COMPLIANT', 'NON_COMPLIANT']), min_size=1))
|
|
683
|
+
def test_compliance_percentage_bounds(self, statuses):
|
|
684
|
+
"""Property: Compliance percentage should always be between 0 and 100."""
|
|
685
|
+
# Create mock compliance results
|
|
686
|
+
results = []
|
|
687
|
+
for i, status in enumerate(statuses):
|
|
688
|
+
result = ComplianceResult(
|
|
689
|
+
resource_id=f"resource-{i}",
|
|
690
|
+
resource_type="AWS::Test::Resource",
|
|
691
|
+
compliance_status=status,
|
|
692
|
+
evaluation_reason="Test",
|
|
693
|
+
config_rule_name="test-rule",
|
|
694
|
+
region="us-east-1",
|
|
695
|
+
timestamp=datetime.now()
|
|
696
|
+
)
|
|
697
|
+
results.append(result)
|
|
698
|
+
|
|
699
|
+
scoring_engine = ScoringEngine()
|
|
700
|
+
control_score = scoring_engine.calculate_control_score(results)
|
|
701
|
+
|
|
702
|
+
assert 0 <= control_score.compliance_percentage <= 100
|
|
703
|
+
|
|
704
|
+
@given(st.integers(min_value=1, max_value=100))
|
|
705
|
+
def test_all_compliant_gives_100_percent(self, num_resources):
|
|
706
|
+
"""Property: All compliant resources should give 100% compliance."""
|
|
707
|
+
results = []
|
|
708
|
+
for i in range(num_resources):
|
|
709
|
+
result = ComplianceResult(
|
|
710
|
+
resource_id=f"resource-{i}",
|
|
711
|
+
resource_type="AWS::Test::Resource",
|
|
712
|
+
compliance_status="COMPLIANT",
|
|
713
|
+
evaluation_reason="Test",
|
|
714
|
+
config_rule_name="test-rule",
|
|
715
|
+
region="us-east-1",
|
|
716
|
+
timestamp=datetime.now()
|
|
717
|
+
)
|
|
718
|
+
results.append(result)
|
|
719
|
+
|
|
720
|
+
scoring_engine = ScoringEngine()
|
|
721
|
+
control_score = scoring_engine.calculate_control_score(results)
|
|
722
|
+
|
|
723
|
+
assert control_score.compliance_percentage == 100.0
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### Integration Tests
|
|
727
|
+
|
|
728
|
+
Test complete workflows:
|
|
729
|
+
|
|
730
|
+
```python
|
|
731
|
+
# tests/test_integration.py
|
|
732
|
+
import pytest
|
|
733
|
+
from aws_cis_assessment.core.assessment_engine import AssessmentEngine
|
|
734
|
+
|
|
735
|
+
@pytest.mark.integration
|
|
736
|
+
class TestAssessmentIntegration:
|
|
737
|
+
|
|
738
|
+
def test_full_ig1_assessment(self, aws_credentials, test_region):
|
|
739
|
+
"""Integration test for full IG1 assessment."""
|
|
740
|
+
engine = AssessmentEngine(
|
|
741
|
+
aws_credentials=aws_credentials,
|
|
742
|
+
regions=[test_region],
|
|
743
|
+
max_workers=1
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
result = engine.run_assessment(implementation_groups=['IG1'])
|
|
747
|
+
|
|
748
|
+
assert result is not None
|
|
749
|
+
assert result.account_id is not None
|
|
750
|
+
assert len(result.regions_assessed) == 1
|
|
751
|
+
assert 'IG1' in result.ig_scores
|
|
752
|
+
assert result.total_resources_evaluated > 0
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
## Contributing Guidelines
|
|
756
|
+
|
|
757
|
+
### Code Standards
|
|
758
|
+
|
|
759
|
+
1. **Follow PEP 8**: Use black for formatting
|
|
760
|
+
2. **Type hints**: Add type hints to all functions
|
|
761
|
+
3. **Docstrings**: Use Google-style docstrings
|
|
762
|
+
4. **Error handling**: Handle exceptions gracefully
|
|
763
|
+
5. **Logging**: Use structured logging
|
|
764
|
+
|
|
765
|
+
### Pull Request Process
|
|
766
|
+
|
|
767
|
+
1. **Fork the repository**
|
|
768
|
+
2. **Create feature branch**: `git checkout -b feature/new-control`
|
|
769
|
+
3. **Write tests**: Ensure good test coverage
|
|
770
|
+
4. **Update documentation**: Update relevant docs
|
|
771
|
+
5. **Run quality checks**: `pre-commit run --all-files`
|
|
772
|
+
6. **Submit PR**: Include description and testing notes
|
|
773
|
+
|
|
774
|
+
### Testing Requirements
|
|
775
|
+
|
|
776
|
+
- **Unit tests**: Test individual components
|
|
777
|
+
- **Integration tests**: Test complete workflows
|
|
778
|
+
- **Property tests**: Test invariants and properties
|
|
779
|
+
- **Coverage**: Maintain >90% test coverage
|
|
780
|
+
|
|
781
|
+
### Documentation Requirements
|
|
782
|
+
|
|
783
|
+
- **Code comments**: Explain complex logic
|
|
784
|
+
- **API documentation**: Document all public APIs
|
|
785
|
+
- **User documentation**: Update user guides
|
|
786
|
+
- **Examples**: Provide usage examples
|
|
787
|
+
|
|
788
|
+
## API Reference
|
|
789
|
+
|
|
790
|
+
### Core Classes
|
|
791
|
+
|
|
792
|
+
#### AssessmentEngine
|
|
793
|
+
|
|
794
|
+
Main orchestration class for running assessments.
|
|
795
|
+
|
|
796
|
+
```python
|
|
797
|
+
class AssessmentEngine:
|
|
798
|
+
def __init__(self, aws_credentials: Dict[str, str], regions: List[str],
|
|
799
|
+
config_path: Optional[str] = None, max_workers: int = 4):
|
|
800
|
+
"""Initialize assessment engine."""
|
|
801
|
+
|
|
802
|
+
def run_assessment(self, implementation_groups: Optional[List[str]] = None,
|
|
803
|
+
controls: Optional[List[str]] = None) -> AssessmentResult:
|
|
804
|
+
"""Run compliance assessment."""
|
|
805
|
+
|
|
806
|
+
def validate_configuration(self) -> List[str]:
|
|
807
|
+
"""Validate configuration and return errors."""
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
#### ScoringEngine
|
|
811
|
+
|
|
812
|
+
Calculate compliance scores from assessment results.
|
|
813
|
+
|
|
814
|
+
```python
|
|
815
|
+
class ScoringEngine:
|
|
816
|
+
def calculate_control_score(self, results: List[ComplianceResult]) -> ControlScore:
|
|
817
|
+
"""Calculate compliance score for individual control."""
|
|
818
|
+
|
|
819
|
+
def calculate_ig_score(self, control_scores: Dict[str, ControlScore]) -> IGScore:
|
|
820
|
+
"""Calculate Implementation Group compliance score."""
|
|
821
|
+
|
|
822
|
+
def generate_compliance_summary(self, assessment_result: AssessmentResult) -> ComplianceSummary:
|
|
823
|
+
"""Generate executive summary of compliance status."""
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
#### AWSClientFactory
|
|
827
|
+
|
|
828
|
+
Manage AWS service clients with credential handling.
|
|
829
|
+
|
|
830
|
+
```python
|
|
831
|
+
class AWSClientFactory:
|
|
832
|
+
def __init__(self, credentials: Dict[str, str], regions: List[str]):
|
|
833
|
+
"""Initialize with AWS credentials and regions."""
|
|
834
|
+
|
|
835
|
+
def get_client(self, service_name: str, region: str = None) -> boto3.client:
|
|
836
|
+
"""Get AWS service client for specified service and region."""
|
|
837
|
+
|
|
838
|
+
def validate_credentials(self) -> bool:
|
|
839
|
+
"""Validate AWS credentials and permissions."""
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
### Data Models
|
|
843
|
+
|
|
844
|
+
All data models are defined in `aws_cis_assessment.core.models`:
|
|
845
|
+
|
|
846
|
+
- `ComplianceResult`: Individual resource compliance result
|
|
847
|
+
- `ControlScore`: CIS Control compliance score
|
|
848
|
+
- `IGScore`: Implementation Group compliance score
|
|
849
|
+
- `AssessmentResult`: Complete assessment result
|
|
850
|
+
- `ComplianceSummary`: Executive summary
|
|
851
|
+
|
|
852
|
+
### Utility Functions
|
|
853
|
+
|
|
854
|
+
Common utility functions are available in various modules:
|
|
855
|
+
|
|
856
|
+
- `aws_cis_assessment.cli.utils`: CLI utilities
|
|
857
|
+
- `aws_cis_assessment.core.utils`: Core utilities
|
|
858
|
+
- `aws_cis_assessment.reporters.utils`: Reporting utilities
|