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
@@ -2,7 +2,6 @@
2
2
  EC2 Utilization Analysis - Investigate EC2 utilization patterns for cost optimization.
3
3
 
4
4
  Business Case: Enhanced EC2 investigation framework for backup infrastructure analysis
5
- Target Account: 637423383469 (Backup infrastructure account)
6
5
  Challenge: Determine if EC2 instances are actively used for backups or idle
7
6
  Strategic Value: Infrastructure right-sizing and cost optimization through utilization analysis
8
7
  """
@@ -19,11 +18,58 @@ from ..common.rich_utils import (
19
18
  console, print_header, print_success, print_error, print_warning,
20
19
  create_table, create_progress_bar, format_cost
21
20
  )
22
- from ..common.env_utils import get_required_env_float
21
+ from ..common.profile_utils import create_operational_session
23
22
 
24
23
  logger = logging.getLogger(__name__)
25
24
 
26
25
 
26
+ def _calculate_instance_monthly_cost(instance_type: str, region: str = "us-east-1") -> float:
27
+ """
28
+ Calculate monthly cost for EC2 instance type using dynamic AWS Pricing API.
29
+
30
+ ENTERPRISE COMPLIANCE: Uses AWS Pricing API to eliminate hardcoded pricing violations.
31
+
32
+ Args:
33
+ instance_type: EC2 instance type (e.g., 't3.micro', 'm5.large')
34
+ region: AWS region for pricing lookup
35
+
36
+ Returns:
37
+ float: Monthly cost in USD from AWS Pricing API
38
+ """
39
+ from ..common.aws_pricing import get_ec2_monthly_cost
40
+ from ..common.rich_utils import console
41
+
42
+ try:
43
+ # Use dynamic AWS pricing - NO hardcoded values allowed
44
+ monthly_cost = get_ec2_monthly_cost(instance_type, region)
45
+ logger.debug(f"Dynamic pricing for {instance_type}: ${monthly_cost:.2f}/month")
46
+ return monthly_cost
47
+
48
+ except Exception as e:
49
+ console.print(f"[red]⚠ ENTERPRISE WARNING: Cannot get dynamic pricing for {instance_type}: {e}[/red]")
50
+ console.print(f"[yellow]Falling back to AWS pricing pattern calculation...[/yellow]")
51
+
52
+ # Use AWS pricing engine's fallback calculation (which uses documented AWS patterns)
53
+ from ..common.aws_pricing import get_aws_pricing_engine
54
+
55
+ try:
56
+ pricing_engine = get_aws_pricing_engine(enable_fallback=True)
57
+ result = pricing_engine.get_ec2_instance_pricing(instance_type, region)
58
+ logger.warning(f"Using fallback pricing for {instance_type}: ${result.monthly_cost:.2f}/month")
59
+ return result.monthly_cost
60
+
61
+ except Exception as fallback_error:
62
+ logger.error(f"All pricing methods failed for {instance_type}: {fallback_error}")
63
+
64
+ # Complete failure - cannot proceed without violating enterprise standards
65
+ raise RuntimeError(
66
+ f"ENTERPRISE VIOLATION: Cannot get dynamic pricing for {instance_type} "
67
+ f"in region {region}. All pricing methods failed. "
68
+ f"Hardcoded values are prohibited. "
69
+ f"Ensure AWS credentials are configured and Pricing API is accessible."
70
+ ) from e
71
+
72
+
27
73
 
28
74
 
29
75
  def calculate_ec2_cost_impact(instances_data: List[Dict]) -> Dict[str, float]:
@@ -36,23 +82,14 @@ def calculate_ec2_cost_impact(instances_data: List[Dict]) -> Dict[str, float]:
36
82
  running_instances = [i for i in instances_data if i.get("State", {}).get("Name") == "running"]
37
83
  idle_instances = [i for i in instances_data if i.get("CpuUtilization", 0) < 5.0] # <5% CPU
38
84
 
39
- # Rough cost estimation (simplified for investigation phase)
85
+ # Dynamic cost estimation using environment configuration
40
86
  estimated_monthly_cost = 0.0
41
87
  for instance in running_instances:
42
88
  instance_type = instance.get("InstanceType", "t3.micro")
43
- # Simplified cost mapping (USD/month for common instance types)
44
- cost_map = {
45
- "t3.micro": 8.47,
46
- "t3.small": 16.94,
47
- "t3.medium": 33.87,
48
- "m5.large": 70.08,
49
- "m5.xlarge": 140.16,
50
- "c5.large": 62.98,
51
- "c5.xlarge": 125.95,
52
- }
53
- # Get dynamic cost estimate based on current AWS pricing - NO hardcoded defaults
54
- default_cost = get_required_env_float('DEFAULT_EC2_MONTHLY_COST')
55
- estimated_monthly_cost += cost_map.get(instance_type, default_cost)
89
+
90
+ # Dynamic cost calculation based on instance type
91
+ # Using AWS pricing calculation: hours * daily_hours * monthly_days
92
+ estimated_monthly_cost += _calculate_instance_monthly_cost(instance_type)
56
93
 
57
94
  return {
58
95
  "total_instances": total_instances,
@@ -66,7 +103,7 @@ def calculate_ec2_cost_impact(instances_data: List[Dict]) -> Dict[str, float]:
66
103
 
67
104
  @click.command()
68
105
  @click.option("--output-file", default="./tmp/commvault_ec2_investigation.csv", help="Output CSV file path")
69
- @click.option("--account", default="637423383469", help="Commvault backup account ID (JIRA FinOps-25)")
106
+ @click.option("--account", help="Commvault backup account ID (JIRA FinOps-25). If not specified, uses current AWS account.")
70
107
  @click.option("--investigate-utilization", is_flag=True, help="Investigate EC2 utilization patterns")
71
108
  @click.option("--days", default=7, help="Number of days to analyze for utilization metrics")
72
109
  @click.option("--dry-run", is_flag=True, default=True, help="Preview analysis without execution")
@@ -74,11 +111,22 @@ def investigate_commvault_ec2(output_file, account, investigate_utilization, day
74
111
  """
75
112
  FinOps-25: Commvault EC2 investigation for cost optimization.
76
113
 
77
- Account: 637423383469 (Commvault backup account)
78
114
  Challenge: Determine if EC2 instances are actively used for backups or idle
79
115
  """
80
116
  print_header("JIRA FinOps-25: Commvault EC2 Investigation", "v0.9.1")
81
117
 
118
+ # Auto-detect account if not specified
119
+ if not account:
120
+ try:
121
+ session = create_operational_session()
122
+ sts = session.client('sts')
123
+ identity = sts.get_caller_identity()
124
+ account = identity['Account']
125
+ console.print(f"[dim cyan]Auto-detected account: {account}[/dim cyan]")
126
+ except Exception as e:
127
+ console.print(f"[red]Error detecting account: {e}[/red]")
128
+ raise click.ClickException("Could not determine AWS account. Please specify --account parameter.")
129
+
82
130
  account_info = display_aws_account_info()
83
131
  console.print(f"[cyan]Account: {account} - Investigating EC2 usage patterns[/cyan]")
84
132
 
@@ -0,0 +1,31 @@
1
+ {
2
+ "target_accounts": [
3
+ {
4
+ "account_id": "111122223333",
5
+ "environment": "production",
6
+ "name": "Production Account",
7
+ "enabled": true
8
+ },
9
+ {
10
+ "account_id": "444455556666",
11
+ "environment": "staging",
12
+ "name": "Staging Account",
13
+ "enabled": true
14
+ },
15
+ {
16
+ "account_id": "777788889999",
17
+ "environment": "development",
18
+ "name": "Development Account",
19
+ "enabled": false
20
+ }
21
+ ],
22
+ "remediation_settings": {
23
+ "parallel_execution": true,
24
+ "max_workers": 5,
25
+ "timeout_seconds": 300,
26
+ "retry_attempts": 3
27
+ },
28
+ "description": "Configuration for multi-account remediation targets",
29
+ "last_updated": "2024-12-19",
30
+ "version": "1.0"
31
+ }
@@ -40,12 +40,9 @@ remediator = MultiAccountRemediator(
40
40
  max_workers=5
41
41
  )
42
42
 
43
- # Define target accounts
44
- accounts = [
45
- AWSAccount("123456789012", "production"),
46
- AWSAccount("987654321098", "staging"),
47
- AWSAccount("456789012345", "development")
48
- ]
43
+ # Define target accounts using dynamic discovery
44
+ # Example: Get accounts from AWS Organizations or environment configuration
45
+ accounts = get_accounts_from_environment() or discover_organization_accounts()
49
46
 
50
47
  # Execute bulk S3 security remediation
51
48
  results = remediator.bulk_s3_security(
@@ -73,6 +70,8 @@ from loguru import logger
73
70
 
74
71
  from runbooks.inventory.models.account import AWSAccount
75
72
  from runbooks.remediation.base import BaseRemediation, RemediationContext, RemediationResult, RemediationStatus
73
+ from runbooks.common.profile_utils import create_management_session
74
+ from runbooks.remediation.universal_account_discovery import UniversalAccountDiscovery, AWSAccount as UniversalAWSAccount
76
75
 
77
76
 
78
77
  class MultiAccountRemediator:
@@ -167,7 +166,7 @@ class MultiAccountRemediator:
167
166
 
168
167
  try:
169
168
  # Create SSO OIDC client
170
- sso_oidc = boto3.client("sso-oidc", region_name="us-east-1")
169
+ sso_oidc = boto3.client("sso-oidc", region_name=os.getenv("AWS_DEFAULT_REGION", "us-east-1"))
171
170
 
172
171
  # Register client
173
172
  client_creds = sso_oidc.register_client(clientName="CloudOpsRemediation", clientType="public")
@@ -567,3 +566,117 @@ class MultiAccountRemediator:
567
566
  compliance_summary["compliance_controls"] = list(compliance_summary["compliance_controls"])
568
567
 
569
568
  return compliance_summary
569
+
570
+
571
+ # Dynamic account discovery functions for enterprise security operations
572
+ def get_accounts_from_environment(profile: Optional[str] = None) -> Optional[List[AWSAccount]]:
573
+ """
574
+ Get AWS accounts using universal account discovery system.
575
+
576
+ Uses enhanced discovery with support for:
577
+ - Environment variables (REMEDIATION_TARGET_ACCOUNTS)
578
+ - Configuration files (REMEDIATION_ACCOUNT_CONFIG)
579
+ - AWS Organizations API (automatic discovery)
580
+ - Current account fallback (single account mode)
581
+
582
+ Args:
583
+ profile: AWS profile to use for discovery
584
+
585
+ Returns:
586
+ List of AWSAccount objects or None if not configured
587
+ """
588
+ try:
589
+ # Use universal account discovery system
590
+ discovery = UniversalAccountDiscovery(profile=profile)
591
+ universal_accounts = discovery.discover_target_accounts()
592
+
593
+ if not universal_accounts:
594
+ return None
595
+
596
+ # Convert to legacy AWSAccount format for compatibility
597
+ legacy_accounts = []
598
+ for universal_account in universal_accounts:
599
+ legacy_account = AWSAccount(
600
+ universal_account.account_id,
601
+ universal_account.account_name or f"account-{universal_account.account_id}"
602
+ )
603
+ legacy_accounts.append(legacy_account)
604
+
605
+ logger.info(f"Using {len(legacy_accounts)} accounts discovered via universal discovery system")
606
+ return legacy_accounts
607
+
608
+ except Exception as e:
609
+ logger.warning(f"Failed to discover accounts using universal discovery: {e}")
610
+ return None
611
+
612
+
613
+ def discover_organization_accounts(profile: Optional[str] = None) -> List[AWSAccount]:
614
+ """
615
+ Discover AWS accounts using universal discovery system.
616
+
617
+ Enhanced to use the universal account discovery system which provides:
618
+ - Organizations API discovery (if available)
619
+ - Environment variable fallback
620
+ - Configuration file support
621
+ - Current account fallback
622
+
623
+ Args:
624
+ profile: AWS profile for discovery (universal profile management)
625
+
626
+ Returns:
627
+ List of discovered AWSAccount objects
628
+ """
629
+ try:
630
+ # Use universal account discovery system for Organizations discovery
631
+ discovery = UniversalAccountDiscovery(profile=profile)
632
+ universal_accounts = discovery._get_accounts_from_organizations()
633
+
634
+ if not universal_accounts:
635
+ # Fallback to other discovery methods
636
+ logger.info("Organizations API not available, trying other discovery methods...")
637
+ universal_accounts = discovery.discover_target_accounts()
638
+
639
+ # Convert to legacy AWSAccount format for compatibility
640
+ legacy_accounts = []
641
+ for universal_account in universal_accounts:
642
+ if universal_account.status == "ACTIVE":
643
+ legacy_account = AWSAccount(
644
+ universal_account.account_id,
645
+ universal_account.account_name or f"org-account-{universal_account.account_id}"
646
+ )
647
+ legacy_accounts.append(legacy_account)
648
+
649
+ logger.info(f"Discovered {len(legacy_accounts)} active AWS accounts via universal discovery")
650
+ return legacy_accounts
651
+
652
+ except Exception as e:
653
+ logger.warning(f"Failed to discover organization accounts: {e}")
654
+ # Universal discovery handles all fallback scenarios
655
+ return []
656
+
657
+
658
+ def _determine_account_environment(account_name: str) -> str:
659
+ """
660
+ Determine account environment based on account name patterns.
661
+
662
+ Args:
663
+ account_name: AWS account name
664
+
665
+ Returns:
666
+ Environment classification
667
+ """
668
+ name_lower = account_name.lower()
669
+
670
+ # Common environment patterns
671
+ if any(env in name_lower for env in ["prod", "production"]):
672
+ return "production"
673
+ elif any(env in name_lower for env in ["staging", "stage", "uat"]):
674
+ return "staging"
675
+ elif any(env in name_lower for env in ["dev", "development"]):
676
+ return "development"
677
+ elif any(env in name_lower for env in ["test", "testing"]):
678
+ return "testing"
679
+ elif any(env in name_lower for env in ["sandbox", "sb"]):
680
+ return "sandbox"
681
+ else:
682
+ return "unknown"