runbooks 1.0.0__py3-none-any.whl → 1.0.1__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/__init__.py +1 -1
- runbooks/cfat/WEIGHT_CONFIG_README.md +368 -0
- runbooks/cfat/app.ts +27 -19
- runbooks/cfat/assessment/runner.py +6 -5
- runbooks/cfat/tests/test_weight_configuration.ts +449 -0
- runbooks/cfat/weight_config.ts +574 -0
- runbooks/common/__init__.py +26 -9
- runbooks/common/aws_pricing.py +1070 -105
- runbooks/common/date_utils.py +115 -0
- runbooks/common/enhanced_exception_handler.py +10 -7
- runbooks/common/mcp_cost_explorer_integration.py +5 -4
- runbooks/common/profile_utils.py +76 -115
- runbooks/common/rich_utils.py +3 -3
- runbooks/finops/dashboard_runner.py +47 -28
- runbooks/finops/ebs_optimizer.py +56 -9
- runbooks/finops/enhanced_trend_visualization.py +7 -2
- runbooks/finops/finops_dashboard.py +6 -5
- runbooks/finops/iam_guidance.py +6 -1
- runbooks/finops/nat_gateway_optimizer.py +46 -27
- runbooks/finops/tests/test_integration.py +3 -1
- runbooks/finops/vpc_cleanup_optimizer.py +22 -29
- runbooks/inventory/core/collector.py +51 -28
- runbooks/inventory/discovery.md +197 -247
- runbooks/inventory/inventory_modules.py +2 -2
- runbooks/inventory/list_ec2_instances.py +3 -3
- runbooks/inventory/organizations_discovery.py +13 -8
- runbooks/inventory/unified_validation_engine.py +2 -15
- runbooks/main.py +74 -32
- runbooks/operate/base.py +9 -6
- runbooks/operate/deployment_framework.py +5 -4
- runbooks/operate/deployment_validator.py +6 -5
- runbooks/operate/mcp_integration.py +6 -5
- runbooks/operate/networking_cost_heatmap.py +17 -13
- runbooks/operate/vpc_operations.py +52 -12
- runbooks/remediation/base.py +3 -1
- runbooks/remediation/commons.py +5 -5
- runbooks/remediation/commvault_ec2_analysis.py +66 -18
- runbooks/remediation/config/accounts_example.json +31 -0
- runbooks/remediation/multi_account.py +120 -7
- runbooks/remediation/remediation_cli.py +710 -0
- runbooks/remediation/universal_account_discovery.py +377 -0
- runbooks/security/compliance_automation_engine.py +99 -20
- runbooks/security/config/__init__.py +24 -0
- runbooks/security/config/compliance_config.py +255 -0
- runbooks/security/config/compliance_weights_example.json +22 -0
- runbooks/security/config_template_generator.py +500 -0
- runbooks/security/security_cli.py +377 -0
- runbooks/validation/cli.py +8 -7
- runbooks/validation/comprehensive_2way_validator.py +26 -15
- runbooks/validation/mcp_validator.py +62 -8
- runbooks/vpc/config.py +32 -7
- runbooks/vpc/cross_account_session.py +5 -1
- runbooks/vpc/heatmap_engine.py +21 -14
- runbooks/vpc/mcp_no_eni_validator.py +115 -36
- runbooks/vpc/runbooks_adapter.py +33 -12
- runbooks/vpc/tests/conftest.py +4 -2
- runbooks/vpc/tests/test_cost_engine.py +3 -1
- {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/METADATA +1 -1
- {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/RECORD +63 -65
- runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/finops/runbooks.security.report_generator.log +0 -0
- runbooks/finops/runbooks.security.run_script.log +0 -0
- runbooks/finops/runbooks.security.security_export.log +0 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +0 -1
- runbooks/inventory/artifacts/scale-optimize-status.txt +0 -12
- runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/runbooks.security.security_export.log +0 -0
- 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-1.0.0.dist-info → runbooks-1.0.1.dist-info}/WHEEL +0 -0
- {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/entry_points.txt +0 -0
- {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/top_level.txt +0 -0
@@ -62,12 +62,13 @@ def _set_global_organizations_cache(data):
|
|
62
62
|
accounts_count = len(data.get('accounts', {}).get('discovered_accounts', [])) if data else 0
|
63
63
|
console.print(f"[green]✅ Global Organizations cache: {accounts_count} accounts (TTL: {_GLOBAL_ORGS_CACHE['ttl_minutes']}min)[/green]")
|
64
64
|
|
65
|
-
#
|
65
|
+
# Universal AWS Environment Profile Support (Compatible with ANY AWS Setup)
|
66
|
+
import os
|
66
67
|
ENTERPRISE_PROFILES = {
|
67
|
-
"BILLING_PROFILE": "
|
68
|
-
"MANAGEMENT_PROFILE": "
|
69
|
-
"CENTRALISED_OPS_PROFILE": "
|
70
|
-
"SINGLE_ACCOUNT_PROFILE": "
|
68
|
+
"BILLING_PROFILE": os.getenv("BILLING_PROFILE", "default"), # Universal compatibility
|
69
|
+
"MANAGEMENT_PROFILE": os.getenv("MANAGEMENT_PROFILE", "default"), # Works with any profile
|
70
|
+
"CENTRALISED_OPS_PROFILE": os.getenv("CENTRALISED_OPS_PROFILE", "default"), # Universal operations
|
71
|
+
"SINGLE_ACCOUNT_PROFILE": os.getenv("SINGLE_AWS_PROFILE", "default"), # Universal single account
|
71
72
|
}
|
72
73
|
|
73
74
|
|
@@ -1290,10 +1291,10 @@ async def run_enhanced_organizations_discovery(
|
|
1290
1291
|
return org_results
|
1291
1292
|
|
1292
1293
|
|
1293
|
-
# Legacy compatibility function
|
1294
|
+
# Legacy compatibility function with universal defaults
|
1294
1295
|
async def run_organizations_discovery(
|
1295
|
-
management_profile: str =
|
1296
|
-
billing_profile: str =
|
1296
|
+
management_profile: str = None,
|
1297
|
+
billing_profile: str = None,
|
1297
1298
|
) -> Dict:
|
1298
1299
|
"""
|
1299
1300
|
Legacy compatibility function - redirects to enhanced discovery
|
@@ -1303,6 +1304,10 @@ async def run_organizations_discovery(
|
|
1303
1304
|
"""
|
1304
1305
|
console.print("[yellow]ℹ️ Using enhanced discovery engine for improved reliability and performance[/yellow]")
|
1305
1306
|
|
1307
|
+
# Apply universal environment defaults
|
1308
|
+
management_profile = management_profile or os.getenv("MANAGEMENT_PROFILE", "default-management-profile")
|
1309
|
+
billing_profile = billing_profile or os.getenv("BILLING_PROFILE", "default-billing-profile")
|
1310
|
+
|
1306
1311
|
return await run_enhanced_organizations_discovery(
|
1307
1312
|
management_profile=management_profile,
|
1308
1313
|
billing_profile=billing_profile,
|
@@ -564,21 +564,8 @@ class UnifiedValidationEngine:
|
|
564
564
|
total_weighted_accuracy = 0.0
|
565
565
|
total_weight = 0.0
|
566
566
|
|
567
|
-
#
|
568
|
-
resource_weights =
|
569
|
-
"ec2": 3.0, # High weight - critical compute resources
|
570
|
-
"vpc": 2.5, # High weight - foundational networking
|
571
|
-
"s3": 2.0, # Medium-high weight - core storage
|
572
|
-
"rds": 2.0, # Medium-high weight - critical databases
|
573
|
-
"iam": 2.0, # Medium-high weight - security foundation
|
574
|
-
"lambda": 1.5, # Medium weight - serverless compute
|
575
|
-
"elbv2": 1.5, # Medium weight - load balancing
|
576
|
-
"cloudformation": 1.0, # Medium weight - infrastructure management
|
577
|
-
"route53": 1.0, # Medium weight - DNS services
|
578
|
-
"sns": 0.8, # Lower weight - messaging
|
579
|
-
"eni": 0.8, # Lower weight - network interfaces
|
580
|
-
"ebs": 0.8, # Lower weight - block storage
|
581
|
-
}
|
567
|
+
# Dynamic resource weighting based on actual discovery for universal compatibility
|
568
|
+
resource_weights = self._calculate_dynamic_resource_weights(resource_counts)
|
582
569
|
|
583
570
|
for resource_type in self.supported_resources.keys():
|
584
571
|
runbooks_count = resource_counts["runbooks"].get(resource_type, 0)
|
runbooks/main.py
CHANGED
@@ -283,7 +283,7 @@ def common_filter_options(f):
|
|
283
283
|
Examples:
|
284
284
|
```bash
|
285
285
|
runbooks inventory ec2 --tags Environment=production Team=platform
|
286
|
-
runbooks inventory s3 --accounts
|
286
|
+
runbooks inventory s3 --accounts 111111111111 222222222222
|
287
287
|
runbooks inventory vpc --regions us-east-1 us-west-2 --tags CostCenter=engineering
|
288
288
|
```
|
289
289
|
"""
|
@@ -376,15 +376,23 @@ def main(ctx, debug, log_level, json_output, profile, region, dry_run, config):
|
|
376
376
|
@click.pass_context
|
377
377
|
def inventory(ctx, profile, region, dry_run, output, output_file, tags, accounts, regions):
|
378
378
|
"""
|
379
|
-
|
379
|
+
Universal AWS resource discovery and inventory - works with ANY AWS environment.
|
380
380
|
|
381
|
-
|
382
|
-
|
381
|
+
✅ Universal Compatibility: Works with single accounts, Organizations, and any profile setup
|
382
|
+
🔍 Read-only operations for safe resource discovery across AWS services
|
383
|
+
🚀 Intelligent fallback: Organizations → standalone account detection
|
384
|
+
|
385
|
+
Profile Options:
|
386
|
+
--profile PROFILE Use specific AWS profile (highest priority)
|
387
|
+
No --profile Uses AWS_PROFILE environment variable
|
388
|
+
No configuration Uses 'default' profile (universal AWS CLI compatibility)
|
383
389
|
|
384
390
|
Examples:
|
385
|
-
runbooks inventory collect
|
386
|
-
runbooks inventory collect --
|
387
|
-
runbooks inventory collect --
|
391
|
+
runbooks inventory collect # Use default profile
|
392
|
+
runbooks inventory collect --profile my-profile # Use specific profile
|
393
|
+
runbooks inventory collect --resources ec2,rds # Specific resources
|
394
|
+
runbooks inventory collect --all-accounts # Multi-account (if Organizations access)
|
395
|
+
runbooks inventory collect --tags Environment=prod # Filtered discovery
|
388
396
|
"""
|
389
397
|
# Update context with inventory-specific options
|
390
398
|
ctx.obj.update(
|
@@ -427,29 +435,35 @@ def inventory(ctx, profile, region, dry_run, output, output_file, tags, accounts
|
|
427
435
|
def collect(ctx, profile, region, dry_run, resources, all_resources, all_accounts, include_costs, parallel, validate, validate_all,
|
428
436
|
all, combine, csv, json, pdf, markdown, export_format, output_dir, report_name):
|
429
437
|
"""
|
430
|
-
🔍
|
438
|
+
🔍 Universal AWS resource inventory collection - works with ANY AWS environment.
|
431
439
|
|
432
|
-
|
433
|
-
-
|
434
|
-
-
|
435
|
-
-
|
436
|
-
-
|
440
|
+
✅ Universal Compatibility Features:
|
441
|
+
- Works with single accounts, AWS Organizations, and standalone setups
|
442
|
+
- Profile override priority: User > Environment > Default ('default' profile fallback)
|
443
|
+
- Intelligent Organizations detection with graceful standalone fallback
|
444
|
+
- 50+ AWS services discovery across any account configuration
|
445
|
+
- Multi-format exports: CSV, JSON, PDF, Markdown, YAML
|
437
446
|
- MCP validation for ≥99.5% accuracy
|
438
|
-
- 3-way comprehensive validation: runbooks + MCP + terraform
|
439
447
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
448
|
+
Universal Profile Usage:
|
449
|
+
- ANY AWS profile works (no hardcoded assumptions)
|
450
|
+
- Organizations permissions auto-detected (graceful fallback to single account)
|
451
|
+
- AWS_PROFILE environment variable used when available
|
452
|
+
- 'default' profile used as universal fallback
|
444
453
|
|
445
454
|
Examples:
|
446
|
-
|
447
|
-
runbooks inventory collect
|
448
|
-
runbooks inventory collect --profile
|
449
|
-
runbooks inventory collect --all-accounts
|
450
|
-
|
451
|
-
|
452
|
-
runbooks inventory collect --
|
455
|
+
# Universal compatibility - works with any AWS setup
|
456
|
+
runbooks inventory collect # Default profile
|
457
|
+
runbooks inventory collect --profile my-aws-profile # Any profile
|
458
|
+
runbooks inventory collect --all-accounts # Auto-detects Organizations
|
459
|
+
|
460
|
+
# Resource-specific discovery
|
461
|
+
runbooks inventory collect --resources ec2,rds,s3 # Specific services
|
462
|
+
runbooks inventory collect --all-resources # All 50+ services
|
463
|
+
|
464
|
+
# Multi-format exports
|
465
|
+
runbooks inventory collect --csv --json --pdf # Multiple formats
|
466
|
+
runbooks inventory collect --profile prod --validate --markdown
|
453
467
|
"""
|
454
468
|
try:
|
455
469
|
console.print(f"[blue]📊 Starting AWS Resource Inventory Collection[/blue]")
|
@@ -3674,10 +3688,25 @@ def security(ctx, profile, region, dry_run, language, output, output_file):
|
|
3674
3688
|
|
3675
3689
|
@security.command()
|
3676
3690
|
@common_aws_options
|
3691
|
+
@click.option(
|
3692
|
+
"--frameworks",
|
3693
|
+
multiple=True,
|
3694
|
+
type=click.Choice([
|
3695
|
+
"aws-well-architected",
|
3696
|
+
"soc2-type-ii",
|
3697
|
+
"pci-dss",
|
3698
|
+
"hipaa",
|
3699
|
+
"iso27001",
|
3700
|
+
"nist-cybersecurity",
|
3701
|
+
"cis-benchmarks"
|
3702
|
+
]),
|
3703
|
+
default=["aws-well-architected"],
|
3704
|
+
help="Compliance frameworks to assess (supports multiple)"
|
3705
|
+
)
|
3677
3706
|
@click.option("--checks", multiple=True, help="Specific security checks to run")
|
3678
3707
|
@click.option("--export-formats", multiple=True, default=["json", "csv"], help="Export formats (json, csv, pdf)")
|
3679
3708
|
@click.pass_context
|
3680
|
-
def assess(ctx, profile, region, dry_run, checks, export_formats):
|
3709
|
+
def assess(ctx, profile, region, dry_run, frameworks, checks, export_formats):
|
3681
3710
|
"""Run comprehensive security baseline assessment with Rich CLI output."""
|
3682
3711
|
try:
|
3683
3712
|
from runbooks.security.security_baseline_tester import SecurityBaselineTester
|
@@ -3688,16 +3717,20 @@ def assess(ctx, profile, region, dry_run, checks, export_formats):
|
|
3688
3717
|
|
3689
3718
|
console.print(f"[blue]🔒 Starting Security Assessment[/blue]")
|
3690
3719
|
console.print(
|
3691
|
-
f"[dim]Profile: {resolved_profile} | Language: {ctx.obj['language']} | Export: {', '.join(export_formats)}[/dim]"
|
3720
|
+
f"[dim]Profile: {resolved_profile} | Language: {ctx.obj['language']} | Frameworks: {', '.join(frameworks)} | Export: {', '.join(export_formats)}[/dim]"
|
3692
3721
|
)
|
3693
3722
|
|
3694
3723
|
# Initialize tester with export formats
|
3724
|
+
# TODO: Add frameworks support to SecurityBaselineTester for SOC2, PCI-DSS, HIPAA compliance
|
3695
3725
|
tester = SecurityBaselineTester(
|
3696
3726
|
profile=resolved_profile,
|
3697
3727
|
lang_code=ctx.obj["language"],
|
3698
3728
|
output_dir=ctx.obj.get("output_file"),
|
3699
3729
|
export_formats=list(export_formats),
|
3700
3730
|
)
|
3731
|
+
|
3732
|
+
# Store frameworks for future enhancement (currently using default aws-well-architected)
|
3733
|
+
console.print(f"[dim]Note: Using AWS Well-Architected baseline checks (frameworks parameter accepted for future enhancement)[/dim]")
|
3701
3734
|
|
3702
3735
|
# Run assessment with Rich CLI
|
3703
3736
|
tester.run()
|
@@ -5488,8 +5521,8 @@ def cost():
|
|
5488
5521
|
pass
|
5489
5522
|
|
5490
5523
|
@cost.command()
|
5491
|
-
@click.option('--billing-profile', default=None, help='AWS
|
5492
|
-
@click.option('--management-profile', default=None, help='AWS
|
5524
|
+
@click.option('--billing-profile', default=None, help='AWS profile for Cost Explorer access (uses universal profile selection if not specified)')
|
5525
|
+
@click.option('--management-profile', default=None, help='AWS profile for Organizations access (uses universal profile selection if not specified)')
|
5493
5526
|
@click.option('--tolerance-percent', default=5.0, help='MCP cross-validation tolerance percentage')
|
5494
5527
|
@click.option('--performance-target-ms', default=30000.0, help='Performance target in milliseconds')
|
5495
5528
|
@click.option('--export-evidence/--no-export', default=True, help='Export DoD validation evidence')
|
@@ -5935,26 +5968,35 @@ def rds_snapshots(profile, region, dry_run, manual_only, older_than, calculate_s
|
|
5935
5968
|
|
5936
5969
|
@cost_optimization.command()
|
5937
5970
|
@common_aws_options
|
5938
|
-
@click.option("--account",
|
5971
|
+
@click.option("--account", help="Commvault backup account ID (JIRA FinOps-25). If not specified, uses current AWS account.")
|
5939
5972
|
@click.option("--investigate-utilization", is_flag=True, help="Investigate EC2 utilization patterns")
|
5940
5973
|
@click.option("--output-file", default="./tmp/commvault_ec2_analysis.csv", help="Output CSV file path")
|
5941
5974
|
def commvault_ec2(profile, region, dry_run, account, investigate_utilization, output_file):
|
5942
5975
|
"""
|
5943
5976
|
FinOps-25: Commvault EC2 investigation for cost optimization.
|
5944
5977
|
|
5945
|
-
Account: 637423383469 (Commvault backup account)
|
5946
5978
|
Challenge: Determine if EC2 instances are actively used for backups or idle
|
5947
5979
|
"""
|
5948
5980
|
from runbooks.remediation.commvault_ec2_analysis import investigate_commvault_ec2
|
5949
5981
|
from runbooks.common.rich_utils import console, print_header
|
5950
5982
|
|
5951
5983
|
print_header("JIRA FinOps-25: Commvault EC2 Investigation", "v0.9.1")
|
5952
|
-
console.print(f"[cyan]Account: {account} - Investigating EC2 usage patterns[/cyan]")
|
5953
5984
|
|
5954
5985
|
try:
|
5955
5986
|
# Handle profile tuple (multiple=True in common_aws_options)
|
5956
5987
|
active_profile = profile[0] if isinstance(profile, tuple) and profile else "default"
|
5957
5988
|
|
5989
|
+
# If no account specified, detect current account from profile
|
5990
|
+
if not account:
|
5991
|
+
from runbooks.common.profile_utils import create_operational_session
|
5992
|
+
session = create_operational_session(active_profile)
|
5993
|
+
sts = session.client('sts')
|
5994
|
+
identity = sts.get_caller_identity()
|
5995
|
+
account = identity['Account']
|
5996
|
+
console.print(f"[dim cyan]Auto-detected account: {account}[/dim cyan]")
|
5997
|
+
|
5998
|
+
console.print(f"[cyan]Account: {account} - Investigating EC2 usage patterns[/cyan]")
|
5999
|
+
|
5958
6000
|
# Call enhanced Commvault EC2 investigation
|
5959
6001
|
ctx = click.Context(investigate_commvault_ec2)
|
5960
6002
|
ctx.params = {
|
runbooks/operate/base.py
CHANGED
@@ -29,12 +29,15 @@ from runbooks.common.rich_utils import print_error, print_info, print_success, p
|
|
29
29
|
from runbooks.inventory.models.account import AWSAccount
|
30
30
|
from runbooks.inventory.utils.aws_helpers import aws_api_retry, get_boto3_session
|
31
31
|
|
32
|
-
# Enterprise 4-Profile Architecture -
|
32
|
+
# Enterprise 4-Profile Architecture - Universal AWS Environment Support
|
33
|
+
# Environment variable based configuration with fallback examples
|
34
|
+
import os
|
35
|
+
|
33
36
|
ENTERPRISE_PROFILES = {
|
34
|
-
"BILLING_PROFILE": "
|
35
|
-
"MANAGEMENT_PROFILE": "
|
36
|
-
"CENTRALISED_OPS_PROFILE": "
|
37
|
-
"SINGLE_ACCOUNT_PROFILE": "
|
37
|
+
"BILLING_PROFILE": os.getenv("BILLING_PROFILE", "default-billing-profile"),
|
38
|
+
"MANAGEMENT_PROFILE": os.getenv("MANAGEMENT_PROFILE", "default-management-profile"),
|
39
|
+
"CENTRALISED_OPS_PROFILE": os.getenv("CENTRALISED_OPS_PROFILE", "default-ops-profile"),
|
40
|
+
"SINGLE_ACCOUNT_PROFILE": os.getenv("SINGLE_ACCOUNT_PROFILE", "default-single-profile"),
|
38
41
|
}
|
39
42
|
|
40
43
|
# Rich console instance for consistent formatting
|
@@ -153,7 +156,7 @@ class BaseOperation(ABC):
|
|
153
156
|
else:
|
154
157
|
self.profile = profile
|
155
158
|
|
156
|
-
self.region = region or "us-east-1"
|
159
|
+
self.region = region or os.getenv("AWS_DEFAULT_REGION", "us-east-1")
|
157
160
|
self.dry_run = dry_run
|
158
161
|
self._session = None
|
159
162
|
self._clients = {}
|
@@ -24,6 +24,7 @@ Production Safety Requirements:
|
|
24
24
|
|
25
25
|
import asyncio
|
26
26
|
import json
|
27
|
+
import os
|
27
28
|
import time
|
28
29
|
from concurrent.futures import ThreadPoolExecutor
|
29
30
|
from dataclasses import dataclass, field
|
@@ -193,11 +194,11 @@ class ProductionDeploymentFramework(BaseOperation):
|
|
193
194
|
self.health_check_timeout = 10 # seconds
|
194
195
|
self.max_retries = 3
|
195
196
|
|
196
|
-
# AWS profiles for multi-account operations
|
197
|
+
# AWS profiles for multi-account operations - Universal environment support
|
197
198
|
self.aws_profiles = {
|
198
|
-
"single_account": "
|
199
|
-
"centralised_ops": "
|
200
|
-
"billing": "
|
199
|
+
"single_account": os.getenv("SINGLE_ACCOUNT_PROFILE", "default-single-profile"),
|
200
|
+
"centralised_ops": os.getenv("CENTRALISED_OPS_PROFILE", "default-ops-profile"),
|
201
|
+
"billing": os.getenv("BILLING_PROFILE", "default-billing-profile"),
|
201
202
|
}
|
202
203
|
|
203
204
|
# Deployment tracking
|
@@ -17,6 +17,7 @@ Features:
|
|
17
17
|
|
18
18
|
import asyncio
|
19
19
|
import json
|
20
|
+
import os
|
20
21
|
from dataclasses import dataclass, field
|
21
22
|
from datetime import datetime, timedelta
|
22
23
|
from pathlib import Path
|
@@ -113,12 +114,12 @@ class DeploymentValidator(BaseOperation):
|
|
113
114
|
"github": "http://localhost:8002/mcp/github",
|
114
115
|
}
|
115
116
|
|
116
|
-
# AWS profiles for multi-account validation
|
117
|
+
# AWS profiles for multi-account validation - Universal environment support
|
117
118
|
self.validation_profiles = {
|
118
|
-
"billing": "
|
119
|
-
"management": "
|
120
|
-
"ops": "
|
121
|
-
"single_account": "
|
119
|
+
"billing": os.getenv("BILLING_PROFILE", "default-billing-profile"),
|
120
|
+
"management": os.getenv("MANAGEMENT_PROFILE", "default-management-profile"),
|
121
|
+
"ops": os.getenv("CENTRALISED_OPS_PROFILE", "default-ops-profile"),
|
122
|
+
"single_account": os.getenv("SINGLE_ACCOUNT_PROFILE", "default-single-profile"),
|
122
123
|
}
|
123
124
|
|
124
125
|
logger.info(f"Deployment Validator initialized with MCP integration")
|
@@ -17,6 +17,7 @@ Features:
|
|
17
17
|
|
18
18
|
import asyncio
|
19
19
|
import json
|
20
|
+
import os
|
20
21
|
import ssl
|
21
22
|
from dataclasses import dataclass, field
|
22
23
|
from datetime import datetime, timedelta
|
@@ -79,12 +80,12 @@ class MCPIntegrationEngine:
|
|
79
80
|
"""
|
80
81
|
self.rich_console = RichConsole()
|
81
82
|
|
82
|
-
# AWS profiles for validation
|
83
|
+
# AWS profiles for validation - Universal environment support
|
83
84
|
self.aws_profiles = profiles or {
|
84
|
-
"billing": "
|
85
|
-
"management": "
|
86
|
-
"ops": "
|
87
|
-
"single_account": "
|
85
|
+
"billing": os.getenv("BILLING_PROFILE", "default-billing-profile"),
|
86
|
+
"management": os.getenv("MANAGEMENT_PROFILE", "default-management-profile"),
|
87
|
+
"ops": os.getenv("CENTRALISED_OPS_PROFILE", "default-ops-profile"),
|
88
|
+
"single_account": os.getenv("SINGLE_ACCOUNT_PROFILE", "default-single-profile"),
|
88
89
|
}
|
89
90
|
|
90
91
|
# MCP Server configurations
|
@@ -21,6 +21,7 @@ Integration Points:
|
|
21
21
|
"""
|
22
22
|
|
23
23
|
import logging
|
24
|
+
import os
|
24
25
|
from dataclasses import dataclass, field
|
25
26
|
from datetime import datetime, timedelta
|
26
27
|
from pathlib import Path
|
@@ -40,11 +41,11 @@ logger = logging.getLogger(__name__)
|
|
40
41
|
class NetworkingCostHeatMapConfig:
|
41
42
|
"""Configuration for networking cost heat map generation"""
|
42
43
|
|
43
|
-
# AWS Profiles (READ-ONLY)
|
44
|
-
billing_profile: str = "
|
45
|
-
centralized_ops_profile: str = "
|
46
|
-
single_account_profile: str = "
|
47
|
-
management_profile: str = "
|
44
|
+
# AWS Profiles (READ-ONLY) - Universal environment support using proven profile pattern
|
45
|
+
billing_profile: str = field(default_factory=lambda: os.getenv("AWS_BILLING_PROFILE") or os.getenv("BILLING_PROFILE", "default"))
|
46
|
+
centralized_ops_profile: str = field(default_factory=lambda: os.getenv("AWS_CENTRALISED_OPS_PROFILE") or os.getenv("CENTRALISED_OPS_PROFILE", "default"))
|
47
|
+
single_account_profile: str = field(default_factory=lambda: os.getenv("AWS_SINGLE_ACCOUNT_PROFILE") or os.getenv("SINGLE_AWS_PROFILE", "default"))
|
48
|
+
management_profile: str = field(default_factory=lambda: os.getenv("AWS_MANAGEMENT_PROFILE") or os.getenv("MANAGEMENT_PROFILE", "default"))
|
48
49
|
|
49
50
|
# Analysis parameters
|
50
51
|
regions: List[str] = field(
|
@@ -216,7 +217,8 @@ class NetworkingCostHeatMapOperation(BaseOperation):
|
|
216
217
|
def _generate_single_account_heat_map(self) -> Dict:
|
217
218
|
"""Generate single account heat map"""
|
218
219
|
|
219
|
-
|
220
|
+
# Use environment-driven account ID for universal compatibility
|
221
|
+
account_id = os.getenv("AWS_ACCOUNT_ID", "123456789012")
|
220
222
|
|
221
223
|
if self._cost_explorer_available():
|
222
224
|
return self._get_real_account_costs(account_id)
|
@@ -226,16 +228,18 @@ class NetworkingCostHeatMapOperation(BaseOperation):
|
|
226
228
|
def _generate_multi_account_heat_map(self, account_ids: Optional[List[str]] = None) -> Dict:
|
227
229
|
"""Generate multi-account aggregated heat map"""
|
228
230
|
|
229
|
-
# Default to
|
231
|
+
# Default to environment-driven account simulation
|
230
232
|
if not account_ids:
|
231
|
-
|
233
|
+
base_account = int(os.getenv("AWS_BASE_ACCOUNT_ID", "100000000000"))
|
234
|
+
account_count = int(os.getenv("AWS_SIMULATED_ACCOUNT_COUNT", "60"))
|
235
|
+
account_ids = [str(base_account + i) for i in range(account_count)]
|
232
236
|
|
233
|
-
# Account categories for realistic distribution
|
237
|
+
# Account categories for realistic distribution - dynamic from environment
|
234
238
|
account_categories = {
|
235
|
-
"production": {"count": 15, "cost_multiplier": 5.0},
|
236
|
-
"staging": {"count": 15, "cost_multiplier": 2.0},
|
237
|
-
"development": {"count": 20, "cost_multiplier": 1.0},
|
238
|
-
"sandbox": {"count": 10, "cost_multiplier": 0.3},
|
239
|
+
"production": {"count": int(os.getenv("AWS_PROD_ACCOUNTS", "15")), "cost_multiplier": float(os.getenv("PROD_COST_MULTIPLIER", "5.0"))},
|
240
|
+
"staging": {"count": int(os.getenv("AWS_STAGING_ACCOUNTS", "15")), "cost_multiplier": float(os.getenv("STAGING_COST_MULTIPLIER", "2.0"))},
|
241
|
+
"development": {"count": int(os.getenv("AWS_DEV_ACCOUNTS", "20")), "cost_multiplier": float(os.getenv("DEV_COST_MULTIPLIER", "1.0"))},
|
242
|
+
"sandbox": {"count": int(os.getenv("AWS_SANDBOX_ACCOUNTS", "10")), "cost_multiplier": float(os.getenv("SANDBOX_COST_MULTIPLIER", "0.3"))},
|
239
243
|
}
|
240
244
|
|
241
245
|
# Generate aggregated heat map
|
@@ -163,9 +163,14 @@ class NATGatewayConfiguration:
|
|
163
163
|
if self.tags is None:
|
164
164
|
self.tags = {}
|
165
165
|
|
166
|
-
# Add cost tracking tags
|
166
|
+
# Add cost tracking tags using dynamic pricing
|
167
167
|
if "MonthlyCostEstimate" not in self.tags:
|
168
|
-
|
168
|
+
try:
|
169
|
+
from ..common.aws_pricing import get_service_monthly_cost
|
170
|
+
monthly_cost = get_service_monthly_cost("nat_gateway", "us-east-1") # Default region
|
171
|
+
self.tags["MonthlyCostEstimate"] = f"${monthly_cost:.2f}"
|
172
|
+
except Exception:
|
173
|
+
self.tags["MonthlyCostEstimate"] = "Dynamic pricing required"
|
169
174
|
if "CostOptimizationReviewed" not in self.tags:
|
170
175
|
self.tags["CostOptimizationReviewed"] = datetime.utcnow().strftime("%Y-%m-%d")
|
171
176
|
|
@@ -262,11 +267,22 @@ class VPCOperations(BaseOperation):
|
|
262
267
|
dry_run=dry_run
|
263
268
|
)
|
264
269
|
|
265
|
-
# Cost tracking for NAT Gateways
|
266
|
-
|
270
|
+
# Cost tracking for NAT Gateways using dynamic pricing
|
271
|
+
try:
|
272
|
+
from ..common.aws_pricing import get_service_monthly_cost
|
273
|
+
self.nat_gateway_monthly_cost = get_service_monthly_cost("nat_gateway", region or "us-east-1")
|
274
|
+
logger.info(f"Dynamic NAT Gateway cost: ${self.nat_gateway_monthly_cost:.2f}/month")
|
275
|
+
except Exception as e:
|
276
|
+
logger.error(f"Cannot get dynamic NAT Gateway pricing: {e}")
|
277
|
+
raise RuntimeError("ENTERPRISE VIOLATION: Cannot proceed without dynamic NAT Gateway pricing") from e
|
267
278
|
|
268
|
-
# Cost tracking for Elastic IPs
|
269
|
-
|
279
|
+
# Cost tracking for Elastic IPs using dynamic pricing
|
280
|
+
try:
|
281
|
+
self.elastic_ip_monthly_cost = get_service_monthly_cost("elastic_ip", region or "us-east-1")
|
282
|
+
logger.info(f"Dynamic Elastic IP cost: ${self.elastic_ip_monthly_cost:.2f}/month")
|
283
|
+
except Exception as e:
|
284
|
+
logger.error(f"Cannot get dynamic Elastic IP pricing: {e}")
|
285
|
+
raise RuntimeError("ENTERPRISE VIOLATION: Cannot proceed without dynamic Elastic IP pricing") from e
|
270
286
|
|
271
287
|
# VPC module patterns integration
|
272
288
|
self.last_discovery_result = None
|
@@ -1392,7 +1408,10 @@ class VPCOperations(BaseOperation):
|
|
1392
1408
|
price_dims = term_data.get('priceDimensions', {})
|
1393
1409
|
if price_dims:
|
1394
1410
|
price_dim = list(price_dims.values())[0]
|
1395
|
-
|
1411
|
+
usd_price = price_dim.get('pricePerUnit', {}).get('USD', '0')
|
1412
|
+
if usd_price == '0' or not usd_price:
|
1413
|
+
raise ValueError("No valid pricing found in AWS response")
|
1414
|
+
hourly_rate = float(usd_price)
|
1396
1415
|
monthly_rate = hourly_rate * 24 * 30 # Convert to monthly
|
1397
1416
|
return monthly_rate
|
1398
1417
|
|
@@ -1488,11 +1507,32 @@ class EnhancedVPCNetworkingManager(BaseOperation):
|
|
1488
1507
|
self.business_recommendations = []
|
1489
1508
|
self.export_directory = Path("./tmp/manager_dashboard")
|
1490
1509
|
|
1491
|
-
# Cost model integration
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1510
|
+
# Cost model integration using dynamic AWS pricing
|
1511
|
+
try:
|
1512
|
+
from ..common.aws_pricing import get_aws_pricing_engine
|
1513
|
+
pricing_engine = get_aws_pricing_engine(enable_fallback=True)
|
1514
|
+
|
1515
|
+
# Get dynamic pricing for all VPC services
|
1516
|
+
nat_pricing = pricing_engine.get_service_pricing("nat_gateway", self.region)
|
1517
|
+
tgw_pricing = pricing_engine.get_service_pricing("transit_gateway", self.region)
|
1518
|
+
vpc_endpoint_pricing = pricing_engine.get_service_pricing("vpc_endpoint", self.region)
|
1519
|
+
|
1520
|
+
# Convert to expected units
|
1521
|
+
self.nat_gateway_hourly_cost = nat_pricing.monthly_cost / (24 * 30) # Monthly to hourly
|
1522
|
+
self.nat_gateway_data_processing = self.nat_gateway_hourly_cost # Same rate for data
|
1523
|
+
self.transit_gateway_monthly_cost = tgw_pricing.monthly_cost
|
1524
|
+
self.vpc_endpoint_hourly_cost = vpc_endpoint_pricing.monthly_cost / (24 * 30) # Monthly to hourly
|
1525
|
+
|
1526
|
+
logger.info(f"Dynamic VPC pricing loaded: NAT=${self.nat_gateway_hourly_cost:.4f}/hr, "
|
1527
|
+
f"TGW=${self.transit_gateway_monthly_cost:.2f}/mo, VPCEndpoint=${self.vpc_endpoint_hourly_cost:.4f}/hr")
|
1528
|
+
|
1529
|
+
except Exception as e:
|
1530
|
+
logger.error(f"ENTERPRISE VIOLATION: Cannot get dynamic VPC pricing: {e}")
|
1531
|
+
raise RuntimeError(
|
1532
|
+
f"ENTERPRISE VIOLATION: Cannot proceed without dynamic pricing for VPC services "
|
1533
|
+
f"in region {self.region}. Hardcoded values are prohibited. "
|
1534
|
+
f"Ensure AWS credentials are configured and Pricing API is accessible."
|
1535
|
+
) from e
|
1496
1536
|
|
1497
1537
|
def execute_operation(self, context: OperationContext, operation_type: str, **kwargs) -> List[OperationResult]:
|
1498
1538
|
"""Enhanced VPC operations with manager interface support"""
|
runbooks/remediation/base.py
CHANGED
@@ -47,7 +47,9 @@ All remediation operations support enterprise multi-account patterns:
|
|
47
47
|
|
48
48
|
```python
|
49
49
|
# Multi-account remediation execution
|
50
|
-
|
50
|
+
# Use dynamic account discovery instead of hardcoded values
|
51
|
+
from .multi_account import discover_organization_accounts
|
52
|
+
accounts = discover_organization_accounts(profile) # Dynamic discovery
|
51
53
|
results = s3_remediation.enforce_ssl_bulk(context, accounts=accounts)
|
52
54
|
```
|
53
55
|
|
runbooks/remediation/commons.py
CHANGED
@@ -25,7 +25,7 @@ def get_all_available_aws_credentials(start_url: str = None, role_name="power-us
|
|
25
25
|
credentials = {}
|
26
26
|
|
27
27
|
# Create an SSO OIDC client
|
28
|
-
sso_oidc = boto3.client("sso-oidc", region_name="
|
28
|
+
sso_oidc = boto3.client("sso-oidc", region_name=os.getenv("AWS_DEFAULT_REGION", "us-east-1"))
|
29
29
|
|
30
30
|
try:
|
31
31
|
# Register client
|
@@ -70,7 +70,7 @@ def get_all_available_aws_credentials(start_url: str = None, role_name="power-us
|
|
70
70
|
return credentials
|
71
71
|
|
72
72
|
# Create SSO client
|
73
|
-
sso = boto3.client("sso", region_name="
|
73
|
+
sso = boto3.client("sso", region_name=os.getenv("AWS_DEFAULT_REGION", "us-east-1"))
|
74
74
|
|
75
75
|
# List accounts (with pagination)
|
76
76
|
all_accounts = []
|
@@ -165,7 +165,7 @@ def get_client(client_name: str, profile_name: str = None, region_name: str = No
|
|
165
165
|
profile_to_use = profile_name or _profile or os.environ.get("AWS_PROFILE")
|
166
166
|
|
167
167
|
# Determine the region to use
|
168
|
-
region_to_use = region_name or os.environ.get("AWS_REGION", "
|
168
|
+
region_to_use = region_name or os.environ.get("AWS_REGION", "us-east-1")
|
169
169
|
|
170
170
|
if profile_to_use:
|
171
171
|
# Use profile-based session (enterprise pattern)
|
@@ -193,7 +193,7 @@ def get_resource(client_name: str, profile_name: str = None, region_name: str =
|
|
193
193
|
profile_to_use = profile_name or _profile or os.environ.get("AWS_PROFILE")
|
194
194
|
|
195
195
|
# Determine the region to use
|
196
|
-
region_to_use = region_name or os.environ.get("AWS_REGION", "
|
196
|
+
region_to_use = region_name or os.environ.get("AWS_REGION", "us-east-1")
|
197
197
|
|
198
198
|
if profile_to_use:
|
199
199
|
# Use profile-based session (enterprise pattern)
|
@@ -372,7 +372,7 @@ def get_price(service_code, region_name, instance_type):
|
|
372
372
|
@LRU_cache(maxsize=32)
|
373
373
|
def get_product_pricing(instance_type, region_name, service_code):
|
374
374
|
# Pricing API available only in selected regions
|
375
|
-
pricing = botocore.session.get_session().create_client("pricing", region_name="us-east-1")
|
375
|
+
pricing = botocore.session.get_session().create_client("pricing", region_name=os.getenv("AWS_DEFAULT_REGION", "us-east-1"))
|
376
376
|
response = pricing.get_products(
|
377
377
|
ServiceCode=service_code,
|
378
378
|
Filters=[
|