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
@@ -0,0 +1,202 @@
|
|
1
|
+
"""
|
2
|
+
EC2 Security Group Cleanup - Identify and remove unused security groups safely.
|
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("--dry-run", is_flag=True, default=True, help="Preview mode - show actions without making changes")
|
17
|
+
@click.option("--include-elb", is_flag=True, help="Also check ELB/ALB/NLB usage (comprehensive scan)")
|
18
|
+
@click.option("--include-eni", is_flag=True, help="Also check network interface usage")
|
19
|
+
def find_unused_security_groups(dry_run, include_elb, include_eni):
|
20
|
+
"""Find and remove unused security groups with comprehensive usage checks."""
|
21
|
+
logger.info(f"Finding unused security groups in {display_aws_account_info()}")
|
22
|
+
|
23
|
+
try:
|
24
|
+
ec2 = get_client("ec2")
|
25
|
+
|
26
|
+
# Collect all security groups
|
27
|
+
logger.info("🔍 Scanning all security groups...")
|
28
|
+
response = ec2.describe_security_groups()
|
29
|
+
all_security_groups = {}
|
30
|
+
default_security_groups = set()
|
31
|
+
|
32
|
+
for sg in response["SecurityGroups"]:
|
33
|
+
sg_id = sg["GroupId"]
|
34
|
+
sg_name = sg.get("GroupName", "")
|
35
|
+
all_security_groups[sg_id] = {
|
36
|
+
"name": sg_name,
|
37
|
+
"description": sg.get("Description", ""),
|
38
|
+
"vpc_id": sg.get("VpcId", ""),
|
39
|
+
}
|
40
|
+
|
41
|
+
# Track default security groups (cannot be deleted)
|
42
|
+
if sg_name == "default":
|
43
|
+
default_security_groups.add(sg_id)
|
44
|
+
|
45
|
+
logger.info(f"Found {len(all_security_groups)} security groups")
|
46
|
+
|
47
|
+
# Find security groups in use
|
48
|
+
used_security_groups = set()
|
49
|
+
|
50
|
+
# Check EC2 instances
|
51
|
+
logger.info("📋 Checking EC2 instance usage...")
|
52
|
+
instances_response = ec2.describe_instances()
|
53
|
+
instance_count = 0
|
54
|
+
|
55
|
+
for reservation in instances_response["Reservations"]:
|
56
|
+
for instance in reservation["Instances"]:
|
57
|
+
instance_count += 1
|
58
|
+
for sg in instance.get("SecurityGroups", []):
|
59
|
+
used_security_groups.add(sg["GroupId"])
|
60
|
+
|
61
|
+
logger.info(f"Checked {instance_count} EC2 instances")
|
62
|
+
|
63
|
+
# Check Load Balancers if requested
|
64
|
+
if include_elb:
|
65
|
+
logger.info("🔍 Checking Load Balancer usage...")
|
66
|
+
try:
|
67
|
+
# Check Classic Load Balancers
|
68
|
+
elb = get_client("elb")
|
69
|
+
elb_response = elb.describe_load_balancers()
|
70
|
+
for lb in elb_response.get("LoadBalancerDescriptions", []):
|
71
|
+
for sg_id in lb.get("SecurityGroups", []):
|
72
|
+
used_security_groups.add(sg_id)
|
73
|
+
|
74
|
+
# Check Application/Network Load Balancers
|
75
|
+
elbv2 = get_client("elbv2")
|
76
|
+
elbv2_response = elbv2.describe_load_balancers()
|
77
|
+
for lb in elbv2_response.get("LoadBalancers", []):
|
78
|
+
for sg_id in lb.get("SecurityGroups", []):
|
79
|
+
used_security_groups.add(sg_id)
|
80
|
+
|
81
|
+
logger.info("✓ Load balancer usage checked")
|
82
|
+
|
83
|
+
except ClientError as e:
|
84
|
+
logger.warning(f"Could not check load balancers: {e}")
|
85
|
+
|
86
|
+
# Check Network Interfaces if requested
|
87
|
+
if include_eni:
|
88
|
+
logger.info("🔍 Checking network interface usage...")
|
89
|
+
try:
|
90
|
+
eni_response = ec2.describe_network_interfaces()
|
91
|
+
for eni in eni_response.get("NetworkInterfaces", []):
|
92
|
+
for group in eni.get("Groups", []):
|
93
|
+
used_security_groups.add(group["GroupId"])
|
94
|
+
|
95
|
+
logger.info("✓ Network interface usage checked")
|
96
|
+
|
97
|
+
except ClientError as e:
|
98
|
+
logger.warning(f"Could not check network interfaces: {e}")
|
99
|
+
|
100
|
+
# Check for security group references (ingress/egress rules)
|
101
|
+
logger.info("🔍 Checking security group rule references...")
|
102
|
+
referenced_security_groups = set()
|
103
|
+
|
104
|
+
for sg_id, sg_info in all_security_groups.items():
|
105
|
+
try:
|
106
|
+
sg_details = ec2.describe_security_groups(GroupIds=[sg_id])["SecurityGroups"][0]
|
107
|
+
|
108
|
+
# Check ingress rules for SG references
|
109
|
+
for rule in sg_details.get("IpPermissions", []):
|
110
|
+
for sg_ref in rule.get("UserIdGroupPairs", []):
|
111
|
+
referenced_security_groups.add(sg_ref["GroupId"])
|
112
|
+
|
113
|
+
# Check egress rules for SG references
|
114
|
+
for rule in sg_details.get("IpPermissionsEgress", []):
|
115
|
+
for sg_ref in rule.get("UserIdGroupPairs", []):
|
116
|
+
referenced_security_groups.add(sg_ref["GroupId"])
|
117
|
+
|
118
|
+
except ClientError as e:
|
119
|
+
logger.debug(f"Could not check rules for {sg_id}: {e}")
|
120
|
+
|
121
|
+
# Combine all usage types
|
122
|
+
all_used_groups = used_security_groups | referenced_security_groups | default_security_groups
|
123
|
+
|
124
|
+
# Find unused security groups
|
125
|
+
unused_security_groups = set(all_security_groups.keys()) - all_used_groups
|
126
|
+
|
127
|
+
logger.info("\n=== ANALYSIS RESULTS ===")
|
128
|
+
logger.info(f"Total security groups: {len(all_security_groups)}")
|
129
|
+
logger.info(f"Used by resources: {len(used_security_groups)}")
|
130
|
+
logger.info(f"Referenced in rules: {len(referenced_security_groups)}")
|
131
|
+
logger.info(f"Default groups (protected): {len(default_security_groups)}")
|
132
|
+
logger.info(f"Unused security groups: {len(unused_security_groups)}")
|
133
|
+
|
134
|
+
if not unused_security_groups:
|
135
|
+
logger.info("✅ No unused security groups found")
|
136
|
+
return
|
137
|
+
|
138
|
+
logger.warning(f"⚠ Found {len(unused_security_groups)} unused security groups")
|
139
|
+
|
140
|
+
# Show unused security groups details
|
141
|
+
logger.info("\n📋 Unused Security Groups:")
|
142
|
+
deletion_candidates = []
|
143
|
+
|
144
|
+
for sg_id in unused_security_groups:
|
145
|
+
sg_info = all_security_groups[sg_id]
|
146
|
+
logger.info(f" {sg_id}: {sg_info['name']}")
|
147
|
+
logger.info(f" Description: {sg_info['description']}")
|
148
|
+
logger.info(f" VPC: {sg_info['vpc_id']}")
|
149
|
+
|
150
|
+
# Skip default security groups
|
151
|
+
if sg_info["name"] == "default":
|
152
|
+
logger.info(f" Status: Protected (default security group)")
|
153
|
+
else:
|
154
|
+
deletion_candidates.append(sg_id)
|
155
|
+
logger.info(f" Status: Can be deleted")
|
156
|
+
|
157
|
+
logger.info(f"\n📊 Summary: {len(deletion_candidates)} security groups can be safely deleted")
|
158
|
+
|
159
|
+
# Delete unused security groups
|
160
|
+
if deletion_candidates:
|
161
|
+
if dry_run:
|
162
|
+
logger.info("DRY-RUN: Would delete the following security groups:")
|
163
|
+
for sg_id in deletion_candidates:
|
164
|
+
sg_info = all_security_groups[sg_id]
|
165
|
+
logger.info(f" - {sg_id} ({sg_info['name']})")
|
166
|
+
logger.info("To perform actual deletion, run with --no-dry-run")
|
167
|
+
else:
|
168
|
+
logger.info("🗑 Deleting unused security groups...")
|
169
|
+
deleted_count = 0
|
170
|
+
failed_count = 0
|
171
|
+
|
172
|
+
for sg_id in deletion_candidates:
|
173
|
+
sg_info = all_security_groups[sg_id]
|
174
|
+
logger.info(f" → Deleting {sg_id} ({sg_info['name']})...")
|
175
|
+
|
176
|
+
try:
|
177
|
+
ec2.delete_security_group(GroupId=sg_id)
|
178
|
+
deleted_count += 1
|
179
|
+
logger.info(f" ✓ Successfully deleted {sg_id}")
|
180
|
+
|
181
|
+
except ClientError as e:
|
182
|
+
error_code = e.response.get("Error", {}).get("Code", "Unknown")
|
183
|
+
if error_code == "DependencyViolation":
|
184
|
+
logger.warning(f" ⚠ Cannot delete {sg_id}: has dependencies")
|
185
|
+
elif error_code == "InvalidGroup.InUse":
|
186
|
+
logger.warning(f" ⚠ Cannot delete {sg_id}: currently in use")
|
187
|
+
else:
|
188
|
+
logger.error(f" ✗ Failed to delete {sg_id}: {e}")
|
189
|
+
failed_count += 1
|
190
|
+
|
191
|
+
logger.info(f"\n✅ Deletion complete: {deleted_count} deleted, {failed_count} failed")
|
192
|
+
|
193
|
+
except ClientError as e:
|
194
|
+
logger.error(f"Failed to scan security groups: {e}")
|
195
|
+
raise
|
196
|
+
except Exception as e:
|
197
|
+
logger.error(f"Unexpected error: {e}")
|
198
|
+
raise
|
199
|
+
|
200
|
+
|
201
|
+
if __name__ == "__main__":
|
202
|
+
find_unused_security_groups()
|