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,166 @@
1
+ """Data models for CIS Controls and AWS Config rule specifications."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import datetime, timedelta
5
+ from typing import Dict, List, Optional, Any
6
+ from enum import Enum
7
+
8
+
9
+ class ComplianceStatus(Enum):
10
+ """Compliance status enumeration."""
11
+ COMPLIANT = "COMPLIANT"
12
+ NON_COMPLIANT = "NON_COMPLIANT"
13
+ NOT_APPLICABLE = "NOT_APPLICABLE"
14
+ ERROR = "ERROR"
15
+ INSUFFICIENT_PERMISSIONS = "INSUFFICIENT_PERMISSIONS"
16
+
17
+
18
+ class ImplementationGroup(Enum):
19
+ """CIS Controls Implementation Groups."""
20
+ IG1 = "IG1"
21
+ IG2 = "IG2"
22
+ IG3 = "IG3"
23
+
24
+
25
+ @dataclass
26
+ class ConfigRule:
27
+ """AWS Config rule specification for CIS Control assessment."""
28
+ name: str
29
+ control_id: str
30
+ resource_types: List[str]
31
+ parameters: Dict[str, Any] = field(default_factory=dict)
32
+ implementation_group: str = "IG1"
33
+ description: str = ""
34
+ remediation_guidance: str = ""
35
+
36
+ def __post_init__(self):
37
+ """Validate ConfigRule after initialization."""
38
+ if not self.name:
39
+ raise ValueError("ConfigRule name cannot be empty")
40
+ if not self.control_id:
41
+ raise ValueError("ConfigRule control_id cannot be empty")
42
+ if not self.resource_types:
43
+ raise ValueError("ConfigRule must have at least one resource type")
44
+
45
+
46
+ @dataclass
47
+ class CISControl:
48
+ """CIS Control definition with associated Config rules."""
49
+ control_id: str
50
+ title: str
51
+ implementation_group: str
52
+ config_rules: List[ConfigRule] = field(default_factory=list)
53
+ weight: float = 1.0
54
+
55
+ def __post_init__(self):
56
+ """Validate CISControl after initialization."""
57
+ if not self.control_id:
58
+ raise ValueError("CISControl control_id cannot be empty")
59
+ if not self.title:
60
+ raise ValueError("CISControl title cannot be empty")
61
+ if self.implementation_group not in [ig.value for ig in ImplementationGroup]:
62
+ raise ValueError(f"Invalid implementation group: {self.implementation_group}")
63
+
64
+
65
+ @dataclass
66
+ class ComplianceResult:
67
+ """Individual resource compliance evaluation result."""
68
+ resource_id: str
69
+ resource_type: str
70
+ compliance_status: ComplianceStatus
71
+ evaluation_reason: str
72
+ config_rule_name: str
73
+ region: str
74
+ timestamp: datetime = field(default_factory=datetime.now)
75
+ remediation_guidance: Optional[str] = None
76
+
77
+ def __post_init__(self):
78
+ """Validate ComplianceResult after initialization."""
79
+ if not self.resource_id:
80
+ raise ValueError("ComplianceResult resource_id cannot be empty")
81
+ if not self.resource_type:
82
+ raise ValueError("ComplianceResult resource_type cannot be empty")
83
+ if not isinstance(self.compliance_status, ComplianceStatus):
84
+ raise ValueError("ComplianceResult compliance_status must be ComplianceStatus enum")
85
+
86
+
87
+ @dataclass
88
+ class ControlScore:
89
+ """CIS Control compliance score."""
90
+ control_id: str
91
+ title: str
92
+ implementation_group: str
93
+ total_resources: int
94
+ compliant_resources: int
95
+ compliance_percentage: float
96
+ config_rules_evaluated: List[str] = field(default_factory=list)
97
+ findings: List[ComplianceResult] = field(default_factory=list)
98
+
99
+ def __post_init__(self):
100
+ """Calculate compliance percentage if not provided."""
101
+ if self.total_resources > 0:
102
+ calculated_percentage = (self.compliant_resources / self.total_resources) * 100
103
+ if abs(self.compliance_percentage - calculated_percentage) > 0.01:
104
+ self.compliance_percentage = calculated_percentage
105
+
106
+
107
+ @dataclass
108
+ class IGScore:
109
+ """Implementation Group compliance score."""
110
+ implementation_group: str
111
+ total_controls: int
112
+ compliant_controls: int
113
+ compliance_percentage: float
114
+ control_scores: Dict[str, ControlScore] = field(default_factory=dict)
115
+
116
+ def __post_init__(self):
117
+ """Validate IGScore after initialization."""
118
+ if self.implementation_group not in [ig.value for ig in ImplementationGroup]:
119
+ raise ValueError(f"Invalid implementation group: {self.implementation_group}")
120
+
121
+
122
+ @dataclass
123
+ class AssessmentResult:
124
+ """Complete assessment result."""
125
+ account_id: str
126
+ regions_assessed: List[str]
127
+ timestamp: datetime
128
+ overall_score: float
129
+ ig_scores: Dict[str, IGScore] = field(default_factory=dict)
130
+ total_resources_evaluated: int = 0
131
+ assessment_duration: Optional[timedelta] = None
132
+
133
+ def __post_init__(self):
134
+ """Validate AssessmentResult after initialization."""
135
+ if not self.account_id:
136
+ raise ValueError("AssessmentResult account_id cannot be empty")
137
+ if not self.regions_assessed:
138
+ raise ValueError("AssessmentResult must assess at least one region")
139
+
140
+
141
+ @dataclass
142
+ class RemediationGuidance:
143
+ """Remediation guidance for non-compliant resources."""
144
+ config_rule_name: str
145
+ control_id: str
146
+ remediation_steps: List[str]
147
+ aws_documentation_link: str
148
+ priority: str = "MEDIUM" # HIGH, MEDIUM, LOW
149
+ estimated_effort: str = "Unknown"
150
+
151
+ def __post_init__(self):
152
+ """Validate RemediationGuidance after initialization."""
153
+ if self.priority not in ["HIGH", "MEDIUM", "LOW"]:
154
+ raise ValueError(f"Invalid priority: {self.priority}")
155
+
156
+
157
+ @dataclass
158
+ class ComplianceSummary:
159
+ """Executive summary of compliance assessment."""
160
+ overall_compliance_percentage: float
161
+ ig1_compliance_percentage: float
162
+ ig2_compliance_percentage: float
163
+ ig3_compliance_percentage: float
164
+ top_risk_areas: List[str] = field(default_factory=list)
165
+ remediation_priorities: List[RemediationGuidance] = field(default_factory=list)
166
+ compliance_trend: Optional[str] = None
@@ -0,0 +1,459 @@
1
+ """Scoring Engine for calculating CIS Controls compliance scores."""
2
+
3
+ import logging
4
+ from typing import Dict, List, Optional, Any
5
+ from datetime import datetime, timedelta
6
+ from collections import defaultdict
7
+
8
+ from aws_cis_assessment.core.models import (
9
+ ComplianceResult, ComplianceStatus, ControlScore, IGScore,
10
+ AssessmentResult, ComplianceSummary, RemediationGuidance
11
+ )
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class ScoringEngine:
17
+ """Calculate compliance scores based on Config rule evaluation results."""
18
+
19
+ def __init__(self, control_weights: Optional[Dict[str, float]] = None,
20
+ ig_weights: Optional[Dict[str, float]] = None):
21
+ """Initialize scoring engine with optional custom weights.
22
+
23
+ Args:
24
+ control_weights: Optional dictionary mapping control IDs to weights
25
+ ig_weights: Optional dictionary mapping IG names to weights
26
+ """
27
+ # Default control weights based on CIS Controls importance
28
+ self.control_weights = control_weights or {
29
+ '1.1': 1.0, # Asset Inventory - foundational
30
+ '3.3': 1.5, # Data Access Control - critical
31
+ '4.1': 1.2, # Secure Configuration - important
32
+ '5.2': 1.3, # Password Management - important
33
+ '3.10': 1.4, # Encryption in Transit - critical
34
+ '3.11': 1.4, # Encryption at Rest - critical
35
+ '7.1': 1.1, # Vulnerability Management - important
36
+ '3.14': 1.2, # Sensitive Data Logging - important
37
+ '12.8': 1.3, # Network Segmentation - important
38
+ '13.1': 1.2, # Network Monitoring - important
39
+ }
40
+
41
+ # Default IG weights - higher IGs have more weight
42
+ self.ig_weights = ig_weights or {
43
+ 'IG1': 1.0,
44
+ 'IG2': 1.5,
45
+ 'IG3': 2.0
46
+ }
47
+
48
+ logger.info("ScoringEngine initialized with control and IG weights")
49
+
50
+ def calculate_control_score(self, control_id: str, rule_results: List[ComplianceResult],
51
+ control_title: str = "", implementation_group: str = "") -> ControlScore:
52
+ """Calculate compliance score for individual CIS Control.
53
+
54
+ Args:
55
+ control_id: CIS Control identifier (e.g., '1.1', '3.3')
56
+ rule_results: List of ComplianceResult objects for this control
57
+ control_title: Optional title for the control
58
+ implementation_group: Optional IG designation
59
+
60
+ Returns:
61
+ ControlScore object with calculated compliance metrics
62
+ """
63
+ if not rule_results:
64
+ return ControlScore(
65
+ control_id=control_id,
66
+ title=control_title or f"CIS Control {control_id}",
67
+ implementation_group=implementation_group,
68
+ total_resources=0,
69
+ compliant_resources=0,
70
+ compliance_percentage=0.0,
71
+ config_rules_evaluated=[],
72
+ findings=[]
73
+ )
74
+
75
+ # Filter out error results for scoring (but keep them in findings)
76
+ scorable_results = [r for r in rule_results
77
+ if r.compliance_status in [ComplianceStatus.COMPLIANT,
78
+ ComplianceStatus.NON_COMPLIANT,
79
+ ComplianceStatus.NOT_APPLICABLE]]
80
+
81
+ # Calculate basic metrics
82
+ total_resources = len(scorable_results)
83
+ compliant_resources = sum(1 for r in scorable_results
84
+ if r.compliance_status == ComplianceStatus.COMPLIANT)
85
+
86
+ # Calculate compliance percentage
87
+ if total_resources > 0:
88
+ compliance_percentage = (compliant_resources / total_resources) * 100
89
+ else:
90
+ compliance_percentage = 0.0
91
+
92
+ # Get unique config rules evaluated
93
+ config_rules_evaluated = list(set(r.config_rule_name for r in rule_results))
94
+
95
+ # Apply control-specific weighting if needed
96
+ control_weight = self.control_weights.get(control_id, 1.0)
97
+ weighted_compliance = compliance_percentage * control_weight
98
+
99
+ logger.debug(f"Control {control_id}: {compliant_resources}/{total_resources} "
100
+ f"({compliance_percentage:.1f}%) compliant, weight: {control_weight}")
101
+
102
+ return ControlScore(
103
+ control_id=control_id,
104
+ title=control_title or f"CIS Control {control_id}",
105
+ implementation_group=implementation_group,
106
+ total_resources=total_resources,
107
+ compliant_resources=compliant_resources,
108
+ compliance_percentage=compliance_percentage,
109
+ config_rules_evaluated=config_rules_evaluated,
110
+ findings=rule_results
111
+ )
112
+
113
+ def calculate_ig_score(self, implementation_group: str,
114
+ control_scores: Dict[str, ControlScore]) -> IGScore:
115
+ """Calculate Implementation Group compliance score.
116
+
117
+ Args:
118
+ implementation_group: IG1, IG2, or IG3
119
+ control_scores: Dictionary mapping control IDs to ControlScore objects
120
+
121
+ Returns:
122
+ IGScore object with calculated IG-level metrics
123
+ """
124
+ if not control_scores:
125
+ return IGScore(
126
+ implementation_group=implementation_group,
127
+ total_controls=0,
128
+ compliant_controls=0,
129
+ compliance_percentage=0.0,
130
+ control_scores={}
131
+ )
132
+
133
+ total_controls = len(control_scores)
134
+
135
+ # Calculate weighted average compliance
136
+ total_weighted_score = 0.0
137
+ total_weight = 0.0
138
+ compliant_controls = 0
139
+
140
+ for control_id, control_score in control_scores.items():
141
+ control_weight = self.control_weights.get(control_id, 1.0)
142
+ total_weighted_score += control_score.compliance_percentage * control_weight
143
+ total_weight += control_weight
144
+
145
+ # Consider control compliant if >= 80% compliance
146
+ if control_score.compliance_percentage >= 80.0:
147
+ compliant_controls += 1
148
+
149
+ # Calculate overall IG compliance percentage
150
+ if total_weight > 0:
151
+ ig_compliance_percentage = total_weighted_score / total_weight
152
+ else:
153
+ ig_compliance_percentage = 0.0
154
+
155
+ logger.info(f"IG {implementation_group}: {compliant_controls}/{total_controls} "
156
+ f"controls compliant, overall: {ig_compliance_percentage:.1f}%")
157
+
158
+ return IGScore(
159
+ implementation_group=implementation_group,
160
+ total_controls=total_controls,
161
+ compliant_controls=compliant_controls,
162
+ compliance_percentage=ig_compliance_percentage,
163
+ control_scores=control_scores
164
+ )
165
+
166
+ def calculate_overall_score(self, ig_scores: Dict[str, IGScore]) -> float:
167
+ """Calculate overall compliance score across all Implementation Groups.
168
+
169
+ Args:
170
+ ig_scores: Dictionary mapping IG names to IGScore objects
171
+
172
+ Returns:
173
+ Overall compliance percentage (0-100)
174
+ """
175
+ if not ig_scores:
176
+ return 0.0
177
+
178
+ # Calculate weighted average across IGs
179
+ total_weighted_score = 0.0
180
+ total_weight = 0.0
181
+
182
+ for ig_name, ig_score in ig_scores.items():
183
+ ig_weight = self.ig_weights.get(ig_name, 1.0)
184
+ total_weighted_score += ig_score.compliance_percentage * ig_weight
185
+ total_weight += ig_weight
186
+
187
+ if total_weight > 0:
188
+ overall_score = total_weighted_score / total_weight
189
+ else:
190
+ overall_score = 0.0
191
+
192
+ logger.info(f"Overall compliance score: {overall_score:.1f}%")
193
+ return overall_score
194
+
195
+ def generate_compliance_summary(self, assessment_result: AssessmentResult) -> ComplianceSummary:
196
+ """Generate executive summary of compliance status.
197
+
198
+ Args:
199
+ assessment_result: Complete assessment result
200
+
201
+ Returns:
202
+ ComplianceSummary with executive-level metrics and recommendations
203
+ """
204
+ # Extract IG-specific compliance percentages
205
+ ig1_compliance = assessment_result.ig_scores.get('IG1', IGScore('IG1', 0, 0, 0.0)).compliance_percentage
206
+ ig2_compliance = assessment_result.ig_scores.get('IG2', IGScore('IG2', 0, 0, 0.0)).compliance_percentage
207
+ ig3_compliance = assessment_result.ig_scores.get('IG3', IGScore('IG3', 0, 0, 0.0)).compliance_percentage
208
+
209
+ # Identify top risk areas (controls with lowest compliance)
210
+ top_risk_areas = self._identify_risk_areas(assessment_result.ig_scores)
211
+
212
+ # Generate remediation priorities
213
+ remediation_priorities = self._generate_remediation_priorities(assessment_result.ig_scores)
214
+
215
+ # Determine compliance trend (would require historical data)
216
+ compliance_trend = self._determine_compliance_trend(assessment_result)
217
+
218
+ return ComplianceSummary(
219
+ overall_compliance_percentage=assessment_result.overall_score,
220
+ ig1_compliance_percentage=ig1_compliance,
221
+ ig2_compliance_percentage=ig2_compliance,
222
+ ig3_compliance_percentage=ig3_compliance,
223
+ top_risk_areas=top_risk_areas,
224
+ remediation_priorities=remediation_priorities,
225
+ compliance_trend=compliance_trend
226
+ )
227
+
228
+ def _identify_risk_areas(self, ig_scores: Dict[str, IGScore],
229
+ max_risk_areas: int = 5) -> List[str]:
230
+ """Identify top risk areas based on lowest compliance scores.
231
+
232
+ Args:
233
+ ig_scores: Dictionary of IG scores
234
+ max_risk_areas: Maximum number of risk areas to return
235
+
236
+ Returns:
237
+ List of risk area descriptions
238
+ """
239
+ risk_areas = []
240
+
241
+ # Collect all control scores across IGs
242
+ all_control_scores = []
243
+ for ig_score in ig_scores.values():
244
+ for control_id, control_score in ig_score.control_scores.items():
245
+ all_control_scores.append((control_id, control_score))
246
+
247
+ # Sort by compliance percentage (lowest first)
248
+ all_control_scores.sort(key=lambda x: x[1].compliance_percentage)
249
+
250
+ # Generate risk area descriptions
251
+ for control_id, control_score in all_control_scores[:max_risk_areas]:
252
+ if control_score.compliance_percentage < 80.0: # Only include non-compliant controls
253
+ risk_description = f"Control {control_id} ({control_score.title}): " \
254
+ f"{control_score.compliance_percentage:.1f}% compliant"
255
+ risk_areas.append(risk_description)
256
+
257
+ return risk_areas
258
+
259
+ def _generate_remediation_priorities(self, ig_scores: Dict[str, IGScore],
260
+ max_priorities: int = 10) -> List[RemediationGuidance]:
261
+ """Generate prioritized remediation guidance.
262
+
263
+ Args:
264
+ ig_scores: Dictionary of IG scores
265
+ max_priorities: Maximum number of remediation items to return
266
+
267
+ Returns:
268
+ List of RemediationGuidance objects prioritized by impact
269
+ """
270
+ remediation_priorities = []
271
+
272
+ # Collect non-compliant findings across all controls
273
+ non_compliant_findings = []
274
+ for ig_score in ig_scores.values():
275
+ for control_score in ig_score.control_scores.values():
276
+ for finding in control_score.findings:
277
+ if finding.compliance_status == ComplianceStatus.NON_COMPLIANT:
278
+ non_compliant_findings.append((control_score, finding))
279
+
280
+ # Group by config rule and prioritize
281
+ rule_findings = defaultdict(list)
282
+ for control_score, finding in non_compliant_findings:
283
+ rule_findings[finding.config_rule_name].append((control_score, finding))
284
+
285
+ # Generate remediation guidance for top rules
286
+ for rule_name, findings in list(rule_findings.items())[:max_priorities]:
287
+ control_score, sample_finding = findings[0]
288
+
289
+ # Determine priority based on control weight and number of affected resources
290
+ control_weight = self.control_weights.get(control_score.control_id, 1.0)
291
+ affected_resources = len(findings)
292
+
293
+ if control_weight >= 1.4 or affected_resources >= 10:
294
+ priority = "HIGH"
295
+ elif control_weight >= 1.2 or affected_resources >= 5:
296
+ priority = "MEDIUM"
297
+ else:
298
+ priority = "LOW"
299
+
300
+ # Generate basic remediation steps
301
+ remediation_steps = self._generate_remediation_steps(rule_name)
302
+
303
+ remediation_guidance = RemediationGuidance(
304
+ config_rule_name=rule_name,
305
+ control_id=control_score.control_id,
306
+ remediation_steps=remediation_steps,
307
+ aws_documentation_link=f"https://docs.aws.amazon.com/config/latest/developerguide/{rule_name}.html",
308
+ priority=priority,
309
+ estimated_effort=self._estimate_remediation_effort(rule_name, affected_resources)
310
+ )
311
+
312
+ remediation_priorities.append(remediation_guidance)
313
+
314
+ # Sort by priority (HIGH, MEDIUM, LOW)
315
+ priority_order = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}
316
+ remediation_priorities.sort(key=lambda x: priority_order.get(x.priority, 3))
317
+
318
+ return remediation_priorities
319
+
320
+ def _generate_remediation_steps(self, rule_name: str) -> List[str]:
321
+ """Generate basic remediation steps for a Config rule.
322
+
323
+ Args:
324
+ rule_name: AWS Config rule name
325
+
326
+ Returns:
327
+ List of remediation step descriptions
328
+ """
329
+ # Basic remediation steps based on rule patterns
330
+ if 'iam' in rule_name:
331
+ return [
332
+ "Review IAM policies and permissions",
333
+ "Remove unnecessary privileges",
334
+ "Enable MFA where required",
335
+ "Update password policies if applicable"
336
+ ]
337
+ elif 'encrypt' in rule_name:
338
+ return [
339
+ "Enable encryption for the identified resources",
340
+ "Use AWS KMS for key management",
341
+ "Update resource configurations to require encryption",
342
+ "Verify encryption settings are applied"
343
+ ]
344
+ elif 's3' in rule_name:
345
+ return [
346
+ "Review S3 bucket policies and ACLs",
347
+ "Remove public access if not required",
348
+ "Enable appropriate S3 security features",
349
+ "Update bucket configurations"
350
+ ]
351
+ elif 'vpc' in rule_name or 'security-group' in rule_name:
352
+ return [
353
+ "Review network security group rules",
354
+ "Remove overly permissive rules",
355
+ "Implement principle of least privilege",
356
+ "Update VPC configurations as needed"
357
+ ]
358
+ else:
359
+ return [
360
+ f"Review {rule_name} configuration",
361
+ "Apply AWS security best practices",
362
+ "Update resource settings to meet compliance requirements",
363
+ "Verify changes resolve the compliance issue"
364
+ ]
365
+
366
+ def _estimate_remediation_effort(self, rule_name: str, affected_resources: int) -> str:
367
+ """Estimate effort required for remediation.
368
+
369
+ Args:
370
+ rule_name: AWS Config rule name
371
+ affected_resources: Number of affected resources
372
+
373
+ Returns:
374
+ Effort estimate string
375
+ """
376
+ # Base effort on rule complexity and resource count
377
+ if affected_resources <= 5:
378
+ base_effort = "Low"
379
+ elif affected_resources <= 20:
380
+ base_effort = "Medium"
381
+ else:
382
+ base_effort = "High"
383
+
384
+ # Adjust for rule complexity
385
+ complex_rules = ['iam-password-policy', 'vpc-sg-open-only-to-authorized-ports',
386
+ 'multi-region-cloudtrail-enabled']
387
+
388
+ if rule_name in complex_rules:
389
+ if base_effort == "Low":
390
+ base_effort = "Medium"
391
+ elif base_effort == "Medium":
392
+ base_effort = "High"
393
+
394
+ return base_effort
395
+
396
+ def _determine_compliance_trend(self, assessment_result: AssessmentResult) -> Optional[str]:
397
+ """Determine compliance trend (requires historical data).
398
+
399
+ Args:
400
+ assessment_result: Current assessment result
401
+
402
+ Returns:
403
+ Trend description or None if no historical data available
404
+ """
405
+ # This would require historical assessment data to implement properly
406
+ # For now, return None to indicate no trend data available
407
+ return None
408
+
409
+ def calculate_resource_count_by_status(self, ig_scores: Dict[str, IGScore]) -> Dict[str, int]:
410
+ """Calculate resource counts by compliance status across all IGs.
411
+
412
+ Args:
413
+ ig_scores: Dictionary of IG scores
414
+
415
+ Returns:
416
+ Dictionary mapping status names to resource counts
417
+ """
418
+ status_counts = defaultdict(int)
419
+
420
+ for ig_score in ig_scores.values():
421
+ for control_score in ig_score.control_scores.values():
422
+ for finding in control_score.findings:
423
+ status_counts[finding.compliance_status.value] += 1
424
+
425
+ return dict(status_counts)
426
+
427
+ def get_control_weights(self) -> Dict[str, float]:
428
+ """Get current control weights.
429
+
430
+ Returns:
431
+ Dictionary mapping control IDs to weights
432
+ """
433
+ return self.control_weights.copy()
434
+
435
+ def get_ig_weights(self) -> Dict[str, float]:
436
+ """Get current IG weights.
437
+
438
+ Returns:
439
+ Dictionary mapping IG names to weights
440
+ """
441
+ return self.ig_weights.copy()
442
+
443
+ def update_control_weights(self, new_weights: Dict[str, float]):
444
+ """Update control weights.
445
+
446
+ Args:
447
+ new_weights: Dictionary mapping control IDs to new weights
448
+ """
449
+ self.control_weights.update(new_weights)
450
+ logger.info(f"Updated control weights: {new_weights}")
451
+
452
+ def update_ig_weights(self, new_weights: Dict[str, float]):
453
+ """Update IG weights.
454
+
455
+ Args:
456
+ new_weights: Dictionary mapping IG names to new weights
457
+ """
458
+ self.ig_weights.update(new_weights)
459
+ logger.info(f"Updated IG weights: {new_weights}")
@@ -0,0 +1,8 @@
1
+ """Reporters package for CIS Controls compliance assessment reports."""
2
+
3
+ from .base_reporter import ReportGenerator, ReportTemplateEngine
4
+ from .json_reporter import JSONReporter
5
+ from .html_reporter import HTMLReporter
6
+ from .csv_reporter import CSVReporter
7
+
8
+ __all__ = ['ReportGenerator', 'ReportTemplateEngine', 'JSONReporter', 'HTMLReporter', 'CSVReporter']