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,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()
|