aws-cis-controls-assessment 1.1.0__tar.gz → 1.1.2__tar.gz
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_controls_assessment-1.1.0/aws_cis_controls_assessment.egg-info → aws_cis_controls_assessment-1.1.2}/PKG-INFO +1 -1
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/__init__.py +1 -1
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/reporters/html_reporter.py +201 -268
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2/aws_cis_controls_assessment.egg-info}/PKG-INFO +1 -1
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/LICENSE +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/MANIFEST.in +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/README.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/cli/__init__.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/cli/examples.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/cli/main.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/cli/utils.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/config/__init__.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/config/config_loader.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/config/rules/cis_controls_ig1.yaml +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/config/rules/cis_controls_ig2.yaml +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/config/rules/cis_controls_ig3.yaml +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/__init__.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/base_control.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/__init__.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_1_1.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_2_2.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_3_3.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_3_4.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_4_1.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_access_keys.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_advanced_security.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_aws_backup_service.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_backup_recovery.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_cloudtrail_logging.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_critical_security.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_data_protection.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_iam_advanced.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_iam_governance.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_iam_policies.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_instance_optimization.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_network_enhancements.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_network_security.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_s3_enhancements.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_s3_security.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_vpc_security.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/__init__.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_3_10.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_3_11.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_4_5_6_access_configuration.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_5_2.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_8_audit_logging.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_advanced_encryption.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_aws_backup_ig2.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_codebuild_security.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_encryption_rest.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_encryption_transit.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_network_ha.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_remaining_encryption.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_remaining_rules.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_service_logging.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig3/__init__.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig3/control_12_8.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig3/control_13_1.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig3/control_3_14.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig3/control_7_1.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/__init__.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/accuracy_validator.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/assessment_engine.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/audit_trail.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/aws_client_factory.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/error_handler.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/models.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/scoring_engine.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/reporters/__init__.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/reporters/base_reporter.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/reporters/csv_reporter.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/reporters/json_reporter.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_controls_assessment.egg-info/SOURCES.txt +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_controls_assessment.egg-info/dependency_links.txt +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_controls_assessment.egg-info/entry_points.txt +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_controls_assessment.egg-info/requires.txt +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_controls_assessment.egg-info/top_level.txt +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/deprecation-package/aws_cis_assessment_deprecated/__init__.py +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/README.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/adding-aws-backup-controls.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/assessment-logic.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/cli-reference.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/config-rule-mappings.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/developer-guide.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/dual-scoring-implementation.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/html-report-improvements.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/installation.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/scoring-comparison-aws-config.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/scoring-methodology.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/troubleshooting.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/user-guide.md +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/pyproject.toml +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/pytest.ini +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/requirements.txt +0 -0
- {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-cis-controls-assessment
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Production-ready AWS CIS Controls compliance assessment framework with 145 comprehensive rules
|
|
5
5
|
Author-email: AWS CIS Assessment Team <security@example.com>
|
|
6
6
|
Maintainer-email: AWS CIS Assessment Team <security@example.com>
|
|
@@ -6,6 +6,6 @@ CIS Controls Implementation Groups (IG1, IG2, IG3). Implements 163 comprehensive
|
|
|
6
6
|
across all implementation groups for complete security compliance assessment.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
__version__ = "1.1.
|
|
9
|
+
__version__ = "1.1.2"
|
|
10
10
|
__author__ = "AWS CIS Assessment Team"
|
|
11
11
|
__description__ = "Production-ready AWS CIS Controls Compliance Assessment Framework"
|
|
@@ -274,6 +274,8 @@ class HTMLReporter(ReportGenerator):
|
|
|
274
274
|
def _generate_html_body(self, html_data: Dict[str, Any]) -> str:
|
|
275
275
|
"""Generate HTML body section with content.
|
|
276
276
|
|
|
277
|
+
Modified in v1.1.2 to remove Detailed Findings and Remediation sections.
|
|
278
|
+
|
|
277
279
|
Args:
|
|
278
280
|
html_data: Enhanced HTML report data
|
|
279
281
|
|
|
@@ -285,9 +287,7 @@ class HTMLReporter(ReportGenerator):
|
|
|
285
287
|
navigation = self._generate_navigation(html_data)
|
|
286
288
|
executive_dashboard = self._generate_executive_dashboard(html_data)
|
|
287
289
|
implementation_groups = self._generate_implementation_groups_section(html_data)
|
|
288
|
-
detailed_findings = self._generate_detailed_findings_section(html_data)
|
|
289
290
|
resource_details = self._generate_resource_details_section(html_data)
|
|
290
|
-
remediation_section = self._generate_remediation_section(html_data)
|
|
291
291
|
footer = self._generate_footer(html_data)
|
|
292
292
|
|
|
293
293
|
body_content = f"""<body>
|
|
@@ -296,9 +296,7 @@ class HTMLReporter(ReportGenerator):
|
|
|
296
296
|
{navigation}
|
|
297
297
|
{executive_dashboard}
|
|
298
298
|
{implementation_groups}
|
|
299
|
-
{detailed_findings}
|
|
300
299
|
{resource_details}
|
|
301
|
-
{remediation_section}
|
|
302
300
|
{footer}
|
|
303
301
|
</div>
|
|
304
302
|
|
|
@@ -771,6 +769,44 @@ class HTMLReporter(ReportGenerator):
|
|
|
771
769
|
background-color: #2c3e50;
|
|
772
770
|
}
|
|
773
771
|
|
|
772
|
+
/* Resource ID column width constraint - increased to 220px in v1.1.2 */
|
|
773
|
+
.resource-table td:first-child {
|
|
774
|
+
max-width: 220px;
|
|
775
|
+
overflow: hidden;
|
|
776
|
+
text-overflow: ellipsis;
|
|
777
|
+
white-space: nowrap;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
.resource-table td:first-child:hover {
|
|
781
|
+
overflow: visible;
|
|
782
|
+
white-space: normal;
|
|
783
|
+
word-wrap: break-word;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/* Resource Type column width constraint - added in v1.1.2 (reduced by 20%) */
|
|
787
|
+
.resource-table td:nth-child(2) {
|
|
788
|
+
max-width: 150px;
|
|
789
|
+
overflow: hidden;
|
|
790
|
+
text-overflow: ellipsis;
|
|
791
|
+
white-space: nowrap;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
.resource-table td:nth-child(2):hover {
|
|
795
|
+
overflow: visible;
|
|
796
|
+
white-space: normal;
|
|
797
|
+
word-wrap: break-word;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/* Visual frames around each resource row */
|
|
801
|
+
.resource-row {
|
|
802
|
+
border: 1px solid #e0e0e0;
|
|
803
|
+
border-radius: 4px;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
.resource-row:hover {
|
|
807
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
808
|
+
}
|
|
809
|
+
|
|
774
810
|
.resource-row.compliant {
|
|
775
811
|
background-color: #f8fff8;
|
|
776
812
|
}
|
|
@@ -1262,56 +1298,6 @@ class HTMLReporter(ReportGenerator):
|
|
|
1262
1298
|
return;
|
|
1263
1299
|
}}
|
|
1264
1300
|
|
|
1265
|
-
// Implementation Groups Compliance Chart
|
|
1266
|
-
const igChartCtx = document.getElementById('igComplianceChart');
|
|
1267
|
-
if (igChartCtx) {{
|
|
1268
|
-
new Chart(igChartCtx, {{
|
|
1269
|
-
type: 'doughnut',
|
|
1270
|
-
data: chartData.igCompliance,
|
|
1271
|
-
options: {{
|
|
1272
|
-
responsive: true,
|
|
1273
|
-
maintainAspectRatio: false,
|
|
1274
|
-
plugins: {{
|
|
1275
|
-
legend: {{
|
|
1276
|
-
position: 'bottom'
|
|
1277
|
-
}},
|
|
1278
|
-
title: {{
|
|
1279
|
-
display: true,
|
|
1280
|
-
text: 'Implementation Groups Compliance'
|
|
1281
|
-
}}
|
|
1282
|
-
}}
|
|
1283
|
-
}}
|
|
1284
|
-
}});
|
|
1285
|
-
}}
|
|
1286
|
-
|
|
1287
|
-
// Overall Compliance Trend Chart
|
|
1288
|
-
const trendChartCtx = document.getElementById('complianceTrendChart');
|
|
1289
|
-
if (trendChartCtx) {{
|
|
1290
|
-
new Chart(trendChartCtx, {{
|
|
1291
|
-
type: 'bar',
|
|
1292
|
-
data: chartData.complianceTrend,
|
|
1293
|
-
options: {{
|
|
1294
|
-
responsive: true,
|
|
1295
|
-
maintainAspectRatio: false,
|
|
1296
|
-
scales: {{
|
|
1297
|
-
y: {{
|
|
1298
|
-
beginAtZero: true,
|
|
1299
|
-
max: 100
|
|
1300
|
-
}}
|
|
1301
|
-
}},
|
|
1302
|
-
plugins: {{
|
|
1303
|
-
legend: {{
|
|
1304
|
-
display: false
|
|
1305
|
-
}},
|
|
1306
|
-
title: {{
|
|
1307
|
-
display: true,
|
|
1308
|
-
text: 'Compliance by Implementation Group'
|
|
1309
|
-
}}
|
|
1310
|
-
}}
|
|
1311
|
-
}}
|
|
1312
|
-
}});
|
|
1313
|
-
}}
|
|
1314
|
-
|
|
1315
1301
|
// Risk Distribution Chart
|
|
1316
1302
|
const riskChartCtx = document.getElementById('riskDistributionChart');
|
|
1317
1303
|
if (riskChartCtx) {{
|
|
@@ -1474,17 +1460,34 @@ class HTMLReporter(ReportGenerator):
|
|
|
1474
1460
|
function exportToCSV() {{
|
|
1475
1461
|
const tables = document.querySelectorAll('.findings-table');
|
|
1476
1462
|
let csvContent = '';
|
|
1463
|
+
let headersAdded = false;
|
|
1477
1464
|
|
|
1478
1465
|
tables.forEach(function(table) {{
|
|
1479
1466
|
const rows = table.querySelectorAll('tr');
|
|
1480
|
-
rows.forEach(function(row) {{
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1467
|
+
rows.forEach(function(row, index) {{
|
|
1468
|
+
// Add headers only once (from first table)
|
|
1469
|
+
if (index === 0) {{
|
|
1470
|
+
if (!headersAdded) {{
|
|
1471
|
+
const cells = row.querySelectorAll('th');
|
|
1472
|
+
if (cells.length > 0) {{
|
|
1473
|
+
const rowData = Array.from(cells).map(cell =>
|
|
1474
|
+
'"' + cell.textContent.replace(/"/g, '""') + '"'
|
|
1475
|
+
).join(',');
|
|
1476
|
+
csvContent += rowData + '\\n';
|
|
1477
|
+
headersAdded = true;
|
|
1478
|
+
}}
|
|
1479
|
+
}}
|
|
1480
|
+
}} else {{
|
|
1481
|
+
// Add data rows (skip header rows from subsequent tables)
|
|
1482
|
+
const cells = row.querySelectorAll('td');
|
|
1483
|
+
if (cells.length > 0) {{
|
|
1484
|
+
const rowData = Array.from(cells).map(cell =>
|
|
1485
|
+
'"' + cell.textContent.replace(/"/g, '""') + '"'
|
|
1486
|
+
).join(',');
|
|
1487
|
+
csvContent += rowData + '\\n';
|
|
1488
|
+
}}
|
|
1489
|
+
}}
|
|
1486
1490
|
}});
|
|
1487
|
-
csvContent += '\\n';
|
|
1488
1491
|
}});
|
|
1489
1492
|
|
|
1490
1493
|
const blob = new Blob([csvContent], {{ type: 'text/csv' }});
|
|
@@ -1496,11 +1499,12 @@ class HTMLReporter(ReportGenerator):
|
|
|
1496
1499
|
window.URL.revokeObjectURL(url);
|
|
1497
1500
|
}}
|
|
1498
1501
|
|
|
1499
|
-
// Resource filtering functionality
|
|
1502
|
+
// Resource filtering functionality (updated in v1.1.2 to support Control filter)
|
|
1500
1503
|
function filterResources() {{
|
|
1501
1504
|
const searchTerm = document.getElementById('resourceSearch').value.toLowerCase();
|
|
1502
1505
|
const statusFilter = document.getElementById('statusFilter').value;
|
|
1503
1506
|
const typeFilter = document.getElementById('typeFilter').value;
|
|
1507
|
+
const controlFilter = document.getElementById('controlFilter').value;
|
|
1504
1508
|
const rows = document.querySelectorAll('#resourceTable tbody tr');
|
|
1505
1509
|
|
|
1506
1510
|
rows.forEach(function(row) {{
|
|
@@ -1509,6 +1513,7 @@ class HTMLReporter(ReportGenerator):
|
|
|
1509
1513
|
const resourceType = cells[1].textContent;
|
|
1510
1514
|
const status = cells[3].textContent.includes('COMPLIANT') ?
|
|
1511
1515
|
(cells[3].textContent.includes('NON_COMPLIANT') ? 'NON_COMPLIANT' : 'COMPLIANT') : 'NON_COMPLIANT';
|
|
1516
|
+
const controlId = cells[4].textContent;
|
|
1512
1517
|
const evaluationReason = cells[6].textContent.toLowerCase();
|
|
1513
1518
|
|
|
1514
1519
|
const matchesSearch = resourceId.includes(searchTerm) ||
|
|
@@ -1516,8 +1521,9 @@ class HTMLReporter(ReportGenerator):
|
|
|
1516
1521
|
evaluationReason.includes(searchTerm);
|
|
1517
1522
|
const matchesStatus = !statusFilter || status === statusFilter;
|
|
1518
1523
|
const matchesType = !typeFilter || resourceType === typeFilter;
|
|
1524
|
+
const matchesControl = !controlFilter || controlId === controlFilter;
|
|
1519
1525
|
|
|
1520
|
-
row.style.display = (matchesSearch && matchesStatus && matchesType) ? '' : 'none';
|
|
1526
|
+
row.style.display = (matchesSearch && matchesStatus && matchesType && matchesControl) ? '' : 'none';
|
|
1521
1527
|
}});
|
|
1522
1528
|
}}
|
|
1523
1529
|
|
|
@@ -1547,18 +1553,74 @@ class HTMLReporter(ReportGenerator):
|
|
|
1547
1553
|
}});
|
|
1548
1554
|
}}
|
|
1549
1555
|
|
|
1550
|
-
// Export resources to CSV
|
|
1556
|
+
// Export resources to CSV (updated in v1.1.2 with ARN, aggregate filtering, and port truncation)
|
|
1551
1557
|
function exportResourcesToCSV() {{
|
|
1552
1558
|
const table = document.getElementById('resourceTable');
|
|
1553
1559
|
const rows = table.querySelectorAll('tr');
|
|
1554
1560
|
let csvContent = '';
|
|
1555
1561
|
|
|
1556
|
-
|
|
1562
|
+
// Excluded aggregate row IDs (v1.1.2)
|
|
1563
|
+
const excludedIds = ['5631', '6460', '629'];
|
|
1564
|
+
|
|
1565
|
+
// Helper function to truncate port lists (v1.1.2)
|
|
1566
|
+
function truncatePortList(text) {{
|
|
1567
|
+
// Match port list patterns like [0, 1, 2, 3, ...]
|
|
1568
|
+
const portListRegex = /\\[(\\d+(?:,\\s*\\d+)*)\\]/g;
|
|
1569
|
+
|
|
1570
|
+
return text.replace(portListRegex, function(match, ports) {{
|
|
1571
|
+
const portArray = ports.split(',').map(p => p.trim());
|
|
1572
|
+
if (portArray.length > 10) {{
|
|
1573
|
+
const truncated = portArray.slice(0, 10).join(', ');
|
|
1574
|
+
return `[${{truncated}}, ...]`;
|
|
1575
|
+
}}
|
|
1576
|
+
return match;
|
|
1577
|
+
}});
|
|
1578
|
+
}}
|
|
1579
|
+
|
|
1580
|
+
rows.forEach(function(row, index) {{
|
|
1557
1581
|
const cells = row.querySelectorAll('th, td');
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1582
|
+
|
|
1583
|
+
// Handle header row - change "Resource ID" to "Resource ARN"
|
|
1584
|
+
if (index === 0) {{
|
|
1585
|
+
const headerData = Array.from(cells).map((cell, cellIndex) => {{
|
|
1586
|
+
let headerText = cell.textContent.replace(/\\s+/g, ' ').trim();
|
|
1587
|
+
// Replace "Resource ID" with "Resource ARN" in header
|
|
1588
|
+
if (cellIndex === 0 && headerText.includes('Resource ID')) {{
|
|
1589
|
+
headerText = headerText.replace('Resource ID', 'Resource ARN');
|
|
1590
|
+
}}
|
|
1591
|
+
return '"' + headerText.replace(/"/g, '""') + '"';
|
|
1592
|
+
}}).join(',');
|
|
1593
|
+
csvContent += headerData + '\\n';
|
|
1594
|
+
}} else if (cells.length > 0) {{
|
|
1595
|
+
// Get resource ID to check if it should be excluded
|
|
1596
|
+
const resourceIdCell = cells[0];
|
|
1597
|
+
const resourceId = resourceIdCell.textContent.replace(/\\s+/g, ' ').trim();
|
|
1598
|
+
|
|
1599
|
+
// Skip aggregate rows (v1.1.2)
|
|
1600
|
+
if (excludedIds.includes(resourceId)) {{
|
|
1601
|
+
return;
|
|
1602
|
+
}}
|
|
1603
|
+
|
|
1604
|
+
// Get ARN from data attribute or fall back to resource ID
|
|
1605
|
+
const resourceArn = resourceIdCell.getAttribute('data-arn') || resourceId;
|
|
1606
|
+
|
|
1607
|
+
const rowData = Array.from(cells).map((cell, cellIndex) => {{
|
|
1608
|
+
let cellText = cell.textContent.replace(/\\s+/g, ' ').trim();
|
|
1609
|
+
|
|
1610
|
+
// Use ARN for first column instead of Resource ID (v1.1.2)
|
|
1611
|
+
if (cellIndex === 0) {{
|
|
1612
|
+
cellText = resourceArn;
|
|
1613
|
+
}}
|
|
1614
|
+
|
|
1615
|
+
// Apply port list truncation to evaluation reason column (v1.1.2)
|
|
1616
|
+
if (cellIndex === 6) {{
|
|
1617
|
+
cellText = truncatePortList(cellText);
|
|
1618
|
+
}}
|
|
1619
|
+
|
|
1620
|
+
return '"' + cellText.replace(/"/g, '""') + '"';
|
|
1621
|
+
}}).join(',');
|
|
1622
|
+
csvContent += rowData + '\\n';
|
|
1623
|
+
}}
|
|
1562
1624
|
}});
|
|
1563
1625
|
|
|
1564
1626
|
const blob = new Blob([csvContent], {{ type: 'text/csv' }});
|
|
@@ -1657,6 +1719,9 @@ class HTMLReporter(ReportGenerator):
|
|
|
1657
1719
|
def _generate_executive_dashboard(self, html_data: Dict[str, Any]) -> str:
|
|
1658
1720
|
"""Generate executive dashboard section.
|
|
1659
1721
|
|
|
1722
|
+
Modified in v1.1.1 to remove pie chart (igComplianceChart) and bar chart
|
|
1723
|
+
(complianceTrendChart), keeping only risk distribution chart.
|
|
1724
|
+
|
|
1660
1725
|
Args:
|
|
1661
1726
|
html_data: Enhanced HTML report data
|
|
1662
1727
|
|
|
@@ -1731,12 +1796,6 @@ class HTMLReporter(ReportGenerator):
|
|
|
1731
1796
|
if self.include_charts:
|
|
1732
1797
|
charts_section = f"""
|
|
1733
1798
|
<div class="charts-section">
|
|
1734
|
-
<div class="chart-container">
|
|
1735
|
-
<canvas id="igComplianceChart"></canvas>
|
|
1736
|
-
</div>
|
|
1737
|
-
<div class="chart-container">
|
|
1738
|
-
<canvas id="complianceTrendChart"></canvas>
|
|
1739
|
-
</div>
|
|
1740
1799
|
<div class="chart-container">
|
|
1741
1800
|
<canvas id="riskDistributionChart"></canvas>
|
|
1742
1801
|
</div>
|
|
@@ -1861,161 +1920,7 @@ class HTMLReporter(ReportGenerator):
|
|
|
1861
1920
|
</section>
|
|
1862
1921
|
"""
|
|
1863
1922
|
|
|
1864
|
-
def _generate_detailed_findings_section(self, html_data: Dict[str, Any]) -> str:
|
|
1865
|
-
"""Generate detailed findings section.
|
|
1866
|
-
|
|
1867
|
-
This method consolidates findings by control ID only (not by IG) to eliminate
|
|
1868
|
-
duplication. Each control appears once with IG membership indicators.
|
|
1869
|
-
|
|
1870
|
-
Args:
|
|
1871
|
-
html_data: Enhanced HTML report data
|
|
1872
|
-
|
|
1873
|
-
Returns:
|
|
1874
|
-
Detailed findings HTML as string
|
|
1875
|
-
"""
|
|
1876
|
-
# Consolidate findings by control ID (deduplicated across IGs)
|
|
1877
|
-
consolidated_findings = self._consolidate_findings_by_control(
|
|
1878
|
-
html_data.get("implementation_groups", {})
|
|
1879
|
-
)
|
|
1880
|
-
|
|
1881
|
-
findings_content = ""
|
|
1882
|
-
|
|
1883
|
-
# Generate findings grouped by control ID only (sorted alphanumerically)
|
|
1884
|
-
for control_id, control_data in consolidated_findings.items():
|
|
1885
|
-
findings = control_data.get('findings', [])
|
|
1886
|
-
|
|
1887
|
-
# Skip if no non-compliant findings
|
|
1888
|
-
if not findings:
|
|
1889
|
-
continue
|
|
1890
|
-
|
|
1891
|
-
# Get control metadata
|
|
1892
|
-
config_rule_name = control_data.get('config_rule_name', '')
|
|
1893
|
-
title = control_data.get('title', f'CIS Control {control_id}')
|
|
1894
|
-
member_igs = control_data.get('member_igs', [])
|
|
1895
|
-
|
|
1896
|
-
# Format display name for collapsible header
|
|
1897
|
-
display_name = self._format_control_display_name(control_id, config_rule_name, title)
|
|
1898
|
-
|
|
1899
|
-
# Generate IG membership badges
|
|
1900
|
-
ig_badges_html = ""
|
|
1901
|
-
for ig in member_igs:
|
|
1902
|
-
badge_class = self._get_ig_badge_class(ig)
|
|
1903
|
-
ig_badges_html += f'<span class="ig-membership-badge {badge_class}">{ig}</span>'
|
|
1904
|
-
|
|
1905
|
-
# Generate findings table rows
|
|
1906
|
-
findings_rows = ""
|
|
1907
|
-
for finding in findings:
|
|
1908
|
-
if finding.get("compliance_status") == "NON_COMPLIANT":
|
|
1909
|
-
findings_rows += f"""
|
|
1910
|
-
<tr>
|
|
1911
|
-
<td>{finding.get('resource_id', 'N/A')}</td>
|
|
1912
|
-
<td>{finding.get('resource_type', 'N/A')}</td>
|
|
1913
|
-
<td>{finding.get('region', 'N/A')}</td>
|
|
1914
|
-
<td><span class="badge {finding.get('compliance_status', '').lower()}">{finding.get('compliance_status', 'UNKNOWN')}</span></td>
|
|
1915
|
-
<td>{finding.get('evaluation_reason', 'N/A')}</td>
|
|
1916
|
-
<td>{finding.get('config_rule_name', config_rule_name)}</td>
|
|
1917
|
-
</tr>
|
|
1918
|
-
"""
|
|
1919
|
-
|
|
1920
|
-
# Only add control section if there are non-compliant findings
|
|
1921
|
-
if findings_rows:
|
|
1922
|
-
non_compliant_count = len([f for f in findings if f.get('compliance_status') == 'NON_COMPLIANT'])
|
|
1923
|
-
|
|
1924
|
-
findings_content += f"""
|
|
1925
|
-
<div class="control-findings-section">
|
|
1926
|
-
<button class="collapsible">
|
|
1927
|
-
<span class="control-display-name">{display_name}</span>
|
|
1928
|
-
<span class="findings-count"> - Non-Compliant Resources ({non_compliant_count} items)</span>
|
|
1929
|
-
</button>
|
|
1930
|
-
<div class="collapsible-content">
|
|
1931
|
-
<div class="ig-membership-badges" style="margin-bottom: 15px;">
|
|
1932
|
-
<strong>Implementation Groups:</strong> {ig_badges_html}
|
|
1933
|
-
</div>
|
|
1934
|
-
<table class="findings-table">
|
|
1935
|
-
<thead>
|
|
1936
|
-
<tr>
|
|
1937
|
-
<th>Resource ID</th>
|
|
1938
|
-
<th>Resource Type</th>
|
|
1939
|
-
<th>Region</th>
|
|
1940
|
-
<th>Compliance Status</th>
|
|
1941
|
-
<th>Reason</th>
|
|
1942
|
-
<th>Config Rule</th>
|
|
1943
|
-
</tr>
|
|
1944
|
-
</thead>
|
|
1945
|
-
<tbody>
|
|
1946
|
-
{findings_rows}
|
|
1947
|
-
</tbody>
|
|
1948
|
-
</table>
|
|
1949
|
-
</div>
|
|
1950
|
-
</div>
|
|
1951
|
-
"""
|
|
1952
|
-
|
|
1953
|
-
return f"""
|
|
1954
|
-
<section id="detailed-findings" class="detailed-findings">
|
|
1955
|
-
<h2>Detailed Findings</h2>
|
|
1956
|
-
<p class="section-description">
|
|
1957
|
-
Findings are grouped by control ID and deduplicated across Implementation Groups.
|
|
1958
|
-
Each control shows which IGs include it.
|
|
1959
|
-
</p>
|
|
1960
|
-
<div class="search-container">
|
|
1961
|
-
<input type="text" placeholder="Search findings..." onkeyup="searchFindings(this.value)" style="width: 100%; padding: 10px; margin-bottom: 20px; border: 1px solid #ddd; border-radius: 5px;">
|
|
1962
|
-
</div>
|
|
1963
|
-
{findings_content if findings_content else '<p>No non-compliant findings to display.</p>'}
|
|
1964
|
-
</section>
|
|
1965
|
-
"""
|
|
1966
1923
|
|
|
1967
|
-
def _generate_remediation_section(self, html_data: Dict[str, Any]) -> str:
|
|
1968
|
-
"""Generate remediation section.
|
|
1969
|
-
|
|
1970
|
-
Args:
|
|
1971
|
-
html_data: Enhanced HTML report data
|
|
1972
|
-
|
|
1973
|
-
Returns:
|
|
1974
|
-
Remediation HTML as string
|
|
1975
|
-
"""
|
|
1976
|
-
remediation_items = ""
|
|
1977
|
-
|
|
1978
|
-
for remediation in html_data["remediation_priorities"]:
|
|
1979
|
-
steps_html = ""
|
|
1980
|
-
for step in remediation["remediation_steps"]:
|
|
1981
|
-
steps_html += f"<li>{step}</li>"
|
|
1982
|
-
|
|
1983
|
-
# Normalize priority and effort text to proper capitalization
|
|
1984
|
-
priority_text = remediation['priority'].capitalize()
|
|
1985
|
-
effort_text = remediation['estimated_effort'].capitalize()
|
|
1986
|
-
|
|
1987
|
-
remediation_items += f"""
|
|
1988
|
-
<div class="remediation-item">
|
|
1989
|
-
<div class="remediation-header">
|
|
1990
|
-
<div>
|
|
1991
|
-
<h4>{remediation['control_id']} - {remediation['config_rule_name']}</h4>
|
|
1992
|
-
</div>
|
|
1993
|
-
<div class="remediation-badges">
|
|
1994
|
-
<span class="badge {remediation['priority_badge']}">{priority_text}</span>
|
|
1995
|
-
<span class="badge {remediation['effort_badge']}">{effort_text}</span>
|
|
1996
|
-
</div>
|
|
1997
|
-
</div>
|
|
1998
|
-
<div class="remediation-content">
|
|
1999
|
-
<h5>Remediation Steps:</h5>
|
|
2000
|
-
<ol>
|
|
2001
|
-
{steps_html}
|
|
2002
|
-
</ol>
|
|
2003
|
-
<p><strong>Documentation:</strong> <a href="{remediation['aws_documentation_link']}" target="_blank">AWS Documentation</a></p>
|
|
2004
|
-
</div>
|
|
2005
|
-
</div>
|
|
2006
|
-
"""
|
|
2007
|
-
|
|
2008
|
-
return f"""
|
|
2009
|
-
<section id="remediation" class="remediation">
|
|
2010
|
-
<h2>Remediation Priorities</h2>
|
|
2011
|
-
<div class="remediation-list">
|
|
2012
|
-
{remediation_items}
|
|
2013
|
-
</div>
|
|
2014
|
-
<div class="export-actions">
|
|
2015
|
-
<button onclick="exportToCSV()" class="export-btn">Export Findings to CSV</button>
|
|
2016
|
-
</div>
|
|
2017
|
-
</section>
|
|
2018
|
-
"""
|
|
2019
1924
|
|
|
2020
1925
|
def _generate_footer(self, html_data: Dict[str, Any]) -> str:
|
|
2021
1926
|
"""Generate footer section.
|
|
@@ -2121,6 +2026,8 @@ class HTMLReporter(ReportGenerator):
|
|
|
2121
2026
|
def _build_navigation_structure(self, html_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
2122
2027
|
"""Build navigation structure for the report.
|
|
2123
2028
|
|
|
2029
|
+
Modified in v1.1.2 to remove Detailed Findings and Remediation sections.
|
|
2030
|
+
|
|
2124
2031
|
Args:
|
|
2125
2032
|
html_data: Enhanced HTML report data
|
|
2126
2033
|
|
|
@@ -2131,9 +2038,7 @@ class HTMLReporter(ReportGenerator):
|
|
|
2131
2038
|
"sections": [
|
|
2132
2039
|
{"id": "dashboard", "title": "Dashboard"},
|
|
2133
2040
|
{"id": "implementation-groups", "title": "Implementation Groups"},
|
|
2134
|
-
{"id": "
|
|
2135
|
-
{"id": "resource-details", "title": "Resource Details"},
|
|
2136
|
-
{"id": "remediation", "title": "Remediation"}
|
|
2041
|
+
{"id": "resource-details", "title": "Resource Details"}
|
|
2137
2042
|
]
|
|
2138
2043
|
}
|
|
2139
2044
|
|
|
@@ -2196,8 +2101,33 @@ class HTMLReporter(ReportGenerator):
|
|
|
2196
2101
|
return "low"
|
|
2197
2102
|
|
|
2198
2103
|
def _get_priority_badge(self, priority: str) -> str:
|
|
2199
|
-
"""Get priority badge class.
|
|
2200
|
-
|
|
2104
|
+
"""Get priority badge class ensuring single value.
|
|
2105
|
+
|
|
2106
|
+
Modified in v1.1.1 to normalize priority values and handle duplicates.
|
|
2107
|
+
Fixes issues like "High High" → "high" and "High Medium" → "high".
|
|
2108
|
+
|
|
2109
|
+
Args:
|
|
2110
|
+
priority: Priority string (may contain multiple values like "High High" or "High Medium")
|
|
2111
|
+
|
|
2112
|
+
Returns:
|
|
2113
|
+
Single priority class: 'high', 'medium', or 'low'
|
|
2114
|
+
"""
|
|
2115
|
+
# Extract first priority if multiple exist
|
|
2116
|
+
priority_lower = priority.lower().strip()
|
|
2117
|
+
|
|
2118
|
+
# Handle multiple priorities (take first one)
|
|
2119
|
+
if ' ' in priority_lower:
|
|
2120
|
+
priority_lower = priority_lower.split()[0]
|
|
2121
|
+
|
|
2122
|
+
# Normalize to standard values
|
|
2123
|
+
if 'high' in priority_lower:
|
|
2124
|
+
return 'high'
|
|
2125
|
+
elif 'medium' in priority_lower or 'med' in priority_lower:
|
|
2126
|
+
return 'medium'
|
|
2127
|
+
elif 'low' in priority_lower:
|
|
2128
|
+
return 'low'
|
|
2129
|
+
else:
|
|
2130
|
+
return 'medium' # Default fallback
|
|
2201
2131
|
|
|
2202
2132
|
def _get_effort_badge(self, effort: str) -> str:
|
|
2203
2133
|
"""Get effort badge class."""
|
|
@@ -2238,40 +2168,27 @@ class HTMLReporter(ReportGenerator):
|
|
|
2238
2168
|
score_diff: float) -> str:
|
|
2239
2169
|
"""Generate scoring methodology comparison section.
|
|
2240
2170
|
|
|
2171
|
+
Modified in v1.1.1 to remove "our approach" phrase, "Reflects actual security
|
|
2172
|
+
posture" text, and score difference warning for cleaner presentation.
|
|
2173
|
+
|
|
2241
2174
|
Args:
|
|
2242
|
-
weighted_score:
|
|
2175
|
+
weighted_score: Weighted compliance score
|
|
2243
2176
|
aws_config_score: AWS Config Conformance Pack style score
|
|
2244
|
-
score_diff: Difference between the two scores
|
|
2177
|
+
score_diff: Difference between the two scores (not displayed)
|
|
2245
2178
|
|
|
2246
2179
|
Returns:
|
|
2247
2180
|
HTML section comparing the two scoring approaches
|
|
2248
2181
|
"""
|
|
2249
|
-
# Determine interpretation based on difference
|
|
2250
|
-
if abs(score_diff) < 1.0:
|
|
2251
|
-
interpretation = "Both scoring approaches show similar results, indicating balanced compliance across all control priorities."
|
|
2252
|
-
icon = "ℹ️"
|
|
2253
|
-
diff_class = "neutral"
|
|
2254
|
-
elif score_diff < 0:
|
|
2255
|
-
# Weighted score is lower
|
|
2256
|
-
interpretation = f"The weighted score is {abs(score_diff):.1f}% lower, indicating critical security controls need attention despite good overall resource compliance."
|
|
2257
|
-
icon = "⚠️"
|
|
2258
|
-
diff_class = "warning"
|
|
2259
|
-
else:
|
|
2260
|
-
# Weighted score is higher
|
|
2261
|
-
interpretation = f"The weighted score is {score_diff:.1f}% higher, indicating strong performance in critical security controls despite some gaps in less critical areas."
|
|
2262
|
-
icon = "✓"
|
|
2263
|
-
diff_class = "positive"
|
|
2264
|
-
|
|
2265
2182
|
return f"""
|
|
2266
2183
|
<div class="score-comparison-section">
|
|
2267
2184
|
<h3>Scoring Methodology Comparison</h3>
|
|
2268
2185
|
<div class="comparison-grid">
|
|
2269
2186
|
<div class="comparison-card">
|
|
2270
|
-
<h4>Weighted Score
|
|
2187
|
+
<h4>Weighted Score</h4>
|
|
2271
2188
|
<div class="comparison-value">{weighted_score:.1f}%</div>
|
|
2272
2189
|
<p class="comparison-description">
|
|
2273
2190
|
Uses risk-based weighting where critical controls (encryption, access control)
|
|
2274
|
-
have higher impact on the overall score.
|
|
2191
|
+
have higher impact on the overall score.
|
|
2275
2192
|
</p>
|
|
2276
2193
|
<ul class="comparison-features">
|
|
2277
2194
|
<li>✓ Prioritizes critical security controls</li>
|
|
@@ -2294,16 +2211,6 @@ class HTMLReporter(ReportGenerator):
|
|
|
2294
2211
|
</ul>
|
|
2295
2212
|
</div>
|
|
2296
2213
|
</div>
|
|
2297
|
-
|
|
2298
|
-
<div class="score-difference {diff_class}">
|
|
2299
|
-
<span class="diff-icon">{icon}</span>
|
|
2300
|
-
<div class="diff-content">
|
|
2301
|
-
<strong>Score Difference: {score_diff:+.1f}%</strong>
|
|
2302
|
-
<p>{interpretation}</p>
|
|
2303
|
-
</div>
|
|
2304
|
-
</div>
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
2214
|
</div>
|
|
2308
2215
|
"""
|
|
2309
2216
|
|
|
@@ -2604,9 +2511,14 @@ class HTMLReporter(ReportGenerator):
|
|
|
2604
2511
|
status_class = "compliant" if resource["compliance_status"] == "COMPLIANT" else "non_compliant"
|
|
2605
2512
|
status_icon = "✓" if resource["compliance_status"] == "COMPLIANT" else "✗"
|
|
2606
2513
|
|
|
2514
|
+
# Construct pseudo-ARN for CSV export (v1.1.2)
|
|
2515
|
+
# Format: arn:aws:service:region:account:resource-type/resource-id
|
|
2516
|
+
# Since we don't have account ID in resource data, we'll use a placeholder
|
|
2517
|
+
resource_arn = f"arn:aws:{resource['resource_type'].split('::')[1].lower() if '::' in resource['resource_type'] else 'unknown'}:{resource['region']}:*:{resource['resource_id']}"
|
|
2518
|
+
|
|
2607
2519
|
resource_rows += f"""
|
|
2608
2520
|
<tr class="resource-row {status_class}">
|
|
2609
|
-
<td><code>{resource['resource_id']}</code></td>
|
|
2521
|
+
<td data-arn="{resource_arn}"><code>{resource['resource_id']}</code></td>
|
|
2610
2522
|
<td>{resource['resource_type']}</td>
|
|
2611
2523
|
<td>{resource['region']}</td>
|
|
2612
2524
|
<td>
|
|
@@ -2626,6 +2538,12 @@ class HTMLReporter(ReportGenerator):
|
|
|
2626
2538
|
non_compliant_resources = total_resources - compliant_resources
|
|
2627
2539
|
compliance_percentage = (compliant_resources / total_resources * 100) if total_resources > 0 else 0
|
|
2628
2540
|
|
|
2541
|
+
# Extract unique Control IDs for filter dropdown (v1.1.2)
|
|
2542
|
+
unique_control_ids = sorted(set(r["control_id"] for r in all_resources), key=self._sort_control_id)
|
|
2543
|
+
control_filter_options = ""
|
|
2544
|
+
for control_id in unique_control_ids:
|
|
2545
|
+
control_filter_options += f'<option value="{control_id}">{control_id}</option>'
|
|
2546
|
+
|
|
2629
2547
|
# Generate resource type breakdown
|
|
2630
2548
|
resource_type_stats = {}
|
|
2631
2549
|
for resource in all_resources:
|
|
@@ -2699,6 +2617,10 @@ class HTMLReporter(ReportGenerator):
|
|
|
2699
2617
|
<option value="">All Types</option>
|
|
2700
2618
|
{self._generate_resource_type_options(resource_type_stats)}
|
|
2701
2619
|
</select>
|
|
2620
|
+
<select id="controlFilter" onchange="filterResources()" class="filter-select">
|
|
2621
|
+
<option value="">All Controls</option>
|
|
2622
|
+
{control_filter_options}
|
|
2623
|
+
</select>
|
|
2702
2624
|
</div>
|
|
2703
2625
|
|
|
2704
2626
|
<table class="findings-table resource-table" id="resourceTable">
|
|
@@ -2869,6 +2791,8 @@ class HTMLReporter(ReportGenerator):
|
|
|
2869
2791
|
Enriches control data with additional fields needed for improved display,
|
|
2870
2792
|
including formatted names, IG membership badges, and truncation indicators.
|
|
2871
2793
|
|
|
2794
|
+
Modified in v1.1.2 to remove duplicate Control ID prefix from display names.
|
|
2795
|
+
|
|
2872
2796
|
Args:
|
|
2873
2797
|
control_data: Existing control data dictionary
|
|
2874
2798
|
control_id: Control identifier (e.g., "1.5")
|
|
@@ -2877,7 +2801,7 @@ class HTMLReporter(ReportGenerator):
|
|
|
2877
2801
|
|
|
2878
2802
|
Returns:
|
|
2879
2803
|
Enhanced control data with additional fields:
|
|
2880
|
-
- display_name: Formatted name
|
|
2804
|
+
- display_name: Formatted name without duplicate Control ID prefix
|
|
2881
2805
|
- originating_ig: Which IG introduced this control (IG1, IG2, or IG3)
|
|
2882
2806
|
- ig_badge_class: CSS class for IG badge styling
|
|
2883
2807
|
- needs_truncation: Boolean indicating if display name exceeds 50 characters
|
|
@@ -2894,7 +2818,7 @@ class HTMLReporter(ReportGenerator):
|
|
|
2894
2818
|
Output enriched data (includes all input fields plus):
|
|
2895
2819
|
{
|
|
2896
2820
|
...original fields...,
|
|
2897
|
-
'display_name': '
|
|
2821
|
+
'display_name': 'root-account-hardware-mfa-enabled', # No "1.5:" prefix
|
|
2898
2822
|
'originating_ig': 'IG1',
|
|
2899
2823
|
'ig_badge_class': 'ig-badge-1',
|
|
2900
2824
|
'needs_truncation': False
|
|
@@ -2905,16 +2829,25 @@ class HTMLReporter(ReportGenerator):
|
|
|
2905
2829
|
- Truncation threshold is 50 characters
|
|
2906
2830
|
- Gracefully handles missing config_rule_name
|
|
2907
2831
|
- Originating IG is determined by checking IG1, IG2, IG3 in order
|
|
2832
|
+
- v1.1.2: Removes duplicate Control ID prefix from display names
|
|
2908
2833
|
"""
|
|
2909
2834
|
enriched = control_data.copy()
|
|
2910
2835
|
|
|
2911
2836
|
# Format display name
|
|
2912
|
-
|
|
2837
|
+
display_name = self._format_control_display_name(
|
|
2913
2838
|
control_id,
|
|
2914
2839
|
control_data.get('config_rule_name', ''),
|
|
2915
2840
|
control_data.get('title')
|
|
2916
2841
|
)
|
|
2917
2842
|
|
|
2843
|
+
# Remove duplicate Control ID prefix if present (v1.1.2 improvement)
|
|
2844
|
+
# Check if display_name starts with "control_id: "
|
|
2845
|
+
prefix = f"{control_id}: "
|
|
2846
|
+
if display_name.startswith(prefix):
|
|
2847
|
+
display_name = display_name[len(prefix):]
|
|
2848
|
+
|
|
2849
|
+
enriched['display_name'] = display_name
|
|
2850
|
+
|
|
2918
2851
|
# Determine originating IG (which IG introduced this control)
|
|
2919
2852
|
originating_ig = self._determine_originating_ig(control_id, all_igs)
|
|
2920
2853
|
enriched['originating_ig'] = originating_ig
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-cis-controls-assessment
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Production-ready AWS CIS Controls compliance assessment framework with 145 comprehensive rules
|
|
5
5
|
Author-email: AWS CIS Assessment Team <security@example.com>
|
|
6
6
|
Maintainer-email: AWS CIS Assessment Team <security@example.com>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/assessment-logic.md
RENAMED
|
File without changes
|
{aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/cli-reference.md
RENAMED
|
File without changes
|
{aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/config-rule-mappings.md
RENAMED
|
File without changes
|
{aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/developer-guide.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/installation.md
RENAMED
|
File without changes
|
|
File without changes
|
{aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/scoring-methodology.md
RENAMED
|
File without changes
|
{aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/troubleshooting.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|