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.
Files changed (77) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cfat/WEIGHT_CONFIG_README.md +368 -0
  3. runbooks/cfat/app.ts +27 -19
  4. runbooks/cfat/assessment/runner.py +6 -5
  5. runbooks/cfat/tests/test_weight_configuration.ts +449 -0
  6. runbooks/cfat/weight_config.ts +574 -0
  7. runbooks/common/__init__.py +26 -9
  8. runbooks/common/aws_pricing.py +1070 -105
  9. runbooks/common/date_utils.py +115 -0
  10. runbooks/common/enhanced_exception_handler.py +10 -7
  11. runbooks/common/mcp_cost_explorer_integration.py +5 -4
  12. runbooks/common/profile_utils.py +76 -115
  13. runbooks/common/rich_utils.py +3 -3
  14. runbooks/finops/dashboard_runner.py +47 -28
  15. runbooks/finops/ebs_optimizer.py +56 -9
  16. runbooks/finops/enhanced_trend_visualization.py +7 -2
  17. runbooks/finops/finops_dashboard.py +6 -5
  18. runbooks/finops/iam_guidance.py +6 -1
  19. runbooks/finops/nat_gateway_optimizer.py +46 -27
  20. runbooks/finops/tests/test_integration.py +3 -1
  21. runbooks/finops/vpc_cleanup_optimizer.py +22 -29
  22. runbooks/inventory/core/collector.py +51 -28
  23. runbooks/inventory/discovery.md +197 -247
  24. runbooks/inventory/inventory_modules.py +2 -2
  25. runbooks/inventory/list_ec2_instances.py +3 -3
  26. runbooks/inventory/organizations_discovery.py +13 -8
  27. runbooks/inventory/unified_validation_engine.py +2 -15
  28. runbooks/main.py +74 -32
  29. runbooks/operate/base.py +9 -6
  30. runbooks/operate/deployment_framework.py +5 -4
  31. runbooks/operate/deployment_validator.py +6 -5
  32. runbooks/operate/mcp_integration.py +6 -5
  33. runbooks/operate/networking_cost_heatmap.py +17 -13
  34. runbooks/operate/vpc_operations.py +52 -12
  35. runbooks/remediation/base.py +3 -1
  36. runbooks/remediation/commons.py +5 -5
  37. runbooks/remediation/commvault_ec2_analysis.py +66 -18
  38. runbooks/remediation/config/accounts_example.json +31 -0
  39. runbooks/remediation/multi_account.py +120 -7
  40. runbooks/remediation/remediation_cli.py +710 -0
  41. runbooks/remediation/universal_account_discovery.py +377 -0
  42. runbooks/security/compliance_automation_engine.py +99 -20
  43. runbooks/security/config/__init__.py +24 -0
  44. runbooks/security/config/compliance_config.py +255 -0
  45. runbooks/security/config/compliance_weights_example.json +22 -0
  46. runbooks/security/config_template_generator.py +500 -0
  47. runbooks/security/security_cli.py +377 -0
  48. runbooks/validation/cli.py +8 -7
  49. runbooks/validation/comprehensive_2way_validator.py +26 -15
  50. runbooks/validation/mcp_validator.py +62 -8
  51. runbooks/vpc/config.py +32 -7
  52. runbooks/vpc/cross_account_session.py +5 -1
  53. runbooks/vpc/heatmap_engine.py +21 -14
  54. runbooks/vpc/mcp_no_eni_validator.py +115 -36
  55. runbooks/vpc/runbooks_adapter.py +33 -12
  56. runbooks/vpc/tests/conftest.py +4 -2
  57. runbooks/vpc/tests/test_cost_engine.py +3 -1
  58. {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/METADATA +1 -1
  59. {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/RECORD +63 -65
  60. runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
  61. runbooks/finops/runbooks.security.report_generator.log +0 -0
  62. runbooks/finops/runbooks.security.run_script.log +0 -0
  63. runbooks/finops/runbooks.security.security_export.log +0 -0
  64. runbooks/finops/tests/results_test_finops_dashboard.xml +0 -1
  65. runbooks/inventory/artifacts/scale-optimize-status.txt +0 -12
  66. runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
  67. runbooks/inventory/runbooks.security.report_generator.log +0 -0
  68. runbooks/inventory/runbooks.security.run_script.log +0 -0
  69. runbooks/inventory/runbooks.security.security_export.log +0 -0
  70. runbooks/vpc/runbooks.inventory.organizations_discovery.log +0 -0
  71. runbooks/vpc/runbooks.security.report_generator.log +0 -0
  72. runbooks/vpc/runbooks.security.run_script.log +0 -0
  73. runbooks/vpc/runbooks.security.security_export.log +0 -0
  74. {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/WHEEL +0 -0
  75. {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/entry_points.txt +0 -0
  76. {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/licenses/LICENSE +0 -0
  77. {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
- # Enterprise 4-Profile AWS SSO Architecture (Proven FinOps Success Pattern)
65
+ # Universal AWS Environment Profile Support (Compatible with ANY AWS Setup)
66
+ import os
66
67
  ENTERPRISE_PROFILES = {
67
- "BILLING_PROFILE": "ams-admin-Billing-ReadOnlyAccess-909135376185", # Cost Explorer access
68
- "MANAGEMENT_PROFILE": "ams-admin-ReadOnlyAccess-909135376185", # Organizations access
69
- "CENTRALISED_OPS_PROFILE": "ams-centralised-ops-ReadOnlyAccess-335083429030", # Operations access
70
- "SINGLE_ACCOUNT_PROFILE": "ams-shared-services-non-prod-ReadOnlyAccess-499201730520", # Single account ops
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 = "ams-admin-ReadOnlyAccess-909135376185",
1296
- billing_profile: str = "ams-admin-Billing-ReadOnlyAccess-909135376185",
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
- # Resource weighting for enterprise accuracy calculation
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 123456789012 987654321098
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
- Multi-account AWS resource discovery and inventory.
379
+ Universal AWS resource discovery and inventory - works with ANY AWS environment.
380
380
 
381
- Read-only operations for comprehensive resource discovery across
382
- AWS services, accounts, and regions with advanced filtering.
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 --resources ec2,rds
386
- runbooks inventory collect --accounts 123456789012 --regions us-east-1
387
- runbooks inventory collect --tags Environment=prod --output json
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
- 🔍 Comprehensive AWS resource inventory collection with enhanced exports and validation.
438
+ 🔍 Universal AWS resource inventory collection - works with ANY AWS environment.
431
439
 
432
- Upgraded to match proven finops/vpc best practices:
433
- - Profile override priority (User > Environment > Default)
434
- - Multi-format exports (CSV, JSON, PDF, Markdown, YAML)
435
- - Multi-account support with --all pattern
436
- - Rich CLI integration with enterprise UX standards
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
- Export Format Precedence (following finops/vpc patterns):
441
- 1. PRIORITY: Convenience flags (--csv, --json, --pdf, --markdown)
442
- 2. FALLBACK: --export-format option (when convenience flags not used)
443
- 3. Multiple formats supported simultaneously
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
- runbooks inventory collect --csv --json
447
- runbooks inventory collect --all --markdown
448
- runbooks inventory collect --profile prod --resources ec2 s3 --pdf
449
- runbooks inventory collect --all-accounts --validate --export-format csv
450
- runbooks inventory collect --resources ec2,rds,lambda --combine --pdf
451
- runbooks inventory collect --validate-all --csv --json --markdown
452
- runbooks inventory collect --profile enterprise --validate-all --export-format json
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 billing profile with Cost Explorer access (uses AWS_BILLING_PROFILE env var if not specified)')
5492
- @click.option('--management-profile', default=None, help='AWS management profile with Organizations access (uses AWS_MANAGEMENT_PROFILE env var if not specified)')
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", default="637423383469", help="Commvault backup account ID (JIRA FinOps-25)")
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 - Proven FinOps Patterns
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": "ams-admin-Billing-ReadOnlyAccess-909135376185",
35
- "MANAGEMENT_PROFILE": "ams-admin-ReadOnlyAccess-909135376185",
36
- "CENTRALISED_OPS_PROFILE": "ams-centralised-ops-ReadOnlyAccess-335083429030",
37
- "SINGLE_ACCOUNT_PROFILE": "ams-shared-services-non-prod-ReadOnlyAccess-499201730520",
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": "ams-shared-services-non-prod-ReadOnlyAccess-499201730520",
199
- "centralised_ops": "ams-centralised-ops-ReadOnlyAccess-335083429030",
200
- "billing": "ams-admin-Billing-ReadOnlyAccess-909135376185",
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": "ams-admin-Billing-ReadOnlyAccess-909135376185",
119
- "management": "ams-admin-ReadOnlyAccess-909135376185",
120
- "ops": "ams-centralised-ops-ReadOnlyAccess-335083429030",
121
- "single_account": "ams-shared-services-non-prod-ReadOnlyAccess-499201730520",
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": "ams-admin-Billing-ReadOnlyAccess-909135376185",
85
- "management": "ams-admin-ReadOnlyAccess-909135376185",
86
- "ops": "ams-centralised-ops-ReadOnlyAccess-335083429030",
87
- "single_account": "ams-shared-services-non-prod-ReadOnlyAccess-499201730520",
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 = "ams-admin-Billing-ReadOnlyAccess-909135376185"
45
- centralized_ops_profile: str = "ams-centralised-ops-ReadOnlyAccess-335083429030"
46
- single_account_profile: str = "ams-shared-services-non-prod-ReadOnlyAccess-499201730520"
47
- management_profile: str = "ams-admin-ReadOnlyAccess-909135376185"
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
- account_id = "499201730520" # Single account ID
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 simulated 60-account environment
231
+ # Default to environment-driven account simulation
230
232
  if not account_ids:
231
- account_ids = [str(100000000000 + i) for i in range(60)]
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 ($45/month awareness)
166
+ # Add cost tracking tags using dynamic pricing
167
167
  if "MonthlyCostEstimate" not in self.tags:
168
- self.tags["MonthlyCostEstimate"] = "$45"
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 ($45/month awareness)
266
- self.nat_gateway_monthly_cost = 45.0
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 ($3.60/month awareness)
269
- self.elastic_ip_monthly_cost = 3.60
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
- hourly_rate = float(price_dim.get('pricePerUnit', {}).get('USD', '0.045'))
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 from vpc cost_engine
1492
- self.nat_gateway_hourly_cost = 0.045 # $0.045/hour
1493
- self.nat_gateway_data_processing = 0.045 # $0.045/GB
1494
- self.transit_gateway_monthly_cost = 36.50
1495
- self.vpc_endpoint_hourly_cost = 0.01
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"""
@@ -47,7 +47,9 @@ All remediation operations support enterprise multi-account patterns:
47
47
 
48
48
  ```python
49
49
  # Multi-account remediation execution
50
- accounts = ["123456789012", "987654321098", "456789012345"]
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
 
@@ -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="ap-southeast-2")
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="ap-southeast-2")
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", "ap-southeast-2")
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", "ap-southeast-2")
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=[