runbooks 0.7.0__py3-none-any.whl → 0.7.6__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 (132) hide show
  1. runbooks/__init__.py +87 -37
  2. runbooks/cfat/README.md +300 -49
  3. runbooks/cfat/__init__.py +2 -2
  4. runbooks/finops/__init__.py +1 -1
  5. runbooks/finops/cli.py +1 -1
  6. runbooks/inventory/collectors/__init__.py +8 -0
  7. runbooks/inventory/collectors/aws_management.py +791 -0
  8. runbooks/inventory/collectors/aws_networking.py +3 -3
  9. runbooks/main.py +3389 -782
  10. runbooks/operate/__init__.py +207 -0
  11. runbooks/operate/base.py +311 -0
  12. runbooks/operate/cloudformation_operations.py +619 -0
  13. runbooks/operate/cloudwatch_operations.py +496 -0
  14. runbooks/operate/dynamodb_operations.py +812 -0
  15. runbooks/operate/ec2_operations.py +926 -0
  16. runbooks/operate/iam_operations.py +569 -0
  17. runbooks/operate/s3_operations.py +1211 -0
  18. runbooks/operate/tagging_operations.py +655 -0
  19. runbooks/remediation/CLAUDE.md +100 -0
  20. runbooks/remediation/DOME9.md +218 -0
  21. runbooks/remediation/README.md +26 -0
  22. runbooks/remediation/Tests/__init__.py +0 -0
  23. runbooks/remediation/Tests/update_policy.py +74 -0
  24. runbooks/remediation/__init__.py +95 -0
  25. runbooks/remediation/acm_cert_expired_unused.py +98 -0
  26. runbooks/remediation/acm_remediation.py +875 -0
  27. runbooks/remediation/api_gateway_list.py +167 -0
  28. runbooks/remediation/base.py +643 -0
  29. runbooks/remediation/cloudtrail_remediation.py +908 -0
  30. runbooks/remediation/cloudtrail_s3_modifications.py +296 -0
  31. runbooks/remediation/cognito_active_users.py +78 -0
  32. runbooks/remediation/cognito_remediation.py +856 -0
  33. runbooks/remediation/cognito_user_password_reset.py +163 -0
  34. runbooks/remediation/commons.py +455 -0
  35. runbooks/remediation/dynamodb_optimize.py +155 -0
  36. runbooks/remediation/dynamodb_remediation.py +744 -0
  37. runbooks/remediation/dynamodb_server_side_encryption.py +108 -0
  38. runbooks/remediation/ec2_public_ips.py +134 -0
  39. runbooks/remediation/ec2_remediation.py +892 -0
  40. runbooks/remediation/ec2_subnet_disable_auto_ip_assignment.py +72 -0
  41. runbooks/remediation/ec2_unattached_ebs_volumes.py +448 -0
  42. runbooks/remediation/ec2_unused_security_groups.py +202 -0
  43. runbooks/remediation/kms_enable_key_rotation.py +651 -0
  44. runbooks/remediation/kms_remediation.py +717 -0
  45. runbooks/remediation/lambda_list.py +243 -0
  46. runbooks/remediation/lambda_remediation.py +971 -0
  47. runbooks/remediation/multi_account.py +569 -0
  48. runbooks/remediation/rds_instance_list.py +199 -0
  49. runbooks/remediation/rds_remediation.py +873 -0
  50. runbooks/remediation/rds_snapshot_list.py +192 -0
  51. runbooks/remediation/requirements.txt +118 -0
  52. runbooks/remediation/s3_block_public_access.py +159 -0
  53. runbooks/remediation/s3_bucket_public_access.py +143 -0
  54. runbooks/remediation/s3_disable_static_website_hosting.py +74 -0
  55. runbooks/remediation/s3_downloader.py +215 -0
  56. runbooks/remediation/s3_enable_access_logging.py +562 -0
  57. runbooks/remediation/s3_encryption.py +526 -0
  58. runbooks/remediation/s3_force_ssl_secure_policy.py +143 -0
  59. runbooks/remediation/s3_list.py +141 -0
  60. runbooks/remediation/s3_object_search.py +201 -0
  61. runbooks/remediation/s3_remediation.py +816 -0
  62. runbooks/remediation/scan_for_phrase.py +425 -0
  63. runbooks/remediation/workspaces_list.py +220 -0
  64. runbooks/security/__init__.py +9 -10
  65. runbooks/security/security_baseline_tester.py +4 -2
  66. runbooks-0.7.6.dist-info/METADATA +608 -0
  67. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/RECORD +84 -76
  68. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/entry_points.txt +0 -1
  69. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/top_level.txt +0 -1
  70. jupyter-agent/.env +0 -2
  71. jupyter-agent/.env.template +0 -2
  72. jupyter-agent/.gitattributes +0 -35
  73. jupyter-agent/.gradio/certificate.pem +0 -31
  74. jupyter-agent/README.md +0 -16
  75. jupyter-agent/__main__.log +0 -8
  76. jupyter-agent/app.py +0 -256
  77. jupyter-agent/cloudops-agent.png +0 -0
  78. jupyter-agent/ds-system-prompt.txt +0 -154
  79. jupyter-agent/jupyter-agent.png +0 -0
  80. jupyter-agent/llama3_template.jinja +0 -123
  81. jupyter-agent/requirements.txt +0 -9
  82. jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +0 -68
  83. jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +0 -91
  84. jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +0 -91
  85. jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +0 -57
  86. jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +0 -53
  87. jupyter-agent/tmp/jupyter-agent.ipynb +0 -27
  88. jupyter-agent/utils.py +0 -409
  89. runbooks/aws/__init__.py +0 -58
  90. runbooks/aws/dynamodb_operations.py +0 -231
  91. runbooks/aws/ec2_copy_image_cross-region.py +0 -195
  92. runbooks/aws/ec2_describe_instances.py +0 -202
  93. runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
  94. runbooks/aws/ec2_run_instances.py +0 -213
  95. runbooks/aws/ec2_start_stop_instances.py +0 -212
  96. runbooks/aws/ec2_terminate_instances.py +0 -143
  97. runbooks/aws/ec2_unused_eips.py +0 -196
  98. runbooks/aws/ec2_unused_volumes.py +0 -188
  99. runbooks/aws/s3_create_bucket.py +0 -142
  100. runbooks/aws/s3_list_buckets.py +0 -152
  101. runbooks/aws/s3_list_objects.py +0 -156
  102. runbooks/aws/s3_object_operations.py +0 -183
  103. runbooks/aws/tagging_lambda_handler.py +0 -183
  104. runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
  105. runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
  106. runbooks/inventory/aws_organization.png +0 -0
  107. runbooks/inventory/cfn_move_stack_instances.py +0 -1526
  108. runbooks/inventory/delete_s3_buckets_objects.py +0 -169
  109. runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
  110. runbooks/inventory/update_aws_actions.py +0 -173
  111. runbooks/inventory/update_cfn_stacksets.py +0 -1215
  112. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
  113. runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
  114. runbooks/inventory/update_s3_public_access_block.py +0 -539
  115. runbooks/organizations/__init__.py +0 -12
  116. runbooks/organizations/manager.py +0 -374
  117. runbooks-0.7.0.dist-info/METADATA +0 -375
  118. /runbooks/inventory/{tests → Tests}/common_test_data.py +0 -0
  119. /runbooks/inventory/{tests → Tests}/common_test_functions.py +0 -0
  120. /runbooks/inventory/{tests → Tests}/script_test_data.py +0 -0
  121. /runbooks/inventory/{tests → Tests}/setup.py +0 -0
  122. /runbooks/inventory/{tests → Tests}/src.py +0 -0
  123. /runbooks/inventory/{tests/test_inventory_modules.py → Tests/test_Inventory_Modules.py} +0 -0
  124. /runbooks/inventory/{tests → Tests}/test_cfn_describe_stacks.py +0 -0
  125. /runbooks/inventory/{tests → Tests}/test_ec2_describe_instances.py +0 -0
  126. /runbooks/inventory/{tests → Tests}/test_lambda_list_functions.py +0 -0
  127. /runbooks/inventory/{tests → Tests}/test_moto_integration_example.py +0 -0
  128. /runbooks/inventory/{tests → Tests}/test_org_list_accounts.py +0 -0
  129. /runbooks/inventory/{Inventory_Modules.py → inventory_modules.py} +0 -0
  130. /runbooks/{aws → operate}/tags.json +0 -0
  131. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/WHEEL +0 -0
  132. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,163 @@
1
+ """
2
+ 🚨 HIGH-RISK: Cognito Password Reset - Handle user authentication with extreme care.
3
+ """
4
+
5
+ import logging
6
+
7
+ import click
8
+ from botocore.exceptions import ClientError
9
+
10
+ from .commons import display_aws_account_info, get_client
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ @click.command()
16
+ @click.option("--user-pool-id", help="Cognito User Pool ID (interactive mode if not provided)")
17
+ @click.option("--username", help="Username to reset (interactive mode if not provided)")
18
+ @click.option("--confirm", is_flag=True, help="Skip confirmation prompts (dangerous!)")
19
+ def reset_password(user_pool_id, username, confirm):
20
+ """🚨 HIGH-RISK: Reset user password in Cognito User Pool with safety checks."""
21
+
22
+ # HIGH-RISK OPERATION WARNING
23
+ if not confirm:
24
+ logger.warning("🚨 HIGH-RISK OPERATION: User password reset")
25
+ logger.warning("This operation will reset user authentication credentials")
26
+ if not click.confirm("Do you want to continue?"):
27
+ logger.info("Operation cancelled by user")
28
+ return
29
+
30
+ logger.info(f"🔐 Cognito password reset in {display_aws_account_info()}")
31
+
32
+ try:
33
+ client = get_client("cognito-idp")
34
+
35
+ # Get User Pool ID if not provided
36
+ if not user_pool_id:
37
+ logger.info("Listing available User Pools...")
38
+ response = client.list_user_pools(MaxResults=60)
39
+ user_pools = response.get("UserPools", [])
40
+
41
+ if not user_pools:
42
+ logger.error("No User Pools found")
43
+ return
44
+
45
+ logger.info("\n📋 Available User Pools:")
46
+ for i, pool in enumerate(user_pools, 1):
47
+ logger.info(f" {i}. {pool['Name']} (ID: {pool['Id']})")
48
+
49
+ user_pool_id = click.prompt("\nPlease enter the User Pool ID", type=str)
50
+
51
+ # Validate User Pool exists
52
+ try:
53
+ client.describe_user_pool(UserPoolId=user_pool_id)
54
+ logger.info(f"✓ User Pool {user_pool_id} found")
55
+ except ClientError as e:
56
+ if e.response.get("Error", {}).get("Code") == "ResourceNotFoundException":
57
+ logger.error(f"❌ User Pool not found: {user_pool_id}")
58
+ return
59
+ raise
60
+
61
+ # Get username if not provided
62
+ if not username:
63
+ logger.info("Listing users in the pool...")
64
+ try:
65
+ response = client.list_users(UserPoolId=user_pool_id, Limit=50)
66
+ users = response.get("Users", [])
67
+
68
+ if not users:
69
+ logger.error("No users found in the User Pool")
70
+ return
71
+
72
+ logger.info("\n👥 Available Users:")
73
+ for i, user in enumerate(users, 1):
74
+ status = user.get("UserStatus", "Unknown")
75
+ enabled = user.get("Enabled", False)
76
+ logger.info(f" {i}. {user['Username']} (Status: {status}, Enabled: {enabled})")
77
+
78
+ username = click.prompt("\nPlease enter the username", type=str)
79
+
80
+ except ClientError as e:
81
+ logger.error(f"Failed to list users: {e}")
82
+ return
83
+
84
+ # Validate user exists and get details
85
+ try:
86
+ user_info = client.admin_get_user(UserPoolId=user_pool_id, Username=username)
87
+ user_status = user_info.get("UserStatus", "Unknown")
88
+ user_enabled = user_info.get("Enabled", False)
89
+
90
+ logger.info(f"\n📝 User Details:")
91
+ logger.info(f" Username: {username}")
92
+ logger.info(f" Status: {user_status}")
93
+ logger.info(f" Enabled: {user_enabled}")
94
+
95
+ except ClientError as e:
96
+ if e.response.get("Error", {}).get("Code") == "UserNotFoundException":
97
+ logger.error(f"❌ User not found: {username}")
98
+ return
99
+ raise
100
+
101
+ # Get new password and settings
102
+ password = click.prompt("Please enter the new password", type=str, hide_input=True, confirmation_prompt=True)
103
+ permanent = click.prompt("Set the password as permanent? (default: temporary)", type=bool, default=False)
104
+
105
+ # FINAL CONFIRMATION for high-risk operation
106
+ if not confirm:
107
+ logger.warning(f"\n🚨 FINAL CONFIRMATION:")
108
+ logger.warning(f" User Pool: {user_pool_id}")
109
+ logger.warning(f" Username: {username}")
110
+ logger.warning(f" Permanent: {permanent}")
111
+ if not click.confirm("Proceed with password reset?"):
112
+ logger.info("Operation cancelled")
113
+ return
114
+
115
+ # Perform password reset
116
+ logger.info(f"🔄 Resetting password for user: {username}")
117
+ try:
118
+ response = client.admin_set_user_password(
119
+ UserPoolId=user_pool_id, Username=username, Password=password, Permanent=permanent
120
+ )
121
+ logger.info(f"✅ Password reset successful for user: {username}")
122
+
123
+ # Log the operation for audit trail
124
+ logger.info(f"🔍 Audit: Password reset completed")
125
+ logger.info(f" User Pool: {user_pool_id}")
126
+ logger.info(f" Username: {username}")
127
+ logger.info(f" Permanent: {permanent}")
128
+
129
+ except ClientError as e:
130
+ error_code = e.response.get("Error", {}).get("Code", "Unknown")
131
+ if error_code == "UserNotFoundException":
132
+ logger.error(f"❌ User not found: {username}")
133
+ elif error_code == "InvalidPasswordException":
134
+ logger.error("❌ Invalid password - check User Pool password policy")
135
+ elif error_code == "NotAuthorizedException":
136
+ logger.error("❌ Not authorized - check IAM permissions")
137
+ else:
138
+ logger.error(f"❌ Failed to reset password: {e}")
139
+ return
140
+
141
+ # Optional: Add user to ReadHistorical group (if needed)
142
+ group_name = "ReadHistorical"
143
+ if click.confirm(f"Add user to group '{group_name}'?", default=False):
144
+ try:
145
+ # Check if user is already in the group
146
+ response = client.admin_list_groups_for_user(UserPoolId=user_pool_id, Username=username)
147
+ existing_groups = [group["GroupName"] for group in response.get("Groups", [])]
148
+
149
+ if group_name in existing_groups:
150
+ logger.info(f"ℹ User already in group: {group_name}")
151
+ else:
152
+ client.admin_add_user_to_group(UserPoolId=user_pool_id, Username=username, GroupName=group_name)
153
+ logger.info(f"✅ User added to group: {group_name}")
154
+
155
+ except ClientError as e:
156
+ logger.error(f"❌ Failed to add user to group: {e}")
157
+
158
+ except ClientError as e:
159
+ logger.error(f"❌ Cognito operation failed: {e}")
160
+ raise
161
+ except Exception as e:
162
+ logger.error(f"❌ Unexpected error: {e}")
163
+ raise
@@ -0,0 +1,455 @@
1
+ import configparser
2
+ import csv
3
+ import json
4
+ import logging
5
+ import os
6
+ import time
7
+ import webbrowser
8
+ from datetime import datetime, timedelta
9
+ from functools import lru_cache as LRU_cache
10
+
11
+ import boto3
12
+ import botocore.exceptions
13
+ import botocore.session
14
+ from botocore.exceptions import ClientError
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def get_all_available_aws_credentials(start_url: str = None, role_name="power-user") -> dict:
20
+ if not start_url:
21
+ raise ValueError("Start URL for AWS SSO is required")
22
+
23
+ credentials = {}
24
+
25
+ # Create an SSO OIDC client
26
+ sso_oidc = boto3.client("sso-oidc", region_name="ap-southeast-2")
27
+
28
+ try:
29
+ # Register client
30
+ client_creds = sso_oidc.register_client(
31
+ clientName="MyApp",
32
+ clientType="public",
33
+ )
34
+
35
+ # Get device authorization
36
+ device_auth = sso_oidc.start_device_authorization(
37
+ clientId=client_creds["clientId"], clientSecret=client_creds["clientSecret"], startUrl=start_url
38
+ )
39
+
40
+ print(f"Please go to {device_auth['verificationUriComplete']} and enter the code: {device_auth['userCode']}")
41
+ webbrowser.open(device_auth["verificationUriComplete"])
42
+
43
+ # Wait for user to authorize
44
+ token = None
45
+ max_retries = 60 # Maximum number of retries (5 minutes with 5-second intervals)
46
+ retry_count = 0
47
+
48
+ while not token and retry_count < max_retries:
49
+ try:
50
+ token = sso_oidc.create_token(
51
+ clientId=client_creds["clientId"],
52
+ clientSecret=client_creds["clientSecret"],
53
+ grantType="urn:ietf:params:oauth:grant-type:device_code",
54
+ deviceCode=device_auth["deviceCode"],
55
+ )
56
+ except sso_oidc.exceptions.AuthorizationPendingException:
57
+ print("Waiting for authorization... Please complete the process in your browser.")
58
+ time.sleep(5) # Wait for 5 seconds before trying again
59
+ retry_count += 1
60
+ except Exception as e:
61
+ print(f"An error occurred: {e}")
62
+ break
63
+
64
+ if not token:
65
+ print("Authorization timed out or failed. Please try again.")
66
+ return credentials
67
+
68
+ # Create SSO client
69
+ sso = boto3.client("sso", region_name="ap-southeast-2")
70
+
71
+ # List accounts (with pagination)
72
+ all_accounts = []
73
+ paginator = sso.get_paginator("list_accounts")
74
+ for page in paginator.paginate(accessToken=token["accessToken"]):
75
+ all_accounts.extend(page["accountList"])
76
+
77
+ for account in all_accounts:
78
+ # Get role names for each account
79
+ roles = []
80
+ role_paginator = sso.get_paginator("list_account_roles")
81
+ for role_page in role_paginator.paginate(accessToken=token["accessToken"], accountId=account["accountId"]):
82
+ roles.extend(role_page["roleList"])
83
+
84
+ for role in roles:
85
+ # Get temporary credentials for each role
86
+ if role["roleName"] == role_name:
87
+ creds = sso.get_role_credentials(
88
+ accessToken=token["accessToken"], accountId=account["accountId"], roleName=role["roleName"]
89
+ )
90
+
91
+ credentials[f"{account['accountId']}_{role['roleName']}"] = {
92
+ "aws_access_key_id": creds["roleCredentials"]["accessKeyId"],
93
+ "aws_secret_access_key": creds["roleCredentials"]["secretAccessKey"],
94
+ "aws_session_token": creds["roleCredentials"]["sessionToken"],
95
+ }
96
+
97
+ except ClientError as e:
98
+ logger.error(f"An error occurred: {e}")
99
+
100
+ return credentials
101
+
102
+
103
+ def read_all_aws_credentials(file_path: str = "credentials") -> dict:
104
+ config = configparser.ConfigParser()
105
+ config.read(file_path) # replace with your file path if not in the same directory
106
+
107
+ credentials = {}
108
+ for profile_name in config.sections():
109
+ aws_access_key_id = config.get(profile_name, "aws_access_key_id")
110
+ aws_secret_access_key = config.get(profile_name, "aws_secret_access_key")
111
+ aws_session_token = config.get(profile_name, "aws_session_token")
112
+
113
+ credentials[profile_name] = {
114
+ "aws_access_key_id": aws_access_key_id,
115
+ "aws_secret_access_key": aws_secret_access_key,
116
+ "aws_session_token": aws_session_token,
117
+ }
118
+
119
+ return credentials
120
+
121
+
122
+ def get_api_gateways(client):
123
+ response = client.get_rest_apis()
124
+ return response["items"]
125
+
126
+
127
+ def get_stages(client, rest_api_id):
128
+ response = client.get_stages(restApiId=rest_api_id)
129
+ return response["item"]
130
+
131
+
132
+ @LRU_cache(maxsize=32)
133
+ def get_resources(client, rest_api_id):
134
+ response = client.get_resources(restApiId=rest_api_id)
135
+ return response["items"]
136
+
137
+
138
+ def get_method_details(client, rest_api_id, resource_id, http_method):
139
+ try:
140
+ response = client.get_method(restApiId=rest_api_id, resourceId=resource_id, httpMethod=http_method)
141
+ return response
142
+ except botocore.exceptions.ClientError as e:
143
+ if e.response["Error"]["Code"] == "NotFoundException":
144
+ # logging.info(f"Method {http_method} not found for resource {resource_id} in API {rest_api_id}")
145
+ return None
146
+ else:
147
+ raise
148
+
149
+
150
+ def get_client(client_name: str):
151
+ return boto3.client(
152
+ client_name,
153
+ aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
154
+ aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
155
+ aws_session_token=os.environ.get("AWS_SESSION_TOKEN"),
156
+ region_name=os.environ.get("AWS_REGION"), # or your preferred region
157
+ )
158
+
159
+
160
+ def get_resource(client_name: str):
161
+ return boto3.resource(
162
+ client_name,
163
+ aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
164
+ aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
165
+ aws_session_token=os.environ.get("AWS_SESSION_TOKEN"),
166
+ region_name=os.environ.get("AWS_REGION"), # or your preferred region
167
+ )
168
+
169
+
170
+ def get_log_groups(client, log_group_name_prefix):
171
+ response = client.describe_log_groups(logGroupNamePrefix=log_group_name_prefix)
172
+ return response["logGroups"]
173
+
174
+
175
+ def write_to_csv(data, filename):
176
+ """Write data to a CSV file.
177
+
178
+ Args:
179
+ data (list of dict): The data to write. Each dict is a row in the CSV.
180
+ filename (str): The name of the CSV file.
181
+ """
182
+ with open(filename, "w", newline="") as csvfile:
183
+ fieldnames = data[0].keys()
184
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
185
+
186
+ writer.writeheader()
187
+ for row in data:
188
+ writer.writerow(row)
189
+
190
+
191
+ @LRU_cache(maxsize=32)
192
+ def get_log_group_from_lambda(client_lambda, function_name):
193
+ try:
194
+ response = client_lambda.get_function_configuration(FunctionName=function_name)
195
+
196
+ if "LoggingConfig" in response:
197
+ return response["LoggingConfig"]["LogGroup"]
198
+
199
+ else:
200
+ return None # Lambda logging might not be configured
201
+
202
+ except client_lambda.exceptions.ResourceNotFoundException:
203
+ message = f"Lambda function '{function_name}' not found."
204
+ logger.info(message)
205
+ return message
206
+
207
+
208
+ @LRU_cache(maxsize=32)
209
+ def get_lambda_config(client_lambda, function_name):
210
+ try:
211
+ response = client_lambda.get_function_configuration(FunctionName=function_name)
212
+
213
+ return response
214
+
215
+ except client_lambda.exceptions.ResourceNotFoundException:
216
+ message = f"Lambda function '{function_name}' not found."
217
+ logger.info(message)
218
+ return message
219
+
220
+
221
+ @LRU_cache(maxsize=32)
222
+ def get_lambda_invocations(function_name, days=30):
223
+ client_cloudwatch = get_client("cloudwatch")
224
+ # Define the time period
225
+ end_time = datetime.now()
226
+ start_time = end_time - timedelta(days=days)
227
+
228
+ # Get the metric statistics
229
+ response = client_cloudwatch.get_metric_statistics(
230
+ Namespace="AWS/Lambda",
231
+ MetricName="Invocations",
232
+ Dimensions=[
233
+ {"Name": "FunctionName", "Value": function_name},
234
+ ],
235
+ StartTime=start_time,
236
+ EndTime=end_time,
237
+ Period=3600 * 24, # One day periods
238
+ Statistics=[
239
+ "Sum", # Get the total (sum) of the invocations
240
+ ],
241
+ )
242
+
243
+ # Calculate the total invocations over the time period
244
+ total_invocations = sum(datapoint["Sum"] for datapoint in response["Datapoints"])
245
+
246
+ return total_invocations
247
+
248
+
249
+ @LRU_cache(maxsize=32)
250
+ def get_api_gateway_calls(api_name, days=30):
251
+ # Create a CloudWatch client
252
+ client = get_client("cloudwatch")
253
+
254
+ # Define the time period
255
+ end_time = datetime.now()
256
+ start_time = end_time - timedelta(days=days)
257
+
258
+ # Get the metric statistics
259
+ response = client.get_metric_statistics(
260
+ Namespace="AWS/ApiGateway",
261
+ MetricName="Count",
262
+ Dimensions=[
263
+ {"Name": "ApiName", "Value": api_name},
264
+ ],
265
+ StartTime=start_time,
266
+ EndTime=end_time,
267
+ Period=3600 * 24, # One day periods
268
+ Statistics=[
269
+ "Sum", # Get the total (sum) of the API calls
270
+ ],
271
+ )
272
+
273
+ # Calculate the total API calls over the time period
274
+ total_calls = sum(datapoint["Sum"] for datapoint in response["Datapoints"])
275
+
276
+ return total_calls
277
+
278
+
279
+ def get_lambda_total_duration(client_cloudwatch, function_name, days=30):
280
+ # Define the time period
281
+ end_time = datetime.now()
282
+ start_time = end_time - timedelta(days=days)
283
+
284
+ # Get the metric statistics for Duration
285
+ response_duration = client_cloudwatch.get_metric_statistics(
286
+ Namespace="AWS/Lambda",
287
+ MetricName="Duration",
288
+ Dimensions=[
289
+ {"Name": "FunctionName", "Value": function_name},
290
+ ],
291
+ StartTime=start_time,
292
+ EndTime=end_time,
293
+ Period=3600, # One hour periods
294
+ Statistics=[
295
+ "Sum", # Get the total duration
296
+ ],
297
+ )
298
+
299
+ # Calculate the total duration over the time period
300
+ total_duration = (
301
+ sum(datapoint["Sum"] for datapoint in response_duration["Datapoints"]) if response_duration["Datapoints"] else 0
302
+ )
303
+
304
+ return total_duration
305
+
306
+
307
+ def get_price(service_code, region_name, instance_type):
308
+ """Get the on-demand price for an instance type in a region"""
309
+ response = get_product_pricing(instance_type, region_name, service_code)
310
+
311
+ for product in response["PriceList"]:
312
+ product_obj = json.loads(product)
313
+ product_terms = product_obj["terms"]
314
+
315
+ for term in product_terms["OnDemand"]:
316
+ price_dimensions = product_terms["OnDemand"][term]["priceDimensions"]
317
+
318
+ for dimension in price_dimensions:
319
+ price_info = price_dimensions[dimension]
320
+ price = float(price_info["pricePerUnit"]["USD"])
321
+ currency = list(price_info["pricePerUnit"].keys())[0] # Get the currency
322
+ if not price:
323
+ continue
324
+ return price, currency
325
+
326
+ return None
327
+
328
+
329
+ @LRU_cache(maxsize=32)
330
+ def get_product_pricing(instance_type, region_name, service_code):
331
+ # Pricing API available only in selected regions
332
+ pricing = botocore.session.get_session().create_client("pricing", region_name="us-east-1")
333
+ response = pricing.get_products(
334
+ ServiceCode=service_code,
335
+ Filters=[
336
+ {"Type": "TERM_MATCH", "Field": "location", "Value": region_name},
337
+ {"Type": "TERM_MATCH", "Field": "instanceType", "Value": instance_type},
338
+ {"Type": "TERM_MATCH", "Field": "preInstalledSw", "Value": "NA"},
339
+ {"Type": "TERM_MATCH", "Field": "termType", "Value": "OnDemand"},
340
+ ],
341
+ MaxResults=100,
342
+ )
343
+ return response
344
+
345
+
346
+ def get_role_permissions(role_name):
347
+ # Create a client for IAM
348
+ iam_client = get_client("iam")
349
+
350
+ # Get the policies attached to the role
351
+ response = iam_client.list_attached_role_policies(RoleName=role_name)
352
+
353
+ # The policies are in the 'AttachedPolicies' field of the response
354
+ attached_policies = response["AttachedPolicies"]
355
+
356
+ response = iam_client.list_role_policies(RoleName=role_name)
357
+
358
+ inline_policies = response["PolicyNames"]
359
+
360
+ inline_policy_documents = {}
361
+ for policy_name in inline_policies:
362
+ policy_document = iam_client.get_role_policy(RoleName=role_name, PolicyName=policy_name)["PolicyDocument"]
363
+ inline_policy_documents[policy_name] = policy_document
364
+
365
+ return attached_policies, inline_policy_documents
366
+
367
+
368
+ def get_vpc_flow_logs():
369
+ # Create a client for EC2
370
+ ec2_client = get_client("ec2")
371
+
372
+ # Get a list of flow logs
373
+ response = ec2_client.describe_flow_logs()
374
+
375
+ # The flow logs are in the 'FlowLogs' field of the response
376
+ flow_logs = response["FlowLogs"]
377
+
378
+ # Create a dictionary to store the flow logs for each VPC
379
+ vpc_flow_logs = {}
380
+
381
+ # Iterate over the flow logs
382
+ for flow_log in flow_logs:
383
+ # Get the ID of the VPC that the flow log is attached to
384
+ vpc_id = flow_log["ResourceId"]
385
+
386
+ # If the VPC ID is not already in the dictionary, add it
387
+ if vpc_id not in vpc_flow_logs:
388
+ vpc_flow_logs[vpc_id] = []
389
+
390
+ # Add the flow log to the list for this VPC
391
+ vpc_flow_logs[vpc_id].append(flow_log)
392
+
393
+ return vpc_flow_logs
394
+
395
+
396
+ def get_ec2_instances_by_security_groups(security_group_ids):
397
+ ec2 = get_resource("ec2") # Use resource instead of client
398
+
399
+ # Filter instances based on security groups
400
+ instances = ec2.instances.filter(Filters=[{"Name": "instance.group-id", "Values": security_group_ids}])
401
+
402
+ instance_info = []
403
+ for instance in instances:
404
+ instance_info.append({"InstanceId": instance.id, "State": instance.state["Name"], "Tags": instance.tags})
405
+
406
+ return instance_info
407
+
408
+
409
+ @LRU_cache(maxsize=32)
410
+ def get_bucket_policy(bucket_name) -> tuple:
411
+ s3 = get_client("s3")
412
+ try:
413
+ result = s3.get_bucket_policy(Bucket=bucket_name)
414
+ policy = json.loads(result["Policy"])
415
+ except ClientError as e:
416
+ if e.response["Error"]["Code"] == "NoSuchBucketPolicy":
417
+ policy = "No policy"
418
+ else:
419
+ policy = e.response["Error"]["Message"]
420
+
421
+ try:
422
+ public_access_block = s3.get_public_access_block(Bucket=bucket_name)
423
+ except ClientError as e:
424
+ if e.response["Error"]["Code"] == "NoSuchPublicAccessBlockConfiguration":
425
+ public_access_block = "No public access block configuration"
426
+ else:
427
+ public_access_block = e.response["Error"]["Message"]
428
+ else:
429
+ if isinstance(public_access_block, dict):
430
+ public_access_block = public_access_block.get("PublicAccessBlockConfiguration", {})
431
+
432
+ return policy, public_access_block
433
+
434
+
435
+ def display_aws_account_info():
436
+ sts = get_client("sts")
437
+
438
+ # Get caller identity
439
+ identity = sts.get_caller_identity()
440
+
441
+ # Get account id
442
+ account_id = identity["Account"]
443
+
444
+ # Get IAM user or role name
445
+ user_or_role_name = identity["Arn"].split("/")[-2]
446
+
447
+ return {"AWS Account ID": account_id, "AWS User or Role Name": user_or_role_name}
448
+
449
+
450
+ def list_tables():
451
+ """Lists all DynamoDB tables in the account."""
452
+ dynamodb = get_client("dynamodb")
453
+ paginator = dynamodb.get_paginator("list_tables")
454
+ for page in paginator.paginate():
455
+ yield from page["TableNames"]