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,108 @@
1
+ """
2
+ DynamoDB Server-Side Encryption - Enable encryption at rest for all tables.
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, list_tables
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def get_table_sse_status(table_name):
16
+ """Check if DynamoDB table has server-side encryption enabled."""
17
+ try:
18
+ dynamodb = get_client("dynamodb")
19
+ response = dynamodb.describe_table(TableName=table_name)
20
+ sse_description = response["Table"].get("SSEDescription")
21
+
22
+ if sse_description and sse_description.get("Status") == "ENABLED":
23
+ logger.debug(f"Table '{table_name}' has SSE enabled")
24
+ return sse_description
25
+ else:
26
+ logger.debug(f"Table '{table_name}' does not have SSE enabled")
27
+ return None
28
+
29
+ except ClientError as e:
30
+ logger.error(f"Failed to get SSE status for table '{table_name}': {e}")
31
+ return None
32
+
33
+
34
+ def enable_table_sse(table_name):
35
+ """Enable server-side encryption for DynamoDB table using AWS managed key."""
36
+ kms_key_id = "alias/aws/dynamodb" # Use AWS managed key for DynamoDB
37
+
38
+ try:
39
+ dynamodb = get_client("dynamodb")
40
+ dynamodb.update_table(
41
+ TableName=table_name,
42
+ SSESpecification={
43
+ "Enabled": True,
44
+ "SSEType": "KMS",
45
+ "KMSMasterKeyId": kms_key_id,
46
+ },
47
+ )
48
+ logger.info(f"Enabled SSE for table '{table_name}' with AWS managed key")
49
+ return True
50
+
51
+ except ClientError as e:
52
+ logger.error(f"Failed to enable SSE for table '{table_name}': {e}")
53
+ return False
54
+
55
+
56
+ @click.command()
57
+ @click.option("--dry-run", is_flag=True, default=True, help="Preview mode - show actions without making changes")
58
+ def dynamodb_server_side_encryption(dry_run=True):
59
+ """Check and enable server-side encryption for all DynamoDB tables."""
60
+ logger.info(f"Checking DynamoDB encryption in {display_aws_account_info()}")
61
+
62
+ try:
63
+ table_names = list_tables()
64
+ if not table_names:
65
+ logger.info("No DynamoDB tables found")
66
+ return
67
+
68
+ logger.info(f"Found {len(table_names)} tables to check")
69
+
70
+ # Track results
71
+ tables_with_sse = []
72
+ tables_without_sse = []
73
+ tables_updated = []
74
+
75
+ # Check each table
76
+ for table_name in table_names:
77
+ logger.info(f"Checking table: {table_name}")
78
+ sse_status = get_table_sse_status(table_name)
79
+
80
+ if sse_status:
81
+ tables_with_sse.append(table_name)
82
+ logger.info(f" ✓ SSE already enabled")
83
+ else:
84
+ tables_without_sse.append(table_name)
85
+ logger.info(f" ✗ SSE not enabled")
86
+
87
+ # Enable SSE if not in dry-run mode
88
+ if not dry_run:
89
+ logger.info(f" → Enabling SSE for table '{table_name}'...")
90
+ if enable_table_sse(table_name):
91
+ tables_updated.append(table_name)
92
+ logger.info(f" ✓ Successfully enabled SSE")
93
+ else:
94
+ logger.error(f" ✗ Failed to enable SSE")
95
+
96
+ # Summary
97
+ logger.info("\n=== SUMMARY ===")
98
+ logger.info(f"Tables with SSE: {len(tables_with_sse)}")
99
+ logger.info(f"Tables without SSE: {len(tables_without_sse)}")
100
+
101
+ if dry_run and tables_without_sse:
102
+ logger.info(f"To enable SSE on {len(tables_without_sse)} tables, run with --no-dry-run")
103
+ elif not dry_run:
104
+ logger.info(f"Successfully enabled SSE on {len(tables_updated)} tables")
105
+
106
+ except Exception as e:
107
+ logger.error(f"Failed to process DynamoDB encryption: {e}")
108
+ raise
@@ -0,0 +1,134 @@
1
+ """
2
+ EC2 Public IP Analysis - Identify instances with public IP addresses.
3
+ """
4
+
5
+ import logging
6
+ from typing import Any, Dict, List
7
+
8
+ import click
9
+ from botocore.exceptions import ClientError
10
+
11
+ from .commons import display_aws_account_info, get_client
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def is_vpc_public(vpc_id: str) -> bool:
17
+ """Check if VPC has internet connectivity (IGW or NAT gateway)."""
18
+ try:
19
+ ec2 = get_client("ec2")
20
+
21
+ # Check for internet gateway attached to VPC
22
+ igw_response = ec2.describe_internet_gateways(Filters=[{"Name": "attachment.vpc-id", "Values": [vpc_id]}])
23
+ has_igw = len(igw_response.get("InternetGateways", [])) > 0
24
+
25
+ # Check for NAT gateways in VPC
26
+ nat_response = ec2.describe_nat_gateways(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
27
+ has_nat = any(gw.get("State") == "available" for gw in nat_response.get("NatGateways", []))
28
+
29
+ return has_igw or has_nat
30
+
31
+ except ClientError as e:
32
+ logger.error(f"Failed to check VPC connectivity for {vpc_id}: {e}")
33
+ return False
34
+
35
+
36
+ def get_instance_public_ips(instance: Dict[str, Any]) -> List[str]:
37
+ """Get all public IP addresses and DNS names for an EC2 instance."""
38
+ public_ips = set()
39
+
40
+ # Instance-level public IP and DNS
41
+ if instance.get("PublicIpAddress"):
42
+ public_ips.add(instance["PublicIpAddress"])
43
+ if instance.get("PublicDnsName") and instance["PublicDnsName"]:
44
+ public_ips.add(instance["PublicDnsName"])
45
+
46
+ # Network interface public IPs and DNS
47
+ for interface in instance.get("NetworkInterfaces", []):
48
+ association = interface.get("Association", {})
49
+ if association.get("PublicIp"):
50
+ public_ips.add(association["PublicIp"])
51
+ if association.get("PublicDnsName") and association["PublicDnsName"]:
52
+ public_ips.add(association["PublicDnsName"])
53
+
54
+ return list(public_ips)
55
+
56
+
57
+ @click.command()
58
+ @click.option("--instance-id", multiple=True, help="Specific instance IDs to check (checks all if not provided)")
59
+ @click.option("--show-private", is_flag=True, help="Also show instances without public IPs")
60
+ def get_public_ips(instance_id: tuple, show_private: bool):
61
+ """Analyze EC2 instances for public IP addresses and VPC connectivity."""
62
+ logger.info(f"Analyzing EC2 public IPs in {display_aws_account_info()}")
63
+
64
+ try:
65
+ ec2 = get_client("ec2")
66
+
67
+ # Build query filters
68
+ if instance_id:
69
+ logger.info(f"Checking specific instances: {list(instance_id)}")
70
+ response = ec2.describe_instances(Filters=[{"Name": "instance-id", "Values": list(instance_id)}])
71
+ else:
72
+ logger.info("Checking all EC2 instances")
73
+ response = ec2.describe_instances()
74
+
75
+ # Track results
76
+ instances_with_public_ips = []
77
+ instances_without_public_ips = []
78
+ total_instances = 0
79
+
80
+ # Process all instances
81
+ for reservation in response["Reservations"]:
82
+ for instance in reservation["Instances"]:
83
+ total_instances += 1
84
+ instance_id = instance["InstanceId"]
85
+ instance_state = instance.get("State", {}).get("Name", "unknown")
86
+ vpc_id = instance.get("VpcId", "unknown")
87
+
88
+ # Get public IPs for this instance
89
+ public_ips = get_instance_public_ips(instance)
90
+ vpc_is_public = is_vpc_public(vpc_id)
91
+
92
+ instance_info = {
93
+ "instance_id": instance_id,
94
+ "state": instance_state,
95
+ "vpc_id": vpc_id,
96
+ "public_ips": public_ips,
97
+ "vpc_has_internet": vpc_is_public,
98
+ }
99
+
100
+ if public_ips:
101
+ instances_with_public_ips.append(instance_info)
102
+ logger.info(f"Instance {instance_id} ({instance_state})")
103
+ logger.info(f" Public IPs: {', '.join(public_ips)}")
104
+ logger.info(f" VPC {vpc_id} has internet: {vpc_is_public}")
105
+ else:
106
+ instances_without_public_ips.append(instance_info)
107
+ if show_private:
108
+ logger.info(f"Instance {instance_id} ({instance_state})")
109
+ logger.info(f" No public IPs")
110
+ logger.info(f" VPC {vpc_id} has internet: {vpc_is_public}")
111
+
112
+ # Summary
113
+ logger.info("\n=== SUMMARY ===")
114
+ logger.info(f"Total instances: {total_instances}")
115
+ logger.info(f"Instances with public IPs: {len(instances_with_public_ips)}")
116
+ logger.info(f"Instances without public IPs: {len(instances_without_public_ips)}")
117
+
118
+ if instances_with_public_ips:
119
+ logger.warning(f"⚠ {len(instances_with_public_ips)} instances have public IP addresses")
120
+ logger.info("Review these instances for security implications")
121
+
122
+ if not show_private and instances_without_public_ips:
123
+ logger.info(f"Use --show-private to see {len(instances_without_public_ips)} instances without public IPs")
124
+
125
+ except ClientError as e:
126
+ logger.error(f"Failed to describe EC2 instances: {e}")
127
+ raise
128
+ except Exception as e:
129
+ logger.error(f"Unexpected error: {e}")
130
+ raise
131
+
132
+
133
+ if __name__ == "__main__":
134
+ get_public_ips()