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.
- runbooks/__init__.py +87 -37
- runbooks/cfat/README.md +300 -49
- runbooks/cfat/__init__.py +2 -2
- runbooks/finops/__init__.py +1 -1
- runbooks/finops/cli.py +1 -1
- runbooks/inventory/collectors/__init__.py +8 -0
- runbooks/inventory/collectors/aws_management.py +791 -0
- runbooks/inventory/collectors/aws_networking.py +3 -3
- runbooks/main.py +3389 -782
- runbooks/operate/__init__.py +207 -0
- runbooks/operate/base.py +311 -0
- runbooks/operate/cloudformation_operations.py +619 -0
- runbooks/operate/cloudwatch_operations.py +496 -0
- runbooks/operate/dynamodb_operations.py +812 -0
- runbooks/operate/ec2_operations.py +926 -0
- runbooks/operate/iam_operations.py +569 -0
- runbooks/operate/s3_operations.py +1211 -0
- runbooks/operate/tagging_operations.py +655 -0
- runbooks/remediation/CLAUDE.md +100 -0
- runbooks/remediation/DOME9.md +218 -0
- runbooks/remediation/README.md +26 -0
- runbooks/remediation/Tests/__init__.py +0 -0
- runbooks/remediation/Tests/update_policy.py +74 -0
- runbooks/remediation/__init__.py +95 -0
- runbooks/remediation/acm_cert_expired_unused.py +98 -0
- runbooks/remediation/acm_remediation.py +875 -0
- runbooks/remediation/api_gateway_list.py +167 -0
- runbooks/remediation/base.py +643 -0
- runbooks/remediation/cloudtrail_remediation.py +908 -0
- runbooks/remediation/cloudtrail_s3_modifications.py +296 -0
- runbooks/remediation/cognito_active_users.py +78 -0
- runbooks/remediation/cognito_remediation.py +856 -0
- runbooks/remediation/cognito_user_password_reset.py +163 -0
- runbooks/remediation/commons.py +455 -0
- runbooks/remediation/dynamodb_optimize.py +155 -0
- runbooks/remediation/dynamodb_remediation.py +744 -0
- runbooks/remediation/dynamodb_server_side_encryption.py +108 -0
- runbooks/remediation/ec2_public_ips.py +134 -0
- runbooks/remediation/ec2_remediation.py +892 -0
- runbooks/remediation/ec2_subnet_disable_auto_ip_assignment.py +72 -0
- runbooks/remediation/ec2_unattached_ebs_volumes.py +448 -0
- runbooks/remediation/ec2_unused_security_groups.py +202 -0
- runbooks/remediation/kms_enable_key_rotation.py +651 -0
- runbooks/remediation/kms_remediation.py +717 -0
- runbooks/remediation/lambda_list.py +243 -0
- runbooks/remediation/lambda_remediation.py +971 -0
- runbooks/remediation/multi_account.py +569 -0
- runbooks/remediation/rds_instance_list.py +199 -0
- runbooks/remediation/rds_remediation.py +873 -0
- runbooks/remediation/rds_snapshot_list.py +192 -0
- runbooks/remediation/requirements.txt +118 -0
- runbooks/remediation/s3_block_public_access.py +159 -0
- runbooks/remediation/s3_bucket_public_access.py +143 -0
- runbooks/remediation/s3_disable_static_website_hosting.py +74 -0
- runbooks/remediation/s3_downloader.py +215 -0
- runbooks/remediation/s3_enable_access_logging.py +562 -0
- runbooks/remediation/s3_encryption.py +526 -0
- runbooks/remediation/s3_force_ssl_secure_policy.py +143 -0
- runbooks/remediation/s3_list.py +141 -0
- runbooks/remediation/s3_object_search.py +201 -0
- runbooks/remediation/s3_remediation.py +816 -0
- runbooks/remediation/scan_for_phrase.py +425 -0
- runbooks/remediation/workspaces_list.py +220 -0
- runbooks/security/__init__.py +9 -10
- runbooks/security/security_baseline_tester.py +4 -2
- runbooks-0.7.6.dist-info/METADATA +608 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/RECORD +84 -76
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/entry_points.txt +0 -1
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/top_level.txt +0 -1
- jupyter-agent/.env +0 -2
- jupyter-agent/.env.template +0 -2
- jupyter-agent/.gitattributes +0 -35
- jupyter-agent/.gradio/certificate.pem +0 -31
- jupyter-agent/README.md +0 -16
- jupyter-agent/__main__.log +0 -8
- jupyter-agent/app.py +0 -256
- jupyter-agent/cloudops-agent.png +0 -0
- jupyter-agent/ds-system-prompt.txt +0 -154
- jupyter-agent/jupyter-agent.png +0 -0
- jupyter-agent/llama3_template.jinja +0 -123
- jupyter-agent/requirements.txt +0 -9
- jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +0 -68
- jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +0 -91
- jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +0 -91
- jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +0 -57
- jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +0 -53
- jupyter-agent/tmp/jupyter-agent.ipynb +0 -27
- jupyter-agent/utils.py +0 -409
- runbooks/aws/__init__.py +0 -58
- runbooks/aws/dynamodb_operations.py +0 -231
- runbooks/aws/ec2_copy_image_cross-region.py +0 -195
- runbooks/aws/ec2_describe_instances.py +0 -202
- runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
- runbooks/aws/ec2_run_instances.py +0 -213
- runbooks/aws/ec2_start_stop_instances.py +0 -212
- runbooks/aws/ec2_terminate_instances.py +0 -143
- runbooks/aws/ec2_unused_eips.py +0 -196
- runbooks/aws/ec2_unused_volumes.py +0 -188
- runbooks/aws/s3_create_bucket.py +0 -142
- runbooks/aws/s3_list_buckets.py +0 -152
- runbooks/aws/s3_list_objects.py +0 -156
- runbooks/aws/s3_object_operations.py +0 -183
- runbooks/aws/tagging_lambda_handler.py +0 -183
- runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
- runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
- runbooks/inventory/aws_organization.png +0 -0
- runbooks/inventory/cfn_move_stack_instances.py +0 -1526
- runbooks/inventory/delete_s3_buckets_objects.py +0 -169
- runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
- runbooks/inventory/update_aws_actions.py +0 -173
- runbooks/inventory/update_cfn_stacksets.py +0 -1215
- runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
- runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
- runbooks/inventory/update_s3_public_access_block.py +0 -539
- runbooks/organizations/__init__.py +0 -12
- runbooks/organizations/manager.py +0 -374
- runbooks-0.7.0.dist-info/METADATA +0 -375
- /runbooks/inventory/{tests → Tests}/common_test_data.py +0 -0
- /runbooks/inventory/{tests → Tests}/common_test_functions.py +0 -0
- /runbooks/inventory/{tests → Tests}/script_test_data.py +0 -0
- /runbooks/inventory/{tests → Tests}/setup.py +0 -0
- /runbooks/inventory/{tests → Tests}/src.py +0 -0
- /runbooks/inventory/{tests/test_inventory_modules.py → Tests/test_Inventory_Modules.py} +0 -0
- /runbooks/inventory/{tests → Tests}/test_cfn_describe_stacks.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_ec2_describe_instances.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_lambda_list_functions.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_moto_integration_example.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_org_list_accounts.py +0 -0
- /runbooks/inventory/{Inventory_Modules.py → inventory_modules.py} +0 -0
- /runbooks/{aws → operate}/tags.json +0 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/WHEEL +0 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/licenses/LICENSE +0 -0
runbooks/aws/s3_list_objects.py
DELETED
@@ -1,156 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
|
3
|
-
"""
|
4
|
-
List all objects inside a specified S3 bucket with logging and error handling.
|
5
|
-
|
6
|
-
Author: nnthanh101@gmail.com
|
7
|
-
Date: 2025-01-06
|
8
|
-
Version: 1.0.0
|
9
|
-
|
10
|
-
Usage:
|
11
|
-
python list_s3_objects.py <bucket_name>
|
12
|
-
"""
|
13
|
-
|
14
|
-
import argparse
|
15
|
-
import sys
|
16
|
-
from typing import Dict, List, Optional
|
17
|
-
|
18
|
-
import boto3
|
19
|
-
from botocore.exceptions import BotoCoreError, ClientError, NoCredentialsError
|
20
|
-
from tabulate import tabulate
|
21
|
-
|
22
|
-
from runbooks.utils.logger import configure_logger
|
23
|
-
|
24
|
-
## ✅ Configure Logger
|
25
|
-
logger = configure_logger(__name__)
|
26
|
-
|
27
|
-
|
28
|
-
## ==============================
|
29
|
-
## AWS S3 UTILITIES
|
30
|
-
## ==============================
|
31
|
-
def s3_list_objects(
|
32
|
-
bucket_name: str,
|
33
|
-
prefix: Optional[str] = None,
|
34
|
-
max_keys: int = 1000,
|
35
|
-
) -> List[Dict[str, str]]:
|
36
|
-
"""
|
37
|
-
List objects in the specified S3 bucket.
|
38
|
-
|
39
|
-
Args:
|
40
|
-
bucket_name (str): The name of the S3 bucket.
|
41
|
-
prefix (Optional[str]): Filter objects by prefix (default: None).
|
42
|
-
max_keys (int): Maximum number of keys to retrieve per request (default: 1000).
|
43
|
-
|
44
|
-
Returns:
|
45
|
-
List[Dict[str, str]]: List of object details, including key, size, and last modified date.
|
46
|
-
|
47
|
-
Raises:
|
48
|
-
NoCredentialsError: If AWS credentials are missing.
|
49
|
-
ClientError: If there is an issue accessing the bucket.
|
50
|
-
"""
|
51
|
-
try:
|
52
|
-
logger.info(f"Initializing S3 client for bucket: {bucket_name}")
|
53
|
-
client = boto3.client("s3")
|
54
|
-
|
55
|
-
## ✅ Prepare Parameters
|
56
|
-
params = {"Bucket": bucket_name, "MaxKeys": max_keys}
|
57
|
-
if prefix:
|
58
|
-
params["Prefix"] = prefix
|
59
|
-
|
60
|
-
## ✅ Fetch Objects with Pagination Support
|
61
|
-
paginator = client.get_paginator("list_objects_v2")
|
62
|
-
page_iterator = paginator.paginate(**params)
|
63
|
-
|
64
|
-
object_list = []
|
65
|
-
for page in page_iterator:
|
66
|
-
if "Contents" in page: # Check if there are objects
|
67
|
-
for obj in page["Contents"]:
|
68
|
-
object_list.append(
|
69
|
-
{
|
70
|
-
"Key": obj["Key"],
|
71
|
-
"Size (KB)": f"{obj['Size'] / 1024:.2f}", # Convert bytes to KB
|
72
|
-
"LastModified": obj["LastModified"].strftime("%Y-%m-%d %H:%M:%S"),
|
73
|
-
}
|
74
|
-
)
|
75
|
-
|
76
|
-
## ✅ Log Results
|
77
|
-
logger.info(f"Found {len(object_list)} object(s) in bucket '{bucket_name}'.")
|
78
|
-
return object_list
|
79
|
-
|
80
|
-
except NoCredentialsError:
|
81
|
-
logger.error("AWS credentials not found. Ensure ~/.aws/credentials is configured.")
|
82
|
-
raise
|
83
|
-
|
84
|
-
except ClientError as e:
|
85
|
-
logger.error(f"AWS Client Error: {e}")
|
86
|
-
raise
|
87
|
-
|
88
|
-
except BotoCoreError as e:
|
89
|
-
logger.error(f"BotoCore Error: {e}")
|
90
|
-
raise
|
91
|
-
|
92
|
-
except Exception as e:
|
93
|
-
logger.error(f"Unexpected error: {e}")
|
94
|
-
raise
|
95
|
-
|
96
|
-
|
97
|
-
## ==============================
|
98
|
-
## DISPLAY UTILITIES
|
99
|
-
## ==============================
|
100
|
-
def display_objects(objects: List[Dict[str, str]], bucket_name: str) -> None:
|
101
|
-
"""
|
102
|
-
Displays S3 object details in Markdown table format.
|
103
|
-
|
104
|
-
Args:
|
105
|
-
objects (List[Dict[str, str]]): List of S3 object details.
|
106
|
-
bucket_name (str): Name of the S3 bucket.
|
107
|
-
"""
|
108
|
-
if not objects:
|
109
|
-
print(f"No objects found in bucket: {bucket_name}")
|
110
|
-
return
|
111
|
-
|
112
|
-
## ✅ Prepare Table Headers and Rows
|
113
|
-
headers = ["Key", "Size (KB)", "Last Modified"]
|
114
|
-
rows = [[obj["Key"], obj["Size (KB)"], obj["LastModified"]] for obj in objects]
|
115
|
-
|
116
|
-
## ✅ Render Markdown Table
|
117
|
-
print(f"### S3 Objects in Bucket: `{bucket_name}`\n")
|
118
|
-
print(tabulate(rows, headers=headers, tablefmt="github"))
|
119
|
-
|
120
|
-
|
121
|
-
## ==============================
|
122
|
-
## MAIN FUNCTION
|
123
|
-
## ==============================
|
124
|
-
def main():
|
125
|
-
"""
|
126
|
-
Main entry point for listing S3 objects.
|
127
|
-
"""
|
128
|
-
## ✅ Parse Command-Line Arguments
|
129
|
-
parser = argparse.ArgumentParser(description="List objects in an AWS S3 bucket.")
|
130
|
-
parser.add_argument("--bucket", required=True, help="The name of the S3 bucket.")
|
131
|
-
parser.add_argument("--prefix", default=None, help="Filter objects by prefix.")
|
132
|
-
parser.add_argument(
|
133
|
-
"--max-keys",
|
134
|
-
type=int,
|
135
|
-
default=1000,
|
136
|
-
help="Max number of keys to fetch (default: 1000).",
|
137
|
-
)
|
138
|
-
|
139
|
-
args = parser.parse_args()
|
140
|
-
|
141
|
-
try:
|
142
|
-
## ✅ Fetch and Display S3 Objects
|
143
|
-
objects = s3_list_objects(
|
144
|
-
bucket_name=args.bucket,
|
145
|
-
prefix=args.prefix,
|
146
|
-
max_keys=args.max_keys,
|
147
|
-
)
|
148
|
-
display_objects(objects, args.bucket)
|
149
|
-
|
150
|
-
except Exception as e:
|
151
|
-
logger.error(f"Program terminated with error: {e}")
|
152
|
-
sys.exit(1)
|
153
|
-
|
154
|
-
|
155
|
-
if __name__ == "__main__":
|
156
|
-
main()
|
@@ -1,183 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
|
3
|
-
"""
|
4
|
-
S3 Object Operations: Upload and Delete Objects in Amazon S3.
|
5
|
-
|
6
|
-
This script provides functionality to:
|
7
|
-
1. Upload a file to an S3 bucket.
|
8
|
-
2. Delete a file from an S3 bucket.
|
9
|
-
|
10
|
-
Designed for usage in Python (pip), Docker, and AWS Lambda environments.
|
11
|
-
|
12
|
-
Author: nnthanh101@gmail.com
|
13
|
-
Date: 2025-01-08
|
14
|
-
Version: 1.0.0
|
15
|
-
"""
|
16
|
-
|
17
|
-
import logging
|
18
|
-
import os
|
19
|
-
from typing import Optional
|
20
|
-
|
21
|
-
import boto3
|
22
|
-
from botocore.exceptions import BotoCoreError, ClientError
|
23
|
-
|
24
|
-
# ==============================
|
25
|
-
# CONFIGURATION VARIABLES
|
26
|
-
# ==============================
|
27
|
-
AWS_REGION = os.getenv("AWS_REGION", "us-east-1")
|
28
|
-
S3_BUCKET = os.getenv("S3_BUCKET", "my-default-bucket")
|
29
|
-
S3_KEY = os.getenv("S3_KEY", "default-key.txt")
|
30
|
-
LOCAL_FILE_PATH = os.getenv("LOCAL_FILE_PATH", "default.txt")
|
31
|
-
ACL = os.getenv("ACL", "private") ## Options: 'private', 'public-read', etc.
|
32
|
-
|
33
|
-
# ==============================
|
34
|
-
# LOGGING CONFIGURATION
|
35
|
-
# ==============================
|
36
|
-
logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO)
|
37
|
-
logger = logging.getLogger(__name__)
|
38
|
-
|
39
|
-
# ==============================
|
40
|
-
# AWS CLIENT INITIALIZATION
|
41
|
-
# ==============================
|
42
|
-
try:
|
43
|
-
s3_client = boto3.client("s3", region_name=AWS_REGION)
|
44
|
-
logger.info("✅ S3 client initialized successfully.")
|
45
|
-
except Exception as e:
|
46
|
-
logger.error(f"❌ Failed to initialize S3 client: {e}")
|
47
|
-
raise
|
48
|
-
|
49
|
-
|
50
|
-
# ==============================
|
51
|
-
# UPLOAD FUNCTION
|
52
|
-
# ==============================
|
53
|
-
def put_object(bucket: str, key: str, file_path: str, acl: str = "private") -> None:
|
54
|
-
"""
|
55
|
-
Uploads a file to an S3 bucket.
|
56
|
-
|
57
|
-
Args:
|
58
|
-
bucket (str): The name of the S3 bucket.
|
59
|
-
key (str): The object key in S3.
|
60
|
-
file_path (str): Local file path to be uploaded.
|
61
|
-
acl (str): Access control list (default: 'private').
|
62
|
-
|
63
|
-
Raises:
|
64
|
-
Exception: Any upload failure.
|
65
|
-
"""
|
66
|
-
try:
|
67
|
-
# ✅ Check if the file exists locally
|
68
|
-
if not os.path.exists(file_path):
|
69
|
-
raise FileNotFoundError(f"File '{file_path}' not found.")
|
70
|
-
|
71
|
-
logger.info(f"🚀 Uploading '{file_path}' to bucket '{bucket}' with key '{key}'...")
|
72
|
-
with open(file_path, "rb") as file_reader:
|
73
|
-
s3_client.put_object(ACL=acl, Body=file_reader, Bucket=bucket, Key=key)
|
74
|
-
logger.info(f"✅ File '{file_path}' uploaded successfully to '{bucket}/{key}'.")
|
75
|
-
|
76
|
-
except FileNotFoundError as e:
|
77
|
-
logger.error(f"❌ File Not Found: {e}")
|
78
|
-
raise
|
79
|
-
|
80
|
-
except ClientError as e:
|
81
|
-
logger.error(f"❌ AWS Client Error: {e}")
|
82
|
-
raise
|
83
|
-
|
84
|
-
except BotoCoreError as e:
|
85
|
-
logger.error(f"❌ BotoCore Error: {e}")
|
86
|
-
raise
|
87
|
-
|
88
|
-
except Exception as e:
|
89
|
-
logger.error(f"❌ Unexpected Error: {e}")
|
90
|
-
raise
|
91
|
-
|
92
|
-
|
93
|
-
# ==============================
|
94
|
-
# DELETE FUNCTION
|
95
|
-
# ==============================
|
96
|
-
def delete_object(bucket: str, key: str) -> None:
|
97
|
-
"""
|
98
|
-
Deletes an object from an S3 bucket.
|
99
|
-
|
100
|
-
Args:
|
101
|
-
bucket (str): The name of the S3 bucket.
|
102
|
-
key (str): The object key to delete.
|
103
|
-
|
104
|
-
Raises:
|
105
|
-
Exception: Any deletion failure.
|
106
|
-
"""
|
107
|
-
try:
|
108
|
-
logger.info(f"🗑️ Deleting object '{key}' from bucket '{bucket}'...")
|
109
|
-
s3_client.delete_object(Bucket=bucket, Key=key)
|
110
|
-
logger.info(f"✅ Object '{key}' deleted successfully from '{bucket}'.")
|
111
|
-
|
112
|
-
except ClientError as e:
|
113
|
-
logger.error(f"❌ AWS Client Error: {e}")
|
114
|
-
raise
|
115
|
-
|
116
|
-
except BotoCoreError as e:
|
117
|
-
logger.error(f"❌ BotoCore Error: {e}")
|
118
|
-
raise
|
119
|
-
|
120
|
-
except Exception as e:
|
121
|
-
logger.error(f"❌ Unexpected Error: {e}")
|
122
|
-
raise
|
123
|
-
|
124
|
-
|
125
|
-
# ==============================
|
126
|
-
# MAIN FUNCTION (CLI/DOCKER)
|
127
|
-
# ==============================
|
128
|
-
def main():
|
129
|
-
"""
|
130
|
-
Main entry point for CLI/Docker execution.
|
131
|
-
"""
|
132
|
-
try:
|
133
|
-
# ✅ Upload Object
|
134
|
-
put_object(S3_BUCKET, S3_KEY, LOCAL_FILE_PATH, ACL)
|
135
|
-
|
136
|
-
# ✅ Delete Object (Uncomment if needed)
|
137
|
-
# delete_object(S3_BUCKET, S3_KEY)
|
138
|
-
|
139
|
-
except Exception as e:
|
140
|
-
logger.error(f"❌ Error in main: {e}")
|
141
|
-
raise
|
142
|
-
|
143
|
-
|
144
|
-
# ==============================
|
145
|
-
# AWS LAMBDA HANDLER
|
146
|
-
# ==============================
|
147
|
-
def lambda_handler(event, context):
|
148
|
-
"""
|
149
|
-
AWS Lambda handler for S3 object operations.
|
150
|
-
|
151
|
-
Args:
|
152
|
-
event (dict): AWS Lambda event payload with 'action', 'bucket', 'key', and 'file_path'.
|
153
|
-
context: AWS Lambda context object.
|
154
|
-
|
155
|
-
Returns:
|
156
|
-
dict: Status code and message.
|
157
|
-
"""
|
158
|
-
try:
|
159
|
-
action = event.get("action") # 'upload' or 'delete'
|
160
|
-
bucket = event.get("bucket", S3_BUCKET)
|
161
|
-
key = event.get("key", S3_KEY)
|
162
|
-
file_path = event.get("file_path", LOCAL_FILE_PATH)
|
163
|
-
acl = event.get("acl", ACL)
|
164
|
-
|
165
|
-
if action == "upload":
|
166
|
-
put_object(bucket, key, file_path, acl)
|
167
|
-
return {"statusCode": 200, "body": f"File '{key}' uploaded to '{bucket}'."}
|
168
|
-
elif action == "delete":
|
169
|
-
delete_object(bucket, key)
|
170
|
-
return {"statusCode": 200, "body": f"File '{key}' deleted from '{bucket}'."}
|
171
|
-
else:
|
172
|
-
raise ValueError("Invalid action. Supported actions: 'upload', 'delete'.")
|
173
|
-
|
174
|
-
except Exception as e:
|
175
|
-
logger.error(f"❌ Lambda Error: {e}")
|
176
|
-
return {"statusCode": 500, "body": str(e)}
|
177
|
-
|
178
|
-
|
179
|
-
# ==============================
|
180
|
-
# SCRIPT ENTRY POINT
|
181
|
-
# ==============================
|
182
|
-
if __name__ == "__main__":
|
183
|
-
main()
|
@@ -1,183 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
|
3
|
-
"""
|
4
|
-
AWS Lambda Function for Auto-Tagging EC2 Instances Based on S3 Configuration.
|
5
|
-
|
6
|
-
Author: nnthanh101@gmail.com
|
7
|
-
Date: 2025-01-07
|
8
|
-
Version: 1.0.0
|
9
|
-
|
10
|
-
Description:
|
11
|
-
- Fetches tagging configuration from an S3 bucket.
|
12
|
-
- Applies tags dynamically to an EC2 instance when triggered by AWS CloudTrail events.
|
13
|
-
|
14
|
-
Requirements:
|
15
|
-
- IAM Role Permissions:
|
16
|
-
* s3:GetObject
|
17
|
-
* ec2:CreateTags
|
18
|
-
* sts:GetCallerIdentity
|
19
|
-
- Environment Variables:
|
20
|
-
* S3_BUCKET: Name of the S3 bucket storing tags.json
|
21
|
-
* S3_OBJECT_KEY: Key for the tags.json file
|
22
|
-
"""
|
23
|
-
|
24
|
-
import json
|
25
|
-
import os
|
26
|
-
import re
|
27
|
-
from typing import Dict, List
|
28
|
-
|
29
|
-
import boto3
|
30
|
-
from botocore.exceptions import BotoCoreError, ClientError
|
31
|
-
|
32
|
-
from runbooks.utils.logger import configure_logger
|
33
|
-
|
34
|
-
## ✅ Configure Logger
|
35
|
-
logger = configure_logger(__name__)
|
36
|
-
|
37
|
-
## ✅ Initialize AWS Clients
|
38
|
-
s3 = boto3.client("s3")
|
39
|
-
ec2 = boto3.client("ec2")
|
40
|
-
|
41
|
-
# ==============================
|
42
|
-
# CONFIGURATIONS: dafault S3 bucket and object key
|
43
|
-
# ==============================
|
44
|
-
BUCKET_NAME = os.getenv("S3_BUCKET", "os-auto-tagging") ## Default S3 bucket name
|
45
|
-
OBJECT_KEY = os.getenv("S3_OBJECT_KEY", "tags.json") ## Default S3 object key
|
46
|
-
LOCAL_FILE_PATH = "/tmp/tags.json" ## Local Temp file path
|
47
|
-
|
48
|
-
# ==============================
|
49
|
-
# VALIDATION CONFIGURATIONS
|
50
|
-
# ==============================
|
51
|
-
REQUIRED_TAGS = [
|
52
|
-
"Account Name",
|
53
|
-
"Functional Area",
|
54
|
-
"WBS Code",
|
55
|
-
"Business Unit",
|
56
|
-
"Managed by",
|
57
|
-
"CostGroup",
|
58
|
-
"TechOwner",
|
59
|
-
]
|
60
|
-
|
61
|
-
TAG_VALUE_REGEX = r"^[a-zA-Z0-9\s\-_@]+$" ## Allowed characters for tag values
|
62
|
-
|
63
|
-
|
64
|
-
def validate_tags(tags: List[Dict[str, str]]) -> None:
|
65
|
-
"""
|
66
|
-
Validates that all required tags are present in the tag key-value list.
|
67
|
-
|
68
|
-
Args:
|
69
|
-
tags (List[Dict[str, str]]): List of tags to validate.
|
70
|
-
|
71
|
-
Raises:
|
72
|
-
ValueError: If any required tag is missing.
|
73
|
-
"""
|
74
|
-
tag_keys = {tag["Key"] for tag in tags} ## Extract tag keys
|
75
|
-
missing_tags = [tag for tag in REQUIRED_TAGS if tag not in tag_keys]
|
76
|
-
|
77
|
-
if missing_tags:
|
78
|
-
raise ValueError(f"Missing required tags: {', '.join(missing_tags)}")
|
79
|
-
|
80
|
-
## Validate tag values
|
81
|
-
for tag in tags:
|
82
|
-
if not re.match(TAG_VALUE_REGEX, tag["Value"]):
|
83
|
-
raise ValueError(f"Invalid value '{tag['Value']}' for tag '{tag['Key']}'.")
|
84
|
-
|
85
|
-
logger.info("All required tags are validated and meet constraints.")
|
86
|
-
|
87
|
-
|
88
|
-
# ==============================
|
89
|
-
# S3 UTILITIES
|
90
|
-
# ==============================
|
91
|
-
def download_tags_from_s3(bucket: str, key: str, local_path: str) -> List[Dict[str, str]]:
|
92
|
-
"""
|
93
|
-
Downloads the tagging configuration file from S3 and parses it.
|
94
|
-
|
95
|
-
Args:
|
96
|
-
bucket (str): The S3 bucket name.
|
97
|
-
key (str): The object key in the bucket.
|
98
|
-
local_path (str): The local path to store the file.
|
99
|
-
|
100
|
-
Returns:
|
101
|
-
List[Dict[str, str]]: List of tags.
|
102
|
-
"""
|
103
|
-
try:
|
104
|
-
## ✅ Download tags.json File from S3
|
105
|
-
logger.info(f"Downloading '{key}' from bucket '{bucket}'...")
|
106
|
-
s3.download_file(bucket, key, local_path)
|
107
|
-
logger.info(f"File downloaded successfully to {local_path}.")
|
108
|
-
|
109
|
-
## ✅ Parse the tags.json file
|
110
|
-
with open(local_path, "r") as file:
|
111
|
-
## Load tags as a list of dictionaries
|
112
|
-
tags = json.load(file)
|
113
|
-
|
114
|
-
validate_tags(tags) ## Validate required tags
|
115
|
-
return tags
|
116
|
-
|
117
|
-
except FileNotFoundError:
|
118
|
-
logger.error("Local file not found after download.")
|
119
|
-
raise
|
120
|
-
|
121
|
-
except ClientError as e:
|
122
|
-
logger.error(f"S3 Client Error: {e}")
|
123
|
-
raise
|
124
|
-
|
125
|
-
except Exception as e:
|
126
|
-
logger.error(f"Unexpected error while downloading tags: {e}")
|
127
|
-
raise
|
128
|
-
|
129
|
-
|
130
|
-
# ==============================
|
131
|
-
# EC2 UTILITIES
|
132
|
-
# ==============================
|
133
|
-
def apply_tags_to_instance(instance_id: str, tags: List[Dict[str, str]]) -> None:
|
134
|
-
"""
|
135
|
-
Applies tags to the specified EC2 instance.
|
136
|
-
|
137
|
-
Args:
|
138
|
-
instance_id (str): The ID of the EC2 instance.
|
139
|
-
tags (List[Dict[str, str]]): Tags to apply.
|
140
|
-
|
141
|
-
Raises:
|
142
|
-
Exception: Any AWS tagging errors.
|
143
|
-
"""
|
144
|
-
try:
|
145
|
-
logger.info(f"Applying tags to EC2 instance: {instance_id}")
|
146
|
-
ec2.create_tags(Resources=[instance_id], Tags=tags)
|
147
|
-
logger.info(f"Tags successfully applied to instance {instance_id}: {tags}")
|
148
|
-
|
149
|
-
except ClientError as e:
|
150
|
-
logger.error(f"EC2 Client Error: {e}")
|
151
|
-
raise
|
152
|
-
|
153
|
-
|
154
|
-
# ==============================
|
155
|
-
# MAIN HANDLER
|
156
|
-
# ==============================
|
157
|
-
def lambda_handler(event, context):
|
158
|
-
"""
|
159
|
-
AWS Lambda Handler for applying tags to EC2 instances.
|
160
|
-
|
161
|
-
Args:
|
162
|
-
event (dict): AWS event data.
|
163
|
-
context: AWS Lambda context.
|
164
|
-
"""
|
165
|
-
try:
|
166
|
-
## ✅ Extract instance ID from the event
|
167
|
-
instance_id = event["detail"]["responseElements"]["instancesSet"]["items"][0]["instanceId"]
|
168
|
-
logger.info(f"Processing instance ID: {instance_id}")
|
169
|
-
|
170
|
-
## ✅ Download and Parse Tags
|
171
|
-
tags = download_tags_from_s3(BUCKET_NAME, OBJECT_KEY, LOCAL_FILE_PATH)
|
172
|
-
|
173
|
-
## ✅ Apply Tags to Instance
|
174
|
-
apply_tags_to_instance(instance_id, tags)
|
175
|
-
|
176
|
-
## ✅ Success Response
|
177
|
-
return {
|
178
|
-
"statusCode": 200,
|
179
|
-
"body": json.dumps(f"Tags successfully applied to instance {instance_id}"),
|
180
|
-
}
|
181
|
-
except Exception as e:
|
182
|
-
logger.error(f"Error during tagging process: {e}")
|
183
|
-
return {"statusCode": 500, "body": json.dumps(f"Error: {str(e)}")}
|