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,526 @@
1
+ """
2
+ Enterprise S3 Encryption Management - Automated Data Protection at Rest
3
+
4
+ ## Overview
5
+
6
+ This module provides comprehensive S3 bucket encryption management to enhance
7
+ data protection and compliance posture. S3 encryption at rest is a fundamental
8
+ security requirement for protecting sensitive data stored in cloud environments.
9
+
10
+ ## Key Features
11
+
12
+ - **Comprehensive Detection**: Identifies buckets without encryption enabled
13
+ - **Flexible Encryption**: Supports SSE-S3, SSE-KMS, and SSE-C options
14
+ - **KMS Integration**: Creates and manages customer-managed encryption keys
15
+ - **Bulk Operations**: Efficiently processes all buckets in an account
16
+ - **Compliance Integration**: Supports CIS, NIST, SOC2, and PCI DSS requirements
17
+ - **Cost Optimization**: Balanced approach between security and cost
18
+
19
+ ## Encryption Options
20
+
21
+ **SSE-S3 (Server-Side Encryption with Amazon S3-Managed Keys):**
22
+ - Simplest option with no additional cost
23
+ - Automatic key management by AWS
24
+ - Good for basic encryption requirements
25
+
26
+ **SSE-KMS (Server-Side Encryption with AWS KMS):**
27
+ - Customer-managed encryption keys
28
+ - Detailed access logging and control
29
+ - Integration with AWS CloudTrail
30
+ - Additional per-request charges apply
31
+
32
+ **SSE-C (Server-Side Encryption with Customer-Provided Keys):**
33
+ - Customer provides encryption keys
34
+ - Maximum control over key management
35
+ - Requires client-side key management
36
+
37
+ ## Usage Examples
38
+
39
+ ```python
40
+ # Audit mode - detect buckets without encryption (safe)
41
+ python s3_encryption.py --dry-run
42
+
43
+ # Enable SSE-S3 encryption (default)
44
+ python s3_encryption.py --encryption-type sse-s3
45
+
46
+ # Enable SSE-KMS with new customer-managed key
47
+ python s3_encryption.py --encryption-type sse-kms --create-kms-key
48
+ ```
49
+
50
+ ## Important Security Notes
51
+
52
+ ⚠️ **COST IMPACT**: SSE-KMS incurs additional charges per request
53
+ ⚠️ **KEY MANAGEMENT**: Customer-managed keys require proper lifecycle management
54
+ ⚠️ **COMPLIANCE**: Some regulations require specific encryption types
55
+
56
+ Version: 0.7.6 - Enterprise Production Ready
57
+ Compliance: CIS AWS Foundations 2.1.1, SOC2 A1.2, PCI DSS 3.4
58
+ """
59
+
60
+ import logging
61
+ from typing import Any, Dict, List, Optional, Tuple
62
+
63
+ import click
64
+ from botocore.exceptions import BotoCoreError, ClientError
65
+
66
+ from .commons import display_aws_account_info, get_client
67
+
68
+ # Configure enterprise logging
69
+ logger = logging.getLogger(__name__)
70
+ logger.setLevel(logging.INFO)
71
+
72
+
73
+ def check_bucket_encryption_status(bucket_name: str, s3_client) -> Tuple[bool, Optional[Dict[str, Any]]]:
74
+ """
75
+ Check the current encryption configuration for an S3 bucket.
76
+
77
+ This function queries the S3 service to determine whether server-side encryption
78
+ is currently enabled for the specified bucket. It handles various edge cases
79
+ and permission scenarios that may occur in enterprise environments.
80
+
81
+ ## Implementation Details
82
+
83
+ - Uses S3 GetBucketEncryption API
84
+ - Handles permission and access errors gracefully
85
+ - Returns both status and configuration details
86
+ - Provides structured error logging for troubleshooting
87
+
88
+ ## Security Considerations
89
+
90
+ - Requires s3:GetBucketEncryption permission
91
+ - May encounter cross-region access restrictions
92
+ - Bucket policies may deny encryption configuration access
93
+
94
+ Args:
95
+ bucket_name (str): S3 bucket name to check
96
+ Must be a valid S3 bucket name format
97
+ s3_client: Initialized boto3 S3 client instance
98
+
99
+ Returns:
100
+ Tuple[bool, Optional[Dict[str, Any]]]: (is_encrypted, encryption_config)
101
+ - is_encrypted: True if encryption is configured
102
+ - encryption_config: Current encryption configuration or None
103
+
104
+ Raises:
105
+ ClientError: If S3 API access fails with unexpected errors
106
+ ValueError: If bucket_name is invalid
107
+
108
+ Example:
109
+ >>> s3_client = boto3.client('s3')
110
+ >>> is_encrypted, config = check_bucket_encryption_status('my-bucket', s3_client)
111
+ >>> if is_encrypted:
112
+ ... print(f"Encryption: {config['Rules'][0]['ApplyServerSideEncryptionByDefault']['SSEAlgorithm']}")
113
+ ... else:
114
+ ... print("Encryption is not configured")
115
+ """
116
+
117
+ # Input validation
118
+ if not bucket_name or not isinstance(bucket_name, str):
119
+ raise ValueError(f"Invalid bucket_name: {bucket_name}. Must be a non-empty string.")
120
+
121
+ logger.debug(f"Checking encryption status for bucket: {bucket_name}")
122
+
123
+ try:
124
+ # Query bucket encryption configuration
125
+ encryption_response = s3_client.get_bucket_encryption(Bucket=bucket_name)
126
+
127
+ # Check if encryption configuration exists
128
+ encryption_config = encryption_response.get("ServerSideEncryptionConfiguration", {})
129
+ rules = encryption_config.get("Rules", [])
130
+
131
+ is_encrypted = bool(rules)
132
+
133
+ if is_encrypted:
134
+ # Extract encryption details
135
+ default_encryption = rules[0].get("ApplyServerSideEncryptionByDefault", {})
136
+ algorithm = default_encryption.get("SSEAlgorithm", "Unknown")
137
+ kms_key = default_encryption.get("KMSMasterKeyID", "")
138
+
139
+ logger.debug(
140
+ f"Bucket {bucket_name} has {algorithm} encryption" + (f" with key {kms_key}" if kms_key else "")
141
+ )
142
+ else:
143
+ logger.debug(f"Bucket {bucket_name} does not have encryption configured")
144
+
145
+ return is_encrypted, encryption_config
146
+
147
+ except ClientError as e:
148
+ error_code = e.response.get("Error", {}).get("Code", "Unknown")
149
+ error_message = e.response.get("Error", {}).get("Message", str(e))
150
+
151
+ # Handle specific S3 errors gracefully
152
+ if error_code == "NoSuchBucket":
153
+ logger.warning(f"Bucket not found: {bucket_name}")
154
+ return False, None
155
+ elif error_code in ["AccessDenied", "Forbidden"]:
156
+ logger.warning(f"Insufficient permissions to check encryption for bucket: {bucket_name}")
157
+ return False, None
158
+ elif error_code == "ServerSideEncryptionConfigurationNotFoundError":
159
+ logger.debug(f"No encryption configuration found for bucket: {bucket_name}")
160
+ return False, None
161
+ else:
162
+ logger.error(f"S3 API error checking encryption for {bucket_name}: {error_code} - {error_message}")
163
+ raise
164
+
165
+ except BotoCoreError as e:
166
+ logger.error(f"AWS service error checking encryption for {bucket_name}: {e}")
167
+ raise
168
+
169
+ except Exception as e:
170
+ logger.error(f"Unexpected error checking encryption for {bucket_name}: {e}")
171
+ raise
172
+
173
+
174
+ def create_kms_key_for_s3(description: str = "S3 Bucket Encryption Key", kms_client=None) -> Optional[str]:
175
+ """
176
+ Create a new customer-managed KMS key for S3 bucket encryption.
177
+
178
+ This function creates a new KMS key specifically designed for S3 bucket
179
+ encryption. The key includes appropriate metadata and tags for identification
180
+ and management purposes.
181
+
182
+ ## Implementation Details
183
+
184
+ - Uses KMS CreateKey API (not S3!)
185
+ - Configures key for symmetric encryption use
186
+ - Adds descriptive tags for management
187
+ - Returns key ID for immediate use
188
+
189
+ ## Security Benefits
190
+
191
+ - **Customer Control**: Full control over key lifecycle
192
+ - **Access Logging**: CloudTrail logs all key usage
193
+ - **Key Rotation**: Supports automatic annual rotation
194
+ - **Cross-Account**: Can be shared across accounts if needed
195
+
196
+ Args:
197
+ description (str): Human-readable description for the KMS key
198
+ kms_client: Initialized boto3 KMS client instance
199
+
200
+ Returns:
201
+ Optional[str]: KMS key ID if creation successful, None otherwise
202
+
203
+ Raises:
204
+ ClientError: If KMS API access fails with unexpected errors
205
+
206
+ Example:
207
+ >>> kms_client = boto3.client('kms')
208
+ >>> key_id = create_kms_key_for_s3("Production S3 Encryption", kms_client)
209
+ >>> if key_id:
210
+ ... print(f"Created KMS key: {key_id}")
211
+ ... else:
212
+ ... print("Failed to create KMS key")
213
+ """
214
+
215
+ if not kms_client:
216
+ raise ValueError("KMS client is required for key creation")
217
+
218
+ logger.info("🔐 Creating new KMS key for S3 bucket encryption...")
219
+
220
+ try:
221
+ # Create KMS key with appropriate configuration
222
+ key_response = kms_client.create_key(
223
+ Description=description,
224
+ KeyUsage="ENCRYPT_DECRYPT",
225
+ Origin="AWS_KMS",
226
+ KeySpec="SYMMETRIC_DEFAULT", # Required for S3 encryption
227
+ Tags=[
228
+ {"TagKey": "Purpose", "TagValue": "S3-Bucket-Encryption"},
229
+ {"TagKey": "CreatedBy", "TagValue": "CloudOps-Remediation-Script"},
230
+ {"TagKey": "Service", "TagValue": "Amazon-S3"},
231
+ ],
232
+ )
233
+
234
+ key_id = key_response["KeyMetadata"]["KeyId"]
235
+ key_arn = key_response["KeyMetadata"]["Arn"]
236
+
237
+ logger.info(f"✅ Successfully created KMS key: {key_id}")
238
+ logger.info(f" 📋 Key ARN: {key_arn}")
239
+
240
+ # Optionally enable key rotation
241
+ try:
242
+ kms_client.enable_key_rotation(KeyId=key_id)
243
+ logger.info(f"✅ Enabled automatic key rotation for: {key_id}")
244
+ except ClientError as e:
245
+ logger.warning(f"⚠️ Could not enable key rotation: {e}")
246
+
247
+ return key_id
248
+
249
+ except ClientError as e:
250
+ error_code = e.response.get("Error", {}).get("Code", "Unknown")
251
+ error_message = e.response.get("Error", {}).get("Message", str(e))
252
+
253
+ logger.error(f"❌ KMS API error creating key: {error_code} - {error_message}")
254
+
255
+ # Handle specific KMS errors
256
+ if error_code in ["AccessDenied", "UnauthorizedOperation"]:
257
+ logger.error("🔒 Insufficient permissions to create KMS keys")
258
+ logger.error(" Required permissions: kms:CreateKey, kms:TagResource, kms:EnableKeyRotation")
259
+ elif error_code == "LimitExceededException":
260
+ logger.error("📊 KMS key limit exceeded - consider deleting unused keys")
261
+
262
+ return None
263
+
264
+ except BotoCoreError as e:
265
+ logger.error(f"❌ AWS service error creating KMS key: {e}")
266
+ return None
267
+
268
+ except Exception as e:
269
+ logger.error(f"❌ Unexpected error creating KMS key: {e}")
270
+ raise
271
+
272
+
273
+ def enable_bucket_encryption(bucket_name: str, encryption_type: str, kms_key_id: Optional[str], s3_client) -> bool:
274
+ """
275
+ Enable server-side encryption for a specific S3 bucket.
276
+
277
+ This function configures S3 server-side encryption for the specified bucket
278
+ using the requested encryption type and key. This is essential for data
279
+ protection and compliance requirements.
280
+
281
+ ## Implementation Details
282
+
283
+ - Uses S3 PutBucketEncryption API
284
+ - Supports SSE-S3 and SSE-KMS encryption types
285
+ - Validates encryption configuration before application
286
+ - Provides comprehensive error handling and logging
287
+
288
+ ## Security Benefits
289
+
290
+ - **Data Protection**: Encrypts data at rest automatically
291
+ - **Compliance**: Meets regulatory requirements for data encryption
292
+ - **Key Management**: Supports both AWS and customer-managed keys
293
+ - **Performance**: Encryption is transparent to applications
294
+
295
+ Args:
296
+ bucket_name (str): S3 bucket to enable encryption for
297
+ encryption_type (str): Encryption type ('sse-s3' or 'sse-kms')
298
+ kms_key_id (str): KMS key ID for SSE-KMS (optional for SSE-S3)
299
+ s3_client: Initialized boto3 S3 client instance
300
+
301
+ Returns:
302
+ bool: True if encryption was successfully enabled, False otherwise
303
+
304
+ Raises:
305
+ ValueError: If parameters are invalid
306
+ ClientError: If S3 API access fails with unexpected errors
307
+
308
+ Example:
309
+ >>> s3_client = boto3.client('s3')
310
+ >>> success = enable_bucket_encryption('my-bucket', 'sse-s3', None, s3_client)
311
+ >>> if success:
312
+ ... print("Encryption enabled successfully")
313
+ ... else:
314
+ ... print("Failed to enable encryption - check logs")
315
+ """
316
+
317
+ # Input validation
318
+ if not bucket_name or not isinstance(bucket_name, str):
319
+ raise ValueError(f"Invalid bucket_name: {bucket_name}. Must be a non-empty string.")
320
+
321
+ if encryption_type not in ["sse-s3", "sse-kms"]:
322
+ raise ValueError(f"Invalid encryption_type: {encryption_type}. Must be 'sse-s3' or 'sse-kms'.")
323
+
324
+ if encryption_type == "sse-kms" and not kms_key_id:
325
+ raise ValueError("KMS key ID is required for SSE-KMS encryption.")
326
+
327
+ logger.info(f"🔒 Enabling {encryption_type.upper()} encryption for bucket: {bucket_name}")
328
+ if kms_key_id:
329
+ logger.info(f" 🔑 Using KMS key: {kms_key_id}")
330
+
331
+ try:
332
+ # Build encryption configuration based on type
333
+ if encryption_type == "sse-s3":
334
+ encryption_config = {"Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]}
335
+ else: # sse-kms
336
+ encryption_config = {
337
+ "Rules": [
338
+ {"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "aws:kms", "KMSMasterKeyID": kms_key_id}}
339
+ ]
340
+ }
341
+
342
+ # Apply encryption configuration
343
+ s3_client.put_bucket_encryption(Bucket=bucket_name, ServerSideEncryptionConfiguration=encryption_config)
344
+
345
+ logger.info(f"✅ Successfully enabled {encryption_type.upper()} encryption for bucket: {bucket_name}")
346
+ return True
347
+
348
+ except ClientError as e:
349
+ error_code = e.response.get("Error", {}).get("Code", "Unknown")
350
+ error_message = e.response.get("Error", {}).get("Message", str(e))
351
+
352
+ # Handle specific S3 errors with informative messages
353
+ if error_code == "NoSuchBucket":
354
+ logger.error(f"❌ Bucket not found: {bucket_name}")
355
+ elif error_code in ["AccessDenied", "Forbidden"]:
356
+ logger.error(f"🔒 Insufficient permissions to enable encryption for bucket: {bucket_name}")
357
+ logger.error(" Required permissions: s3:PutBucketEncryption")
358
+ elif error_code == "InvalidRequest":
359
+ logger.error(f"❌ Invalid encryption configuration for bucket: {bucket_name}")
360
+ logger.error(f" Check encryption type and KMS key: {kms_key_id}")
361
+ elif error_code == "KMSNotFoundException":
362
+ logger.error(f"❌ KMS key not found: {kms_key_id}")
363
+ else:
364
+ logger.error(f"❌ S3 API error enabling encryption for {bucket_name}: {error_code} - {error_message}")
365
+
366
+ return False
367
+
368
+ except BotoCoreError as e:
369
+ logger.error(f"❌ AWS service error enabling encryption for {bucket_name}: {e}")
370
+ return False
371
+
372
+ except Exception as e:
373
+ logger.error(f"❌ Unexpected error enabling encryption for {bucket_name}: {e}")
374
+ raise
375
+
376
+
377
+ @click.command()
378
+ @click.option(
379
+ "--dry-run", is_flag=True, default=True, help="Preview mode - show buckets that need encryption without enabling it"
380
+ )
381
+ @click.option(
382
+ "--encryption-type",
383
+ type=click.Choice(["sse-s3", "sse-kms"]),
384
+ default="sse-s3",
385
+ help="Encryption type: sse-s3 (free) or sse-kms (customer-managed)",
386
+ )
387
+ @click.option("--kms-key-id", type=str, help="Existing KMS key ID for SSE-KMS (creates new if not provided)")
388
+ @click.option("--create-kms-key", is_flag=True, help="Create a new KMS key for SSE-KMS encryption")
389
+ @click.option("--region", type=str, help="AWS region to scan (defaults to current region)")
390
+ @click.option("--bucket-filter", type=str, help="Filter buckets by name pattern (supports wildcards)")
391
+ @click.option("--output-file", type=str, help="Save results to CSV file")
392
+ def check_and_enable_encryption(
393
+ dry_run: bool,
394
+ encryption_type: str,
395
+ kms_key_id: Optional[str],
396
+ create_kms_key: bool,
397
+ region: Optional[str],
398
+ bucket_filter: Optional[str],
399
+ output_file: Optional[str],
400
+ ):
401
+ """
402
+ Enterprise S3 Encryption Management - Bulk encryption enablement for data protection.
403
+
404
+ This command provides comprehensive detection and enablement of S3 server-side encryption
405
+ across all buckets in your AWS account. Encryption at rest is a fundamental security
406
+ requirement for protecting sensitive data stored in cloud environments.
407
+
408
+ Examples:
409
+ # Safe audit of all buckets (recommended first step)
410
+ python s3_encryption.py --dry-run
411
+
412
+ # Enable SSE-S3 encryption (free)
413
+ python s3_encryption.py --no-dry-run --encryption-type sse-s3
414
+ """
415
+
416
+ # Input validation
417
+ if encryption_type == "sse-kms" and not kms_key_id and not create_kms_key:
418
+ raise click.ClickException("For SSE-KMS encryption, either provide --kms-key-id or use --create-kms-key")
419
+
420
+ # Enhanced logging for operation start
421
+ operation_mode = "DRY-RUN (Safe Audit)" if dry_run else "ENABLEMENT (Configuration Change)"
422
+ logger.info(f"🔒 Starting S3 Encryption Analysis - Mode: {operation_mode}")
423
+ logger.info(f"📊 Configuration: region={region or 'default'}, filter={bucket_filter or 'none'}")
424
+ logger.info(
425
+ f"🔐 Encryption settings: type={encryption_type}, kms_key={kms_key_id or 'create new' if create_kms_key else 'N/A'}"
426
+ )
427
+
428
+ # Display account information for verification
429
+ account_info = display_aws_account_info()
430
+ logger.info(f"🏢 {account_info}")
431
+
432
+ if not dry_run:
433
+ logger.warning("⚠️ CONFIGURATION MODE ENABLED - Bucket encryption will be enabled!")
434
+ if encryption_type == "sse-kms":
435
+ logger.warning("⚠️ SSE-KMS incurs additional charges per request!")
436
+
437
+ try:
438
+ # Initialize AWS clients
439
+ s3_client = get_client("s3", region_name=region)
440
+ kms_client = get_client("kms", region_name=region) if encryption_type == "sse-kms" else None
441
+ logger.debug(f"Initialized AWS clients for region: {region or 'default'}")
442
+
443
+ # Create KMS key if needed for SSE-KMS
444
+ effective_kms_key_id = kms_key_id
445
+ if encryption_type == "sse-kms" and create_kms_key and not dry_run:
446
+ logger.info("🔐 Creating new KMS key for S3 encryption...")
447
+ effective_kms_key_id = create_kms_key_for_s3(
448
+ f"S3 Bucket Encryption Key - Created by CloudOps Remediation", kms_client
449
+ )
450
+ if not effective_kms_key_id:
451
+ logger.error("❌ Failed to create KMS key - aborting encryption enablement")
452
+ return
453
+
454
+ # List all buckets
455
+ try:
456
+ response = s3_client.list_buckets()
457
+ all_buckets = response.get("Buckets", [])
458
+ except ClientError as e:
459
+ error_code = e.response.get("Error", {}).get("Code", "Unknown")
460
+ logger.error(f"❌ Failed to list S3 buckets: {error_code}")
461
+ raise
462
+
463
+ logger.info(f"📋 Found {len(all_buckets)} total buckets to analyze")
464
+
465
+ # Process buckets
466
+ buckets_without_encryption = []
467
+ buckets_with_encryption = []
468
+ successful_enablements = 0
469
+
470
+ for bucket in all_buckets:
471
+ bucket_name = bucket["Name"]
472
+
473
+ # Apply bucket filtering if specified
474
+ if bucket_filter:
475
+ if bucket_filter.replace("*", "") not in bucket_name:
476
+ continue
477
+
478
+ try:
479
+ # Check current encryption status
480
+ is_encrypted, encryption_config = check_bucket_encryption_status(bucket_name, s3_client)
481
+
482
+ if is_encrypted:
483
+ buckets_with_encryption.append(bucket_name)
484
+ logger.debug(f"✅ ENCRYPTED: {bucket_name}")
485
+ else:
486
+ buckets_without_encryption.append(bucket_name)
487
+ logger.info(f"🎯 NEEDS ENCRYPTION: {bucket_name}")
488
+
489
+ # Enable encryption if not in dry-run mode
490
+ if not dry_run:
491
+ success = enable_bucket_encryption(
492
+ bucket_name, encryption_type, effective_kms_key_id, s3_client
493
+ )
494
+ if success:
495
+ successful_enablements += 1
496
+
497
+ except Exception as e:
498
+ logger.warning(f"⚠️ Could not analyze bucket {bucket_name}: {e}")
499
+ continue
500
+
501
+ # Generate summary
502
+ needs_encryption_count = len(buckets_without_encryption)
503
+ already_encrypted_count = len(buckets_with_encryption)
504
+
505
+ logger.info("📊 S3 ENCRYPTION ANALYSIS SUMMARY:")
506
+ logger.info(f" ✅ Buckets with encryption: {already_encrypted_count}")
507
+ logger.info(f" 🎯 Buckets needing encryption: {needs_encryption_count}")
508
+
509
+ if not dry_run and successful_enablements > 0:
510
+ logger.info(f" 🔒 Successfully enabled encryption: {successful_enablements} buckets")
511
+
512
+ # Final summary
513
+ if dry_run and needs_encryption_count > 0:
514
+ logger.info(
515
+ f"💡 To enable {encryption_type.upper()} encryption on {needs_encryption_count} buckets, run with --no-dry-run"
516
+ )
517
+ elif not dry_run:
518
+ logger.info("✅ ENCRYPTION ENABLEMENT COMPLETE")
519
+
520
+ except Exception as e:
521
+ logger.error(f"❌ Error during S3 encryption analysis: {e}")
522
+ raise
523
+
524
+
525
+ if __name__ == "__main__":
526
+ check_and_enable_encryption()
@@ -0,0 +1,143 @@
1
+ """
2
+ S3 HTTPS-Only Policy - Enforce secure transport for all S3 bucket access.
3
+ """
4
+
5
+ import json
6
+ import logging
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 check_https_only_policy(bucket_name):
17
+ """Check if S3 bucket has HTTPS-only policy enabled."""
18
+ try:
19
+ s3 = get_client("s3")
20
+
21
+ # Get existing bucket policy
22
+ response = s3.get_bucket_policy(Bucket=bucket_name)
23
+ policy_json = json.loads(response["Policy"])
24
+
25
+ # Look for HTTPS-only deny statement
26
+ for statement in policy_json.get("Statement", []):
27
+ if (
28
+ statement.get("Effect") == "Deny"
29
+ and statement.get("Condition", {}).get("Bool", {}).get("aws:SecureTransport") == "false"
30
+ ):
31
+ logger.debug(f"Bucket '{bucket_name}' has HTTPS-only policy")
32
+ return True
33
+
34
+ logger.debug(f"Bucket '{bucket_name}' does not have HTTPS-only policy")
35
+ return False
36
+
37
+ except ClientError as e:
38
+ error_code = e.response.get("Error", {}).get("Code", "Unknown")
39
+ if error_code == "NoSuchBucketPolicy":
40
+ logger.debug(f"Bucket '{bucket_name}' has no bucket policy")
41
+ return False
42
+ else:
43
+ logger.error(f"Failed to check bucket policy for '{bucket_name}': {e}")
44
+ return False
45
+
46
+
47
+ def update_https_only_policy(bucket_name):
48
+ """Add HTTPS-only policy to S3 bucket."""
49
+ try:
50
+ s3 = get_client("s3")
51
+
52
+ # Get existing bucket policy or create new one
53
+ try:
54
+ response = s3.get_bucket_policy(Bucket=bucket_name)
55
+ policy_json = json.loads(response["Policy"])
56
+ except ClientError as e:
57
+ if e.response.get("Error", {}).get("Code") == "NoSuchBucketPolicy":
58
+ # Create new policy if none exists
59
+ policy_json = {"Version": "2012-10-17", "Statement": []}
60
+ else:
61
+ raise
62
+
63
+ # Create HTTPS-only policy statement
64
+ https_only_statement = {
65
+ "Sid": "AllowSSLRequestsOnly",
66
+ "Effect": "Deny",
67
+ "Principal": "*",
68
+ "Action": "s3:*",
69
+ "Resource": [f"arn:aws:s3:::{bucket_name}/*", f"arn:aws:s3:::{bucket_name}"],
70
+ "Condition": {"Bool": {"aws:SecureTransport": "false"}},
71
+ }
72
+
73
+ # Add the statement to the policy
74
+ policy_json["Statement"].append(https_only_statement)
75
+
76
+ # Apply the updated policy
77
+ s3.put_bucket_policy(Bucket=bucket_name, Policy=json.dumps(policy_json))
78
+ logger.info(f"Applied HTTPS-only policy to bucket '{bucket_name}'")
79
+ return True
80
+
81
+ except ClientError as e:
82
+ logger.error(f"Failed to update policy for bucket '{bucket_name}': {e}")
83
+ return False
84
+
85
+
86
+ @click.command()
87
+ @click.option("--dry-run", is_flag=True, default=True, help="Preview mode - show actions without making changes")
88
+ def enable_ssl_secure_on_all_buckets(dry_run: bool = True):
89
+ """Enforce HTTPS-only access for all S3 buckets."""
90
+ logger.info(f"Checking S3 HTTPS policies in {display_aws_account_info()}")
91
+
92
+ try:
93
+ s3 = get_client("s3")
94
+ response = s3.list_buckets()
95
+ buckets = response.get("Buckets", [])
96
+
97
+ if not buckets:
98
+ logger.info("No S3 buckets found")
99
+ return
100
+
101
+ logger.info(f"Found {len(buckets)} buckets to check")
102
+
103
+ # Track results
104
+ buckets_with_https = []
105
+ buckets_without_https = []
106
+ buckets_updated = []
107
+
108
+ # Check each bucket
109
+ for bucket in buckets:
110
+ bucket_name = bucket["Name"]
111
+ logger.info(f"Checking bucket: {bucket_name}")
112
+
113
+ has_https_policy = check_https_only_policy(bucket_name)
114
+
115
+ if has_https_policy:
116
+ buckets_with_https.append(bucket_name)
117
+ logger.info(f" ✓ HTTPS-only policy already enabled")
118
+ else:
119
+ buckets_without_https.append(bucket_name)
120
+ logger.info(f" ✗ HTTPS-only policy not found")
121
+
122
+ # Apply policy if not in dry-run mode
123
+ if not dry_run:
124
+ logger.info(f" → Adding HTTPS-only policy to '{bucket_name}'...")
125
+ if update_https_only_policy(bucket_name):
126
+ buckets_updated.append(bucket_name)
127
+ logger.info(f" ✓ Successfully applied HTTPS-only policy")
128
+ else:
129
+ logger.error(f" ✗ Failed to apply HTTPS-only policy")
130
+
131
+ # Summary
132
+ logger.info("\n=== SUMMARY ===")
133
+ logger.info(f"Buckets with HTTPS policy: {len(buckets_with_https)}")
134
+ logger.info(f"Buckets without HTTPS policy: {len(buckets_without_https)}")
135
+
136
+ if dry_run and buckets_without_https:
137
+ logger.info(f"To apply HTTPS policies to {len(buckets_without_https)} buckets, run with --no-dry-run")
138
+ elif not dry_run:
139
+ logger.info(f"Successfully applied HTTPS policies to {len(buckets_updated)} buckets")
140
+
141
+ except Exception as e:
142
+ logger.error(f"Failed to process S3 HTTPS policies: {e}")
143
+ raise