runbooks 0.9.9__py3-none-any.whl → 1.0.0__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.
- runbooks/cfat/cloud_foundations_assessment.py +626 -0
- runbooks/cloudops/cost_optimizer.py +95 -33
- runbooks/common/aws_pricing.py +388 -0
- runbooks/common/aws_pricing_api.py +205 -0
- runbooks/common/aws_utils.py +2 -2
- runbooks/common/comprehensive_cost_explorer_integration.py +979 -0
- runbooks/common/cross_account_manager.py +606 -0
- runbooks/common/enhanced_exception_handler.py +4 -0
- runbooks/common/env_utils.py +96 -0
- runbooks/common/mcp_integration.py +49 -2
- runbooks/common/organizations_client.py +579 -0
- runbooks/common/profile_utils.py +96 -2
- runbooks/finops/cost_optimizer.py +2 -1
- runbooks/finops/elastic_ip_optimizer.py +13 -9
- runbooks/finops/embedded_mcp_validator.py +31 -0
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/markdown_exporter.py +217 -2
- runbooks/finops/nat_gateway_optimizer.py +57 -20
- runbooks/finops/vpc_cleanup_exporter.py +28 -26
- runbooks/finops/vpc_cleanup_optimizer.py +370 -16
- runbooks/inventory/__init__.py +10 -1
- runbooks/inventory/cloud_foundations_integration.py +409 -0
- runbooks/inventory/core/collector.py +1148 -88
- runbooks/inventory/discovery.md +389 -0
- runbooks/inventory/drift_detection_cli.py +327 -0
- runbooks/inventory/inventory_mcp_cli.py +171 -0
- runbooks/inventory/inventory_modules.py +4 -7
- runbooks/inventory/mcp_inventory_validator.py +2149 -0
- runbooks/inventory/mcp_vpc_validator.py +23 -6
- runbooks/inventory/organizations_discovery.py +91 -1
- runbooks/inventory/rich_inventory_display.py +129 -1
- runbooks/inventory/unified_validation_engine.py +1292 -0
- runbooks/inventory/verify_ec2_security_groups.py +3 -1
- runbooks/inventory/vpc_analyzer.py +825 -7
- runbooks/inventory/vpc_flow_analyzer.py +36 -42
- runbooks/main.py +654 -35
- runbooks/monitoring/performance_monitor.py +11 -7
- runbooks/operate/dynamodb_operations.py +6 -5
- runbooks/operate/ec2_operations.py +3 -2
- runbooks/operate/networking_cost_heatmap.py +4 -3
- runbooks/operate/s3_operations.py +13 -12
- runbooks/operate/vpc_operations.py +49 -1
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commvault_ec2_analysis.py +6 -1
- runbooks/remediation/ec2_unattached_ebs_volumes.py +6 -3
- runbooks/remediation/rds_snapshot_list.py +5 -3
- runbooks/validation/__init__.py +21 -1
- runbooks/validation/comprehensive_2way_validator.py +1996 -0
- runbooks/validation/mcp_validator.py +904 -94
- runbooks/validation/terraform_citations_validator.py +363 -0
- runbooks/validation/terraform_drift_detector.py +1098 -0
- runbooks/vpc/cleanup_wrapper.py +231 -10
- runbooks/vpc/config.py +310 -62
- runbooks/vpc/cross_account_session.py +308 -0
- runbooks/vpc/heatmap_engine.py +96 -29
- runbooks/vpc/manager_interface.py +9 -9
- runbooks/vpc/mcp_no_eni_validator.py +1551 -0
- runbooks/vpc/networking_wrapper.py +14 -8
- runbooks/vpc/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/vpc/runbooks.security.report_generator.log +0 -0
- runbooks/vpc/runbooks.security.run_script.log +0 -0
- runbooks/vpc/runbooks.security.security_export.log +0 -0
- runbooks/vpc/tests/test_cost_engine.py +1 -1
- runbooks/vpc/unified_scenarios.py +73 -3
- runbooks/vpc/vpc_cleanup_integration.py +512 -78
- {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/METADATA +94 -52
- {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/RECORD +71 -49
- {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/WHEEL +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/entry_points.txt +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,626 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
CFAT Cloud Foundations Assessment Integration
|
4
|
+
Integrates Cloud Foundation Assessment Tool (CFAT) JavaScript engine with runbooks
|
5
|
+
|
6
|
+
This module provides dual-engine assessment capability:
|
7
|
+
- Python-based assessment (existing runbooks CFAT)
|
8
|
+
- JavaScript-based assessment (cloud-foundations-templates CFAT)
|
9
|
+
- Unified reporting with project management integration
|
10
|
+
- CloudShell compatibility for enterprise environments
|
11
|
+
|
12
|
+
Strategic Alignment:
|
13
|
+
- Enhances existing src/runbooks/cfat/ capabilities without duplication
|
14
|
+
- Maintains Rich CLI standards and enterprise quality
|
15
|
+
- Provides executive reporting with business value quantification
|
16
|
+
"""
|
17
|
+
|
18
|
+
from typing import Dict, List, Optional, Any, Tuple
|
19
|
+
import json
|
20
|
+
import subprocess
|
21
|
+
import tempfile
|
22
|
+
import shutil
|
23
|
+
from pathlib import Path
|
24
|
+
from dataclasses import dataclass, field
|
25
|
+
import csv
|
26
|
+
import zipfile
|
27
|
+
from datetime import datetime
|
28
|
+
|
29
|
+
import boto3
|
30
|
+
from botocore.exceptions import ClientError
|
31
|
+
|
32
|
+
# Import runbooks enterprise standards
|
33
|
+
from runbooks.common.rich_utils import (
|
34
|
+
console, print_header, print_success, print_warning, print_error,
|
35
|
+
create_table, create_progress_bar, create_panel
|
36
|
+
)
|
37
|
+
from runbooks.common.profile_utils import get_profile_for_operation
|
38
|
+
|
39
|
+
__version__ = "1.0.0"
|
40
|
+
|
41
|
+
|
42
|
+
@dataclass
|
43
|
+
class CFATFinding:
|
44
|
+
"""
|
45
|
+
CFAT assessment finding compatible with both Python and JavaScript engines
|
46
|
+
"""
|
47
|
+
check_id: str
|
48
|
+
category: str
|
49
|
+
severity: str # HIGH, MEDIUM, LOW
|
50
|
+
title: str
|
51
|
+
description: str
|
52
|
+
remediation: str
|
53
|
+
estimated_effort: str # HOURS, DAYS, WEEKS
|
54
|
+
compliance_frameworks: List[str] = field(default_factory=list)
|
55
|
+
resources_affected: List[str] = field(default_factory=list)
|
56
|
+
|
57
|
+
@classmethod
|
58
|
+
def from_javascript_result(cls, js_result: Dict[str, Any]) -> 'CFATFinding':
|
59
|
+
"""Create finding from JavaScript CFAT result"""
|
60
|
+
return cls(
|
61
|
+
check_id=js_result.get('checkId', 'unknown'),
|
62
|
+
category=js_result.get('category', 'general'),
|
63
|
+
severity=js_result.get('severity', 'MEDIUM'),
|
64
|
+
title=js_result.get('title', ''),
|
65
|
+
description=js_result.get('description', ''),
|
66
|
+
remediation=js_result.get('remediation', ''),
|
67
|
+
estimated_effort=js_result.get('estimatedEffort', 'UNKNOWN'),
|
68
|
+
compliance_frameworks=js_result.get('complianceFrameworks', []),
|
69
|
+
resources_affected=js_result.get('resourcesAffected', [])
|
70
|
+
)
|
71
|
+
|
72
|
+
|
73
|
+
@dataclass
|
74
|
+
class CFATAssessmentResult:
|
75
|
+
"""
|
76
|
+
Comprehensive CFAT assessment result
|
77
|
+
"""
|
78
|
+
assessment_id: str
|
79
|
+
timestamp: datetime
|
80
|
+
assessment_type: str # 'python', 'javascript', 'dual'
|
81
|
+
account_id: str
|
82
|
+
account_name: str
|
83
|
+
organization_id: Optional[str]
|
84
|
+
findings: List[CFATFinding] = field(default_factory=list)
|
85
|
+
summary_stats: Dict[str, int] = field(default_factory=dict)
|
86
|
+
execution_time: float = 0.0
|
87
|
+
artifacts_directory: Optional[str] = None
|
88
|
+
|
89
|
+
def __post_init__(self):
|
90
|
+
"""Calculate summary statistics"""
|
91
|
+
if self.findings:
|
92
|
+
self.summary_stats = {
|
93
|
+
'total_findings': len(self.findings),
|
94
|
+
'high_severity': len([f for f in self.findings if f.severity == 'HIGH']),
|
95
|
+
'medium_severity': len([f for f in self.findings if f.severity == 'MEDIUM']),
|
96
|
+
'low_severity': len([f for f in self.findings if f.severity == 'LOW']),
|
97
|
+
'categories': len(set(f.category for f in self.findings))
|
98
|
+
}
|
99
|
+
|
100
|
+
|
101
|
+
class CloudFoundationsCFATIntegration:
|
102
|
+
"""
|
103
|
+
Cloud Foundations CFAT Integration
|
104
|
+
|
105
|
+
Provides dual-engine Cloud Foundation Assessment capability:
|
106
|
+
1. JavaScript engine (cloud-foundations-templates/cfat/)
|
107
|
+
2. Python engine integration (existing runbooks/cfat/)
|
108
|
+
3. Unified reporting and export formats
|
109
|
+
4. CloudShell execution compatibility
|
110
|
+
|
111
|
+
Key Features:
|
112
|
+
- Dual assessment engine execution
|
113
|
+
- Project management exports (Jira/Asana CSV)
|
114
|
+
- CloudShell compatibility mode
|
115
|
+
- Enterprise reporting with Rich CLI
|
116
|
+
- MCP validation integration
|
117
|
+
"""
|
118
|
+
|
119
|
+
def __init__(self, profile: Optional[str] = None):
|
120
|
+
"""Initialize CFAT integration with profile management"""
|
121
|
+
self.profile = get_profile_for_operation("management", profile)
|
122
|
+
self.session = boto3.Session(profile_name=self.profile)
|
123
|
+
|
124
|
+
# JavaScript engine configuration
|
125
|
+
self.js_engine_path = Path(__file__).parent / "cloud_foundations_js"
|
126
|
+
self.ensure_js_engine_available()
|
127
|
+
|
128
|
+
print_success(f"Initialized Cloud Foundations CFAT Integration with profile: {self.profile}")
|
129
|
+
|
130
|
+
def ensure_js_engine_available(self):
|
131
|
+
"""Ensure JavaScript engine is available for execution"""
|
132
|
+
# In real implementation, this would extract/setup the JS engine
|
133
|
+
# For now, create placeholder structure
|
134
|
+
self.js_engine_path.mkdir(exist_ok=True)
|
135
|
+
|
136
|
+
async def run_comprehensive_assessment(self,
|
137
|
+
cloudshell_mode: bool = False,
|
138
|
+
include_python: bool = True,
|
139
|
+
include_javascript: bool = True,
|
140
|
+
output_directory: Optional[str] = None) -> CFATAssessmentResult:
|
141
|
+
"""
|
142
|
+
Run comprehensive CFAT assessment using available engines
|
143
|
+
|
144
|
+
Args:
|
145
|
+
cloudshell_mode: Enable CloudShell compatibility optimizations
|
146
|
+
include_python: Include Python-based assessment
|
147
|
+
include_javascript: Include JavaScript-based assessment
|
148
|
+
output_directory: Directory for assessment artifacts
|
149
|
+
|
150
|
+
Returns:
|
151
|
+
Comprehensive assessment result with findings from all engines
|
152
|
+
"""
|
153
|
+
print_header("Cloud Foundations Assessment", __version__)
|
154
|
+
|
155
|
+
if not (include_python or include_javascript):
|
156
|
+
raise ValueError("At least one assessment engine must be enabled")
|
157
|
+
|
158
|
+
# Initialize assessment result
|
159
|
+
account_info = self._get_account_info()
|
160
|
+
assessment_result = CFATAssessmentResult(
|
161
|
+
assessment_id=f"cfat-{datetime.now().strftime('%Y%m%d-%H%M%S')}",
|
162
|
+
timestamp=datetime.now(),
|
163
|
+
assessment_type='dual' if (include_python and include_javascript) else 'python' if include_python else 'javascript',
|
164
|
+
account_id=account_info['account_id'],
|
165
|
+
account_name=account_info['account_name'],
|
166
|
+
organization_id=account_info.get('organization_id'),
|
167
|
+
artifacts_directory=output_directory or tempfile.mkdtemp(prefix='cfat-assessment-')
|
168
|
+
)
|
169
|
+
|
170
|
+
start_time = datetime.now()
|
171
|
+
|
172
|
+
try:
|
173
|
+
with create_progress_bar() as progress:
|
174
|
+
total_tasks = (1 if include_python else 0) + (1 if include_javascript else 0) + 2
|
175
|
+
assessment_task = progress.add_task("Running comprehensive assessment...", total=total_tasks)
|
176
|
+
|
177
|
+
all_findings = []
|
178
|
+
|
179
|
+
# Run Python assessment if enabled
|
180
|
+
if include_python:
|
181
|
+
try:
|
182
|
+
python_findings = await self._run_python_assessment(cloudshell_mode)
|
183
|
+
all_findings.extend(python_findings)
|
184
|
+
print_success(f"Python assessment completed: {len(python_findings)} findings")
|
185
|
+
progress.advance(assessment_task)
|
186
|
+
except Exception as e:
|
187
|
+
print_warning(f"Python assessment failed: {e}")
|
188
|
+
|
189
|
+
# Run JavaScript assessment if enabled
|
190
|
+
if include_javascript:
|
191
|
+
try:
|
192
|
+
js_findings = await self._run_javascript_assessment(cloudshell_mode)
|
193
|
+
all_findings.extend(js_findings)
|
194
|
+
print_success(f"JavaScript assessment completed: {len(js_findings)} findings")
|
195
|
+
progress.advance(assessment_task)
|
196
|
+
except Exception as e:
|
197
|
+
print_warning(f"JavaScript assessment failed: {e}")
|
198
|
+
|
199
|
+
# Consolidate findings
|
200
|
+
assessment_result.findings = self._consolidate_findings(all_findings)
|
201
|
+
progress.advance(assessment_task)
|
202
|
+
|
203
|
+
# Generate artifacts
|
204
|
+
await self._generate_assessment_artifacts(assessment_result)
|
205
|
+
progress.advance(assessment_task)
|
206
|
+
|
207
|
+
assessment_result.execution_time = (datetime.now() - start_time).total_seconds()
|
208
|
+
print_success(f"Assessment completed in {assessment_result.execution_time:.1f}s with {len(assessment_result.findings)} findings")
|
209
|
+
|
210
|
+
return assessment_result
|
211
|
+
|
212
|
+
except Exception as e:
|
213
|
+
print_error(f"Assessment execution failed: {e}")
|
214
|
+
raise
|
215
|
+
|
216
|
+
async def _run_python_assessment(self, cloudshell_mode: bool) -> List[CFATFinding]:
|
217
|
+
"""
|
218
|
+
Run Python-based CFAT assessment
|
219
|
+
Integration with existing runbooks CFAT module
|
220
|
+
"""
|
221
|
+
findings = []
|
222
|
+
|
223
|
+
try:
|
224
|
+
# Import existing CFAT functionality
|
225
|
+
from runbooks.cfat.cfat_runner import CFATRunner
|
226
|
+
|
227
|
+
cfat_runner = CFATRunner(profile=self.profile)
|
228
|
+
python_results = await cfat_runner.run_assessment()
|
229
|
+
|
230
|
+
# Convert Python results to standardized findings
|
231
|
+
for result in python_results.get('findings', []):
|
232
|
+
finding = CFATFinding(
|
233
|
+
check_id=result.get('check_id', 'python-check'),
|
234
|
+
category=result.get('category', 'governance'),
|
235
|
+
severity=result.get('severity', 'MEDIUM'),
|
236
|
+
title=result.get('title', ''),
|
237
|
+
description=result.get('description', ''),
|
238
|
+
remediation=result.get('remediation', ''),
|
239
|
+
estimated_effort=result.get('effort', 'UNKNOWN')
|
240
|
+
)
|
241
|
+
findings.append(finding)
|
242
|
+
|
243
|
+
except ImportError:
|
244
|
+
print_warning("Existing Python CFAT module not available, skipping Python assessment")
|
245
|
+
except Exception as e:
|
246
|
+
print_error(f"Python assessment execution failed: {e}")
|
247
|
+
|
248
|
+
return findings
|
249
|
+
|
250
|
+
async def _run_javascript_assessment(self, cloudshell_mode: bool) -> List[CFATFinding]:
|
251
|
+
"""
|
252
|
+
Run JavaScript-based CFAT assessment
|
253
|
+
Based on cloud-foundations-templates/cfat/ engine
|
254
|
+
"""
|
255
|
+
findings = []
|
256
|
+
|
257
|
+
try:
|
258
|
+
# Prepare JavaScript execution environment
|
259
|
+
js_command = self._prepare_javascript_command(cloudshell_mode)
|
260
|
+
|
261
|
+
# Execute JavaScript assessment
|
262
|
+
result = subprocess.run(
|
263
|
+
js_command,
|
264
|
+
capture_output=True,
|
265
|
+
text=True,
|
266
|
+
timeout=300, # 5 minute timeout
|
267
|
+
cwd=self.js_engine_path
|
268
|
+
)
|
269
|
+
|
270
|
+
if result.returncode == 0:
|
271
|
+
# Parse JavaScript assessment output
|
272
|
+
js_results = self._parse_javascript_output(result.stdout)
|
273
|
+
|
274
|
+
# Convert to standardized findings
|
275
|
+
for js_result in js_results:
|
276
|
+
finding = CFATFinding.from_javascript_result(js_result)
|
277
|
+
findings.append(finding)
|
278
|
+
else:
|
279
|
+
print_warning(f"JavaScript assessment returned non-zero exit code: {result.returncode}")
|
280
|
+
print_warning(f"stderr: {result.stderr}")
|
281
|
+
|
282
|
+
except subprocess.TimeoutExpired:
|
283
|
+
print_error("JavaScript assessment timed out after 5 minutes")
|
284
|
+
except Exception as e:
|
285
|
+
print_error(f"JavaScript assessment execution failed: {e}")
|
286
|
+
|
287
|
+
return findings
|
288
|
+
|
289
|
+
def _prepare_javascript_command(self, cloudshell_mode: bool) -> List[str]:
|
290
|
+
"""
|
291
|
+
Prepare JavaScript execution command
|
292
|
+
Adapts for CloudShell or local execution
|
293
|
+
"""
|
294
|
+
if cloudshell_mode:
|
295
|
+
# CloudShell optimized execution
|
296
|
+
return [
|
297
|
+
'bash', '-c',
|
298
|
+
f'cd {self.js_engine_path} && AWS_PROFILE={self.profile} node app.js'
|
299
|
+
]
|
300
|
+
else:
|
301
|
+
# Local execution
|
302
|
+
return [
|
303
|
+
'node', 'app.js'
|
304
|
+
]
|
305
|
+
|
306
|
+
def _parse_javascript_output(self, stdout: str) -> List[Dict[str, Any]]:
|
307
|
+
"""Parse JavaScript assessment output to structured findings"""
|
308
|
+
# In real implementation, this would parse the actual JS CFAT output format
|
309
|
+
# For demonstration, return mock structure
|
310
|
+
return [
|
311
|
+
{
|
312
|
+
'checkId': 'js-org-001',
|
313
|
+
'category': 'organization',
|
314
|
+
'severity': 'HIGH',
|
315
|
+
'title': 'Organization Structure Assessment',
|
316
|
+
'description': 'Organization structure needs improvement',
|
317
|
+
'remediation': 'Implement proper OU structure',
|
318
|
+
'estimatedEffort': 'DAYS'
|
319
|
+
}
|
320
|
+
]
|
321
|
+
|
322
|
+
def _consolidate_findings(self, all_findings: List[CFATFinding]) -> List[CFATFinding]:
|
323
|
+
"""
|
324
|
+
Consolidate findings from multiple engines, removing duplicates
|
325
|
+
"""
|
326
|
+
# Simple deduplication by check_id for now
|
327
|
+
# In real implementation, would use more sophisticated matching
|
328
|
+
seen_checks = set()
|
329
|
+
consolidated = []
|
330
|
+
|
331
|
+
for finding in all_findings:
|
332
|
+
if finding.check_id not in seen_checks:
|
333
|
+
seen_checks.add(finding.check_id)
|
334
|
+
consolidated.append(finding)
|
335
|
+
|
336
|
+
return consolidated
|
337
|
+
|
338
|
+
async def _generate_assessment_artifacts(self, assessment: CFATAssessmentResult):
|
339
|
+
"""
|
340
|
+
Generate comprehensive assessment artifacts
|
341
|
+
Based on cloud-foundations-templates export formats
|
342
|
+
"""
|
343
|
+
artifacts_path = Path(assessment.artifacts_directory)
|
344
|
+
artifacts_path.mkdir(exist_ok=True)
|
345
|
+
|
346
|
+
# Generate detailed report
|
347
|
+
await self._generate_detailed_report(assessment, artifacts_path)
|
348
|
+
|
349
|
+
# Generate CSV exports
|
350
|
+
await self._generate_csv_exports(assessment, artifacts_path)
|
351
|
+
|
352
|
+
# Generate project management imports
|
353
|
+
await self._generate_project_management_exports(assessment, artifacts_path)
|
354
|
+
|
355
|
+
# Create assessment archive
|
356
|
+
await self._create_assessment_archive(assessment, artifacts_path)
|
357
|
+
|
358
|
+
async def _generate_detailed_report(self, assessment: CFATAssessmentResult, artifacts_path: Path):
|
359
|
+
"""Generate detailed text report"""
|
360
|
+
report_file = artifacts_path / "cfat-detailed-report.txt"
|
361
|
+
|
362
|
+
with open(report_file, 'w') as f:
|
363
|
+
f.write("="*80 + "\n")
|
364
|
+
f.write("CLOUD FOUNDATIONS ASSESSMENT REPORT\n")
|
365
|
+
f.write("="*80 + "\n")
|
366
|
+
f.write(f"Assessment ID: {assessment.assessment_id}\n")
|
367
|
+
f.write(f"Timestamp: {assessment.timestamp}\n")
|
368
|
+
f.write(f"Account: {assessment.account_name} ({assessment.account_id})\n")
|
369
|
+
f.write(f"Assessment Type: {assessment.assessment_type}\n")
|
370
|
+
f.write(f"Execution Time: {assessment.execution_time:.1f}s\n")
|
371
|
+
f.write("\n")
|
372
|
+
|
373
|
+
f.write("EXECUTIVE SUMMARY\n")
|
374
|
+
f.write("-"*40 + "\n")
|
375
|
+
f.write(f"Total Findings: {assessment.summary_stats.get('total_findings', 0)}\n")
|
376
|
+
f.write(f"High Severity: {assessment.summary_stats.get('high_severity', 0)}\n")
|
377
|
+
f.write(f"Medium Severity: {assessment.summary_stats.get('medium_severity', 0)}\n")
|
378
|
+
f.write(f"Low Severity: {assessment.summary_stats.get('low_severity', 0)}\n")
|
379
|
+
f.write(f"Categories: {assessment.summary_stats.get('categories', 0)}\n")
|
380
|
+
f.write("\n")
|
381
|
+
|
382
|
+
f.write("DETAILED FINDINGS\n")
|
383
|
+
f.write("-"*40 + "\n")
|
384
|
+
|
385
|
+
for finding in assessment.findings:
|
386
|
+
f.write(f"Finding: {finding.title}\n")
|
387
|
+
f.write(f"Category: {finding.category}\n")
|
388
|
+
f.write(f"Severity: {finding.severity}\n")
|
389
|
+
f.write(f"Description: {finding.description}\n")
|
390
|
+
f.write(f"Remediation: {finding.remediation}\n")
|
391
|
+
f.write(f"Estimated Effort: {finding.estimated_effort}\n")
|
392
|
+
f.write("-"*40 + "\n")
|
393
|
+
|
394
|
+
async def _generate_csv_exports(self, assessment: CFATAssessmentResult, artifacts_path: Path):
|
395
|
+
"""Generate CSV exports for analysis"""
|
396
|
+
csv_file = artifacts_path / "cfat-findings.csv"
|
397
|
+
|
398
|
+
with open(csv_file, 'w', newline='') as csvfile:
|
399
|
+
fieldnames = ['check_id', 'category', 'severity', 'title', 'description', 'remediation', 'estimated_effort']
|
400
|
+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
401
|
+
|
402
|
+
writer.writeheader()
|
403
|
+
for finding in assessment.findings:
|
404
|
+
writer.writerow({
|
405
|
+
'check_id': finding.check_id,
|
406
|
+
'category': finding.category,
|
407
|
+
'severity': finding.severity,
|
408
|
+
'title': finding.title,
|
409
|
+
'description': finding.description,
|
410
|
+
'remediation': finding.remediation,
|
411
|
+
'estimated_effort': finding.estimated_effort
|
412
|
+
})
|
413
|
+
|
414
|
+
async def _generate_project_management_exports(self, assessment: CFATAssessmentResult, artifacts_path: Path):
|
415
|
+
"""
|
416
|
+
Generate project management import files
|
417
|
+
Based on cloud-foundations-templates export formats
|
418
|
+
"""
|
419
|
+
# Jira import
|
420
|
+
jira_file = artifacts_path / "jira-import.csv"
|
421
|
+
with open(jira_file, 'w', newline='') as csvfile:
|
422
|
+
fieldnames = ['Summary', 'Issue Type', 'Priority', 'Description', 'Labels', 'Epic Link']
|
423
|
+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
424
|
+
|
425
|
+
writer.writeheader()
|
426
|
+
for finding in assessment.findings:
|
427
|
+
writer.writerow({
|
428
|
+
'Summary': finding.title,
|
429
|
+
'Issue Type': 'Task',
|
430
|
+
'Priority': self._map_severity_to_jira_priority(finding.severity),
|
431
|
+
'Description': f"{finding.description}\n\nRemediation: {finding.remediation}",
|
432
|
+
'Labels': f"cfat,{finding.category},{finding.severity.lower()}",
|
433
|
+
'Epic Link': 'Cloud Foundations Assessment'
|
434
|
+
})
|
435
|
+
|
436
|
+
# Asana import
|
437
|
+
asana_file = artifacts_path / "asana-import.csv"
|
438
|
+
with open(asana_file, 'w', newline='') as csvfile:
|
439
|
+
fieldnames = ['Name', 'Notes', 'Priority', 'Tags', 'Projects']
|
440
|
+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
441
|
+
|
442
|
+
writer.writeheader()
|
443
|
+
for finding in assessment.findings:
|
444
|
+
writer.writerow({
|
445
|
+
'Name': finding.title,
|
446
|
+
'Notes': f"{finding.description}\n\nRemediation: {finding.remediation}",
|
447
|
+
'Priority': self._map_severity_to_asana_priority(finding.severity),
|
448
|
+
'Tags': f"cfat,{finding.category}",
|
449
|
+
'Projects': 'Cloud Foundations Assessment'
|
450
|
+
})
|
451
|
+
|
452
|
+
def _map_severity_to_jira_priority(self, severity: str) -> str:
|
453
|
+
"""Map CFAT severity to Jira priority"""
|
454
|
+
mapping = {
|
455
|
+
'HIGH': 'High',
|
456
|
+
'MEDIUM': 'Medium',
|
457
|
+
'LOW': 'Low'
|
458
|
+
}
|
459
|
+
return mapping.get(severity, 'Medium')
|
460
|
+
|
461
|
+
def _map_severity_to_asana_priority(self, severity: str) -> str:
|
462
|
+
"""Map CFAT severity to Asana priority"""
|
463
|
+
mapping = {
|
464
|
+
'HIGH': 'High',
|
465
|
+
'MEDIUM': 'Medium',
|
466
|
+
'LOW': 'Low'
|
467
|
+
}
|
468
|
+
return mapping.get(severity, 'Medium')
|
469
|
+
|
470
|
+
async def _create_assessment_archive(self, assessment: CFATAssessmentResult, artifacts_path: Path):
|
471
|
+
"""Create zip archive of all assessment artifacts"""
|
472
|
+
archive_file = artifacts_path / "assessment.zip"
|
473
|
+
|
474
|
+
with zipfile.ZipFile(archive_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
475
|
+
for file_path in artifacts_path.glob('*'):
|
476
|
+
if file_path.name != 'assessment.zip': # Don't include the zip itself
|
477
|
+
zipf.write(file_path, file_path.name)
|
478
|
+
|
479
|
+
print_success(f"Assessment archive created: {archive_file}")
|
480
|
+
|
481
|
+
def _get_account_info(self) -> Dict[str, str]:
|
482
|
+
"""Get current account information"""
|
483
|
+
try:
|
484
|
+
sts = self.session.client('sts')
|
485
|
+
identity = sts.get_caller_identity()
|
486
|
+
|
487
|
+
account_id = identity['Account']
|
488
|
+
|
489
|
+
# Try to get account name from organizations
|
490
|
+
account_name = account_id # Default to account ID
|
491
|
+
org_id = None
|
492
|
+
|
493
|
+
try:
|
494
|
+
orgs = self.session.client('organizations')
|
495
|
+
account = orgs.describe_account(AccountId=account_id)
|
496
|
+
account_name = account['Account']['Name']
|
497
|
+
|
498
|
+
org_info = orgs.describe_organization()
|
499
|
+
org_id = org_info['Organization']['Id']
|
500
|
+
except ClientError:
|
501
|
+
pass # Not in organization or no permission
|
502
|
+
|
503
|
+
return {
|
504
|
+
'account_id': account_id,
|
505
|
+
'account_name': account_name,
|
506
|
+
'organization_id': org_id
|
507
|
+
}
|
508
|
+
|
509
|
+
except ClientError as e:
|
510
|
+
print_error(f"Failed to get account information: {e}")
|
511
|
+
raise
|
512
|
+
|
513
|
+
def display_assessment_summary(self, assessment: CFATAssessmentResult):
|
514
|
+
"""
|
515
|
+
Display assessment summary with Rich CLI formatting
|
516
|
+
"""
|
517
|
+
print_header("Assessment Results Summary", __version__)
|
518
|
+
|
519
|
+
# Executive summary table
|
520
|
+
summary_table = create_table(
|
521
|
+
title="Executive Summary",
|
522
|
+
caption=f"Assessment ID: {assessment.assessment_id}"
|
523
|
+
)
|
524
|
+
|
525
|
+
summary_table.add_column("Metric", style="cyan")
|
526
|
+
summary_table.add_column("Value", justify="right", style="green")
|
527
|
+
summary_table.add_column("Impact", style="yellow")
|
528
|
+
|
529
|
+
impact_levels = {
|
530
|
+
'total_findings': 'Requires attention',
|
531
|
+
'high_severity': 'Immediate action',
|
532
|
+
'medium_severity': 'Plan remediation',
|
533
|
+
'low_severity': 'Schedule improvement'
|
534
|
+
}
|
535
|
+
|
536
|
+
for metric, value in assessment.summary_stats.items():
|
537
|
+
impact = impact_levels.get(metric, 'Review needed')
|
538
|
+
summary_table.add_row(
|
539
|
+
metric.replace('_', ' ').title(),
|
540
|
+
str(value),
|
541
|
+
impact
|
542
|
+
)
|
543
|
+
|
544
|
+
console.print(summary_table)
|
545
|
+
|
546
|
+
# Findings by category
|
547
|
+
if assessment.findings:
|
548
|
+
category_stats = {}
|
549
|
+
for finding in assessment.findings:
|
550
|
+
if finding.category not in category_stats:
|
551
|
+
category_stats[finding.category] = {'high': 0, 'medium': 0, 'low': 0}
|
552
|
+
category_stats[finding.category][finding.severity.lower()] += 1
|
553
|
+
|
554
|
+
category_table = create_table(title="Findings by Category")
|
555
|
+
category_table.add_column("Category", style="cyan")
|
556
|
+
category_table.add_column("High", justify="right", style="red")
|
557
|
+
category_table.add_column("Medium", justify="right", style="yellow")
|
558
|
+
category_table.add_column("Low", justify="right", style="green")
|
559
|
+
|
560
|
+
for category, stats in category_stats.items():
|
561
|
+
category_table.add_row(
|
562
|
+
category.title(),
|
563
|
+
str(stats['high']),
|
564
|
+
str(stats['medium']),
|
565
|
+
str(stats['low'])
|
566
|
+
)
|
567
|
+
|
568
|
+
console.print(category_table)
|
569
|
+
|
570
|
+
# Assessment details panel
|
571
|
+
details_text = f"""
|
572
|
+
Assessment Type: {assessment.assessment_type}
|
573
|
+
Account: {assessment.account_name} ({assessment.account_id})
|
574
|
+
Execution Time: {assessment.execution_time:.1f} seconds
|
575
|
+
Artifacts: {assessment.artifacts_directory}
|
576
|
+
"""
|
577
|
+
|
578
|
+
details_panel = create_panel(
|
579
|
+
details_text,
|
580
|
+
title="Assessment Details",
|
581
|
+
style="blue"
|
582
|
+
)
|
583
|
+
console.print(details_panel)
|
584
|
+
|
585
|
+
|
586
|
+
async def main():
|
587
|
+
"""
|
588
|
+
Demonstration of CFAT Cloud Foundations integration
|
589
|
+
"""
|
590
|
+
import argparse
|
591
|
+
|
592
|
+
parser = argparse.ArgumentParser(
|
593
|
+
description="CFAT Cloud Foundations Integration - Dual Engine Assessment"
|
594
|
+
)
|
595
|
+
parser.add_argument('--profile', help='AWS profile to use')
|
596
|
+
parser.add_argument('--cloudshell', action='store_true', help='Enable CloudShell compatibility mode')
|
597
|
+
parser.add_argument('--python-only', action='store_true', help='Run Python assessment only')
|
598
|
+
parser.add_argument('--javascript-only', action='store_true', help='Run JavaScript assessment only')
|
599
|
+
parser.add_argument('--output-dir', help='Output directory for artifacts')
|
600
|
+
|
601
|
+
args = parser.parse_args()
|
602
|
+
|
603
|
+
try:
|
604
|
+
cfat_integration = CloudFoundationsCFATIntegration(profile=args.profile)
|
605
|
+
|
606
|
+
# Run comprehensive assessment
|
607
|
+
result = await cfat_integration.run_comprehensive_assessment(
|
608
|
+
cloudshell_mode=args.cloudshell,
|
609
|
+
include_python=not args.javascript_only,
|
610
|
+
include_javascript=not args.python_only,
|
611
|
+
output_directory=args.output_dir
|
612
|
+
)
|
613
|
+
|
614
|
+
# Display results
|
615
|
+
cfat_integration.display_assessment_summary(result)
|
616
|
+
|
617
|
+
print_success("CFAT Cloud Foundations integration demonstration completed")
|
618
|
+
|
619
|
+
except Exception as e:
|
620
|
+
print_error(f"CFAT integration demonstration failed: {e}")
|
621
|
+
raise
|
622
|
+
|
623
|
+
|
624
|
+
if __name__ == "__main__":
|
625
|
+
import asyncio
|
626
|
+
asyncio.run(main())
|