runbooks 0.7.0__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 (100) 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.5.dist-info/METADATA +606 -0
  67. {runbooks-0.7.0.dist-info → runbooks-0.7.5.dist-info}/RECORD +72 -44
  68. {runbooks-0.7.0.dist-info → runbooks-0.7.5.dist-info}/entry_points.txt +0 -1
  69. runbooks/aws/__init__.py +0 -58
  70. runbooks/aws/dynamodb_operations.py +0 -231
  71. runbooks/aws/ec2_copy_image_cross-region.py +0 -195
  72. runbooks/aws/ec2_describe_instances.py +0 -202
  73. runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
  74. runbooks/aws/ec2_run_instances.py +0 -213
  75. runbooks/aws/ec2_start_stop_instances.py +0 -212
  76. runbooks/aws/ec2_terminate_instances.py +0 -143
  77. runbooks/aws/ec2_unused_eips.py +0 -196
  78. runbooks/aws/ec2_unused_volumes.py +0 -188
  79. runbooks/aws/s3_create_bucket.py +0 -142
  80. runbooks/aws/s3_list_buckets.py +0 -152
  81. runbooks/aws/s3_list_objects.py +0 -156
  82. runbooks/aws/s3_object_operations.py +0 -183
  83. runbooks/aws/tagging_lambda_handler.py +0 -183
  84. runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
  85. runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
  86. runbooks/inventory/cfn_move_stack_instances.py +0 -1526
  87. runbooks/inventory/delete_s3_buckets_objects.py +0 -169
  88. runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
  89. runbooks/inventory/update_aws_actions.py +0 -173
  90. runbooks/inventory/update_cfn_stacksets.py +0 -1215
  91. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
  92. runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
  93. runbooks/inventory/update_s3_public_access_block.py +0 -539
  94. runbooks/organizations/__init__.py +0 -12
  95. runbooks/organizations/manager.py +0 -374
  96. runbooks-0.7.0.dist-info/METADATA +0 -375
  97. /runbooks/{aws → operate}/tags.json +0 -0
  98. {runbooks-0.7.0.dist-info → runbooks-0.7.5.dist-info}/WHEEL +0 -0
  99. {runbooks-0.7.0.dist-info → runbooks-0.7.5.dist-info}/licenses/LICENSE +0 -0
  100. {runbooks-0.7.0.dist-info → runbooks-0.7.5.dist-info}/top_level.txt +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()