runbooks 0.6.1__py3-none-any.whl → 0.7.5__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 (142) hide show
  1. jupyter-agent/.env +2 -0
  2. jupyter-agent/.gradio/certificate.pem +31 -0
  3. jupyter-agent/__main__.log +8 -0
  4. jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +68 -0
  5. jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +91 -0
  6. jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +91 -0
  7. jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +57 -0
  8. jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +53 -0
  9. jupyter-agent/tmp/jupyter-agent.ipynb +27 -0
  10. runbooks/__init__.py +87 -37
  11. runbooks/cfat/README.md +300 -49
  12. runbooks/cfat/__init__.py +2 -2
  13. runbooks/finops/README.md +337 -0
  14. runbooks/finops/__init__.py +2 -4
  15. runbooks/finops/cli.py +1 -1
  16. runbooks/inventory/aws_organization.png +0 -0
  17. runbooks/inventory/collectors/__init__.py +8 -0
  18. runbooks/inventory/collectors/aws_management.py +791 -0
  19. runbooks/inventory/collectors/aws_networking.py +3 -3
  20. runbooks/main.py +3416 -590
  21. runbooks/operate/__init__.py +207 -0
  22. runbooks/operate/base.py +311 -0
  23. runbooks/operate/cloudformation_operations.py +619 -0
  24. runbooks/operate/cloudwatch_operations.py +496 -0
  25. runbooks/operate/dynamodb_operations.py +812 -0
  26. runbooks/operate/ec2_operations.py +926 -0
  27. runbooks/operate/iam_operations.py +569 -0
  28. runbooks/operate/s3_operations.py +1211 -0
  29. runbooks/operate/tagging_operations.py +655 -0
  30. runbooks/remediation/CLAUDE.md +100 -0
  31. runbooks/remediation/DOME9.md +218 -0
  32. runbooks/remediation/README.md +26 -0
  33. runbooks/remediation/Tests/update_policy.py +74 -0
  34. runbooks/remediation/__init__.py +95 -0
  35. runbooks/remediation/acm_cert_expired_unused.py +98 -0
  36. runbooks/remediation/acm_remediation.py +875 -0
  37. runbooks/remediation/api_gateway_list.py +167 -0
  38. runbooks/remediation/base.py +643 -0
  39. runbooks/remediation/cloudtrail_remediation.py +908 -0
  40. runbooks/remediation/cloudtrail_s3_modifications.py +296 -0
  41. runbooks/remediation/cognito_active_users.py +78 -0
  42. runbooks/remediation/cognito_remediation.py +856 -0
  43. runbooks/remediation/cognito_user_password_reset.py +163 -0
  44. runbooks/remediation/commons.py +455 -0
  45. runbooks/remediation/dynamodb_optimize.py +155 -0
  46. runbooks/remediation/dynamodb_remediation.py +744 -0
  47. runbooks/remediation/dynamodb_server_side_encryption.py +108 -0
  48. runbooks/remediation/ec2_public_ips.py +134 -0
  49. runbooks/remediation/ec2_remediation.py +892 -0
  50. runbooks/remediation/ec2_subnet_disable_auto_ip_assignment.py +72 -0
  51. runbooks/remediation/ec2_unattached_ebs_volumes.py +448 -0
  52. runbooks/remediation/ec2_unused_security_groups.py +202 -0
  53. runbooks/remediation/kms_enable_key_rotation.py +651 -0
  54. runbooks/remediation/kms_remediation.py +717 -0
  55. runbooks/remediation/lambda_list.py +243 -0
  56. runbooks/remediation/lambda_remediation.py +971 -0
  57. runbooks/remediation/multi_account.py +569 -0
  58. runbooks/remediation/rds_instance_list.py +199 -0
  59. runbooks/remediation/rds_remediation.py +873 -0
  60. runbooks/remediation/rds_snapshot_list.py +192 -0
  61. runbooks/remediation/requirements.txt +118 -0
  62. runbooks/remediation/s3_block_public_access.py +159 -0
  63. runbooks/remediation/s3_bucket_public_access.py +143 -0
  64. runbooks/remediation/s3_disable_static_website_hosting.py +74 -0
  65. runbooks/remediation/s3_downloader.py +215 -0
  66. runbooks/remediation/s3_enable_access_logging.py +562 -0
  67. runbooks/remediation/s3_encryption.py +526 -0
  68. runbooks/remediation/s3_force_ssl_secure_policy.py +143 -0
  69. runbooks/remediation/s3_list.py +141 -0
  70. runbooks/remediation/s3_object_search.py +201 -0
  71. runbooks/remediation/s3_remediation.py +816 -0
  72. runbooks/remediation/scan_for_phrase.py +425 -0
  73. runbooks/remediation/workspaces_list.py +220 -0
  74. runbooks/{security_baseline → security}/README.md +191 -68
  75. runbooks/security/__init__.py +70 -0
  76. runbooks/{security_baseline → security}/security_baseline_tester.py +5 -3
  77. runbooks-0.7.5.dist-info/METADATA +606 -0
  78. {runbooks-0.6.1.dist-info → runbooks-0.7.5.dist-info}/RECORD +115 -75
  79. {runbooks-0.6.1.dist-info → runbooks-0.7.5.dist-info}/entry_points.txt +0 -1
  80. runbooks/aws/__init__.py +0 -58
  81. runbooks/aws/dynamodb_operations.py +0 -231
  82. runbooks/aws/ec2_copy_image_cross-region.py +0 -195
  83. runbooks/aws/ec2_describe_instances.py +0 -202
  84. runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
  85. runbooks/aws/ec2_run_instances.py +0 -213
  86. runbooks/aws/ec2_start_stop_instances.py +0 -212
  87. runbooks/aws/ec2_terminate_instances.py +0 -143
  88. runbooks/aws/ec2_unused_eips.py +0 -196
  89. runbooks/aws/ec2_unused_volumes.py +0 -188
  90. runbooks/aws/s3_create_bucket.py +0 -142
  91. runbooks/aws/s3_list_buckets.py +0 -152
  92. runbooks/aws/s3_list_objects.py +0 -156
  93. runbooks/aws/s3_object_operations.py +0 -183
  94. runbooks/aws/tagging_lambda_handler.py +0 -183
  95. runbooks/inventory/cfn_move_stack_instances.py +0 -1526
  96. runbooks/inventory/delete_s3_buckets_objects.py +0 -169
  97. runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
  98. runbooks/inventory/update_aws_actions.py +0 -173
  99. runbooks/inventory/update_cfn_stacksets.py +0 -1215
  100. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
  101. runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
  102. runbooks/inventory/update_s3_public_access_block.py +0 -539
  103. runbooks/organizations/__init__.py +0 -12
  104. runbooks/organizations/manager.py +0 -374
  105. runbooks/security_baseline/requirements.txt +0 -7
  106. runbooks-0.6.1.dist-info/METADATA +0 -373
  107. /runbooks/{aws → operate}/tags.json +0 -0
  108. /runbooks/{security_baseline → remediation/Tests}/__init__.py +0 -0
  109. /runbooks/{security_baseline → security}/checklist/__init__.py +0 -0
  110. /runbooks/{security_baseline → security}/checklist/account_level_bucket_public_access.py +0 -0
  111. /runbooks/{security_baseline → security}/checklist/alternate_contacts.py +0 -0
  112. /runbooks/{security_baseline → security}/checklist/bucket_public_access.py +0 -0
  113. /runbooks/{security_baseline → security}/checklist/cloudwatch_alarm_configuration.py +0 -0
  114. /runbooks/{security_baseline → security}/checklist/direct_attached_policy.py +0 -0
  115. /runbooks/{security_baseline → security}/checklist/guardduty_enabled.py +0 -0
  116. /runbooks/{security_baseline → security}/checklist/iam_password_policy.py +0 -0
  117. /runbooks/{security_baseline → security}/checklist/iam_user_mfa.py +0 -0
  118. /runbooks/{security_baseline → security}/checklist/multi_region_instance_usage.py +0 -0
  119. /runbooks/{security_baseline → security}/checklist/multi_region_trail.py +0 -0
  120. /runbooks/{security_baseline → security}/checklist/root_access_key.py +0 -0
  121. /runbooks/{security_baseline → security}/checklist/root_mfa.py +0 -0
  122. /runbooks/{security_baseline → security}/checklist/root_usage.py +0 -0
  123. /runbooks/{security_baseline → security}/checklist/trail_enabled.py +0 -0
  124. /runbooks/{security_baseline → security}/checklist/trusted_advisor.py +0 -0
  125. /runbooks/{security_baseline → security}/config-origin.json +0 -0
  126. /runbooks/{security_baseline → security}/config.json +0 -0
  127. /runbooks/{security_baseline → security}/permission.json +0 -0
  128. /runbooks/{security_baseline → security}/report_generator.py +0 -0
  129. /runbooks/{security_baseline → security}/report_template_en.html +0 -0
  130. /runbooks/{security_baseline → security}/report_template_jp.html +0 -0
  131. /runbooks/{security_baseline → security}/report_template_kr.html +0 -0
  132. /runbooks/{security_baseline → security}/report_template_vn.html +0 -0
  133. /runbooks/{security_baseline → security}/run_script.py +0 -0
  134. /runbooks/{security_baseline → security}/utils/__init__.py +0 -0
  135. /runbooks/{security_baseline → security}/utils/common.py +0 -0
  136. /runbooks/{security_baseline → security}/utils/enums.py +0 -0
  137. /runbooks/{security_baseline → security}/utils/language.py +0 -0
  138. /runbooks/{security_baseline → security}/utils/level_const.py +0 -0
  139. /runbooks/{security_baseline → security}/utils/permission_list.py +0 -0
  140. {runbooks-0.6.1.dist-info → runbooks-0.7.5.dist-info}/WHEEL +0 -0
  141. {runbooks-0.6.1.dist-info → runbooks-0.7.5.dist-info}/licenses/LICENSE +0 -0
  142. {runbooks-0.6.1.dist-info → runbooks-0.7.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,243 @@
1
+ """
2
+ Lambda Function Inventory - Analyze and optimize Lambda function configurations.
3
+ """
4
+
5
+ import copy
6
+ import json
7
+ import logging
8
+ import re
9
+
10
+ import click
11
+ from botocore.exceptions import ClientError
12
+
13
+ from .commons import (
14
+ display_aws_account_info,
15
+ get_client,
16
+ get_lambda_invocations,
17
+ get_lambda_total_duration,
18
+ write_to_csv,
19
+ )
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # Lambda pricing (adjust for your region)
24
+ PRICE_PER_GB_SECOND = 0.00001667 # US East (N. Virginia) - update for your region
25
+
26
+
27
+ def update_iam_role_with_inline_policies(role_name, new_policy_document):
28
+ """Update IAM role inline policies with improved error handling."""
29
+ try:
30
+ client_iam = get_client("iam")
31
+
32
+ for policy_name, policy in new_policy_document.items():
33
+ policy_string = json.dumps(policy)
34
+
35
+ try:
36
+ client_iam.put_role_policy(RoleName=role_name, PolicyName=policy_name, PolicyDocument=policy_string)
37
+ logger.info(f"✓ Updated policy '{policy_name}' for role '{role_name}'")
38
+
39
+ except ClientError as e:
40
+ error_code = e.response.get("Error", {}).get("Code", "Unknown")
41
+ logger.error(f"✗ Failed to update policy '{policy_name}' for role '{role_name}': {error_code}")
42
+
43
+ except Exception as e:
44
+ logger.error(f"Failed to update IAM role policies: {e}")
45
+ raise
46
+
47
+
48
+ def update_policy_document(policy_document):
49
+ new_policy_document = copy.deepcopy(policy_document)
50
+ changes = {}
51
+
52
+ for policy_name, policy in new_policy_document.items():
53
+ for statement in iterate_policy_statement(policy):
54
+ # Check if 'Action' is '*'
55
+ if statement["Action"] == "*" and statement["Resource"] != "*" and isinstance(statement["Resource"], str):
56
+ match = re.search(r"arn:aws:(\w+):", statement["Resource"])
57
+ if match and set(statement["Action"]) != {f"{match.group(1)}:*"}:
58
+ statement["Action"] = f"{match.group(1)}:*"
59
+ changes.setdefault(policy_name, []).append(statement["Action"])
60
+ elif isinstance(statement["Resource"], list) and statement["Action"] == "*":
61
+ statement["Action"] = []
62
+ for resource in statement["Resource"]:
63
+ match = re.search(r"arn:aws:(\w+):", resource)
64
+ if match and set(statement["Action"]) != {f"{match.group(1)}:*"}:
65
+ statement["Action"].append(f"{match.group(1)}:*")
66
+ changes.setdefault(policy_name, []).append(statement["Action"])
67
+
68
+ # Check if 'Resource' is '*'
69
+ if statement["Resource"] == "*" and statement["Action"] != "*" and isinstance(statement["Action"], str):
70
+ match = re.search(r"(\w+):", statement["Action"])
71
+ if match and set(statement["Resource"]) != {f"arn:aws:{match.group(1)}:*:*:*"}:
72
+ statement["Resource"] = f"arn:aws:{match.group(1)}:*:*:*"
73
+ changes.setdefault(policy_name, []).append(statement["Resource"])
74
+ elif isinstance(statement["Action"], list) and statement["Resource"] == "*":
75
+ statement["Resource"] = []
76
+ for action in statement["Action"]:
77
+ match = re.search(r"(\w+):", action)
78
+ if match and set(statement["Resource"]) != {f"arn:aws:{match.group(1)}:*:*:*"}:
79
+ statement["Resource"].append(f"arn:aws:{match.group(1)}:*:*:*")
80
+ changes.setdefault(policy_name, []).append(statement["Resource"])
81
+
82
+ return changes, new_policy_document
83
+
84
+
85
+ def iterate_policy_statement(policy):
86
+ if isinstance(policy["Statement"], list):
87
+ for statement in policy["Statement"]:
88
+ yield statement
89
+ elif isinstance(policy["Statement"], dict):
90
+ yield policy["Statement"]
91
+
92
+
93
+ def list_all_lambda_functions(client_lambda):
94
+ """Generator that yields all Lambda functions with pagination."""
95
+ try:
96
+ paginator = client_lambda.get_paginator("list_functions")
97
+
98
+ for page in paginator.paginate():
99
+ for function in page["Functions"]:
100
+ yield function
101
+
102
+ except ClientError as e:
103
+ logger.error(f"Failed to list Lambda functions: {e}")
104
+ raise
105
+
106
+
107
+ @click.command()
108
+ @click.option(
109
+ "--dry-run", is_flag=True, default=True, help="Preview mode - show analysis without making policy changes"
110
+ )
111
+ @click.option("--output-file", default="lambda_functions.csv", help="Output CSV file path")
112
+ @click.option("--days", default=360, help="Number of days to analyze for invocations")
113
+ def list_lambda_functions(dry_run: bool = True, output_file: str = "lambda_functions.csv", days: int = 360):
114
+ """Analyze Lambda functions, costs, and IAM policies with optimization suggestions."""
115
+ logger.info(f"Analyzing Lambda functions in {display_aws_account_info()}")
116
+
117
+ try:
118
+ # Initialize AWS clients
119
+ client_lambda = get_client("lambda")
120
+ client_cloudwatch = get_client("cloudwatch")
121
+ client_iam = get_client("iam")
122
+
123
+ # Get all Lambda functions
124
+ all_functions = list(list_all_lambda_functions(client_lambda))
125
+
126
+ if not all_functions:
127
+ logger.info("No Lambda functions found")
128
+ return
129
+
130
+ logger.info(f"Found {len(all_functions)} Lambda functions to analyze")
131
+
132
+ data = []
133
+ policy_updates_count = 0
134
+
135
+ # Analyze each function
136
+ for i, function in enumerate(all_functions, 1):
137
+ function_name = function["FunctionName"]
138
+ logger.info(f"Analyzing function {i}/{len(all_functions)}: {function_name}")
139
+
140
+ try:
141
+ # Get function metrics
142
+ invocations = get_lambda_invocations(function_name, days)
143
+ total_duration = get_lambda_total_duration(client_cloudwatch, function_name)
144
+ memory_size_gb = function["MemorySize"] / 1024 # Convert to GB
145
+ role_arn = function["Role"]
146
+ role_name = role_arn.split("/")[-1]
147
+
148
+ warnings = []
149
+
150
+ # Get IAM role policies
151
+ try:
152
+ attached_response = client_iam.list_attached_role_policies(RoleName=role_name)
153
+ attached_policies = attached_response.get("AttachedPolicies", [])
154
+ except ClientError as e:
155
+ if e.response.get("Error", {}).get("Code") == "NoSuchEntity":
156
+ message = f"Role {role_name} does not exist for function {function_name}"
157
+ logger.warning(message)
158
+ warnings.append(message)
159
+ attached_policies = []
160
+ else:
161
+ raise
162
+
163
+ try:
164
+ inline_response = client_iam.list_role_policies(RoleName=role_name)
165
+ inline_policies = inline_response.get("PolicyNames", [])
166
+ except ClientError as e:
167
+ if e.response.get("Error", {}).get("Code") == "NoSuchEntity":
168
+ inline_policies = []
169
+ else:
170
+ raise
171
+
172
+ # Get inline policy documents
173
+ inline_policy_documents = {}
174
+ for policy_name in inline_policies:
175
+ try:
176
+ policy_response = client_iam.get_role_policy(RoleName=role_name, PolicyName=policy_name)
177
+ inline_policy_documents[policy_name] = policy_response["PolicyDocument"]
178
+ except ClientError as e:
179
+ logger.warning(f"Could not get policy '{policy_name}' for role '{role_name}': {e}")
180
+
181
+ # Analyze and potentially update policies
182
+ changes, new_policy_document = update_policy_document(inline_policy_documents)
183
+
184
+ if changes:
185
+ logger.info(f" → Policy optimization recommendations found for {role_name}")
186
+ if not dry_run:
187
+ logger.info(f" → Updating policies for role: {role_name}")
188
+ update_iam_role_with_inline_policies(role_name, new_policy_document)
189
+ policy_updates_count += 1
190
+ else:
191
+ logger.info(f" → DRY-RUN: Would update policies for role: {role_name}")
192
+
193
+ # Calculate cost estimate
194
+ gb_seconds = (total_duration / 1000) * memory_size_gb
195
+ cost_estimate = gb_seconds * PRICE_PER_GB_SECOND
196
+
197
+ # Collect function data
198
+ function_data = {
199
+ "FunctionName": function_name,
200
+ "Runtime": function.get("Runtime", ""),
201
+ "MemorySize": function.get("MemorySize", 0),
202
+ "Timeout": function.get("Timeout", 0),
203
+ "LastModified": function.get("LastModified", ""),
204
+ "Description": function.get("Description", ""),
205
+ "Version": function.get("Version", ""),
206
+ f"Total Invocations in {days} days": invocations,
207
+ "Total Duration 30 days (seconds)": total_duration / 1000,
208
+ "Estimated Cost (30 days)": round(cost_estimate, 4),
209
+ "IAM Role": role_name,
210
+ "Attached Policies Count": len(attached_policies),
211
+ "Inline Policies Count": len(inline_policies),
212
+ "Policy Optimization": "Changes available" if changes else "No changes needed",
213
+ "Warnings": "; ".join(warnings) if warnings else "None",
214
+ }
215
+
216
+ data.append(function_data)
217
+
218
+ except Exception as e:
219
+ logger.error(f" ✗ Failed to analyze function {function_name}: {e}")
220
+ # Add minimal data for failed analysis
221
+ data.append({"FunctionName": function_name, "Error": str(e), "Status": "Analysis Failed"})
222
+
223
+ # Export results
224
+ write_to_csv(data, output_file)
225
+ logger.info(f"Lambda analysis exported to: {output_file}")
226
+
227
+ # Summary
228
+ logger.info("\n=== ANALYSIS SUMMARY ===")
229
+ logger.info(f"Functions analyzed: {len(all_functions)}")
230
+ logger.info(
231
+ f"Functions with policy optimizations: {sum(1 for d in data if d.get('Policy Optimization') == 'Changes available')}"
232
+ )
233
+
234
+ if dry_run and policy_updates_count == 0:
235
+ policy_candidates = sum(1 for d in data if d.get("Policy Optimization") == "Changes available")
236
+ if policy_candidates > 0:
237
+ logger.info(f"To apply {policy_candidates} policy optimizations, run with --no-dry-run")
238
+ elif not dry_run:
239
+ logger.info(f"Applied policy updates to {policy_updates_count} roles")
240
+
241
+ except Exception as e:
242
+ logger.error(f"Failed to analyze Lambda functions: {e}")
243
+ raise