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