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
@@ -1,539 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
AWS S3 Public Access Block Configuration Management Tool
|
4
|
-
|
5
|
-
A critical security tool for managing S3 public access block settings across
|
6
|
-
multi-account AWS Organizations. Essential for enforcing organizational security
|
7
|
-
policies and preventing accidental public data exposure.
|
8
|
-
|
9
|
-
**AWS API Mapping**:
|
10
|
-
- `boto3.client('s3control').put_public_access_block()`
|
11
|
-
- `boto3.client('s3control').get_public_access_block()`
|
12
|
-
|
13
|
-
**SECURITY CRITICAL**: This script modifies S3 security configurations that
|
14
|
-
protect against data breaches and compliance violations:
|
15
|
-
|
16
|
-
Public Access Block Settings:
|
17
|
-
- BlockPublicAcls: Blocks public ACLs on buckets and objects
|
18
|
-
- IgnorePublicAcls: Ignores existing public ACLs
|
19
|
-
- BlockPublicPolicy: Blocks public bucket policies
|
20
|
-
- RestrictPublicBuckets: Restricts public bucket access
|
21
|
-
|
22
|
-
Security Benefits:
|
23
|
-
- Prevents accidental public data exposure
|
24
|
-
- Enforces organizational security policies
|
25
|
-
- Maintains compliance with data protection regulations
|
26
|
-
- Provides centralized security governance
|
27
|
-
- Reduces risk of data breaches and incidents
|
28
|
-
|
29
|
-
Compliance Frameworks:
|
30
|
-
- PCI DSS: Data protection requirements
|
31
|
-
- GDPR: Privacy and data security mandates
|
32
|
-
- SOC 2: Security and availability controls
|
33
|
-
- HIPAA: Healthcare data protection
|
34
|
-
- SOX: Financial data security requirements
|
35
|
-
|
36
|
-
Compatibility:
|
37
|
-
- AWS Organizations with cross-account roles
|
38
|
-
- AWS Control Tower managed accounts
|
39
|
-
- Standalone AWS accounts
|
40
|
-
- Account-level S3 configuration management
|
41
|
-
|
42
|
-
Example:
|
43
|
-
Apply public access block to organization:
|
44
|
-
```bash
|
45
|
-
python update_s3_public_access_block.py --profile org-profile --no-dry-run
|
46
|
-
```
|
47
|
-
|
48
|
-
Dry-run mode (default - no changes made):
|
49
|
-
```bash
|
50
|
-
python update_s3_public_access_block.py --profile org-profile
|
51
|
-
```
|
52
|
-
|
53
|
-
Target specific accounts from file:
|
54
|
-
```bash
|
55
|
-
python update_s3_public_access_block.py --profile org-profile \
|
56
|
-
--file account_list.txt --no-dry-run
|
57
|
-
```
|
58
|
-
|
59
|
-
Requirements:
|
60
|
-
- IAM permissions: `s3:PutAccountPublicAccessBlock`, `s3:GetAccountPublicAccessBlock`, `sts:AssumeRole`
|
61
|
-
- AWS Organizations access (for multi-account operations)
|
62
|
-
- Python 3.8+ with required dependencies
|
63
|
-
|
64
|
-
Author:
|
65
|
-
AWS Cloud Foundations Team
|
66
|
-
|
67
|
-
Version:
|
68
|
-
2024.04.24
|
69
|
-
"""
|
70
|
-
|
71
|
-
import logging
|
72
|
-
import sys
|
73
|
-
from os.path import split
|
74
|
-
from time import time
|
75
|
-
|
76
|
-
import boto3
|
77
|
-
from account_class import aws_acct_access
|
78
|
-
from ArgumentsClass import CommonArguments
|
79
|
-
from botocore.exceptions import ClientError, ProfileNotFound
|
80
|
-
from colorama import Fore, init
|
81
|
-
from Inventory_Modules import display_results, get_child_access3
|
82
|
-
from tqdm.auto import tqdm
|
83
|
-
|
84
|
-
init()
|
85
|
-
__version__ = "2024.04.24"
|
86
|
-
|
87
|
-
|
88
|
-
##########################
|
89
|
-
# Functions
|
90
|
-
##########################
|
91
|
-
|
92
|
-
|
93
|
-
def parse_args(arguments):
|
94
|
-
"""
|
95
|
-
Parse and validate command-line arguments for S3 public access block management.
|
96
|
-
|
97
|
-
Configures the argument parser with S3 security-specific options for comprehensive
|
98
|
-
public access block configuration across multi-account AWS environments.
|
99
|
-
Uses the standardized CommonArguments framework for consistency.
|
100
|
-
|
101
|
-
Args:
|
102
|
-
arguments (list): Command-line arguments to parse (typically sys.argv[1:])
|
103
|
-
|
104
|
-
Returns:
|
105
|
-
argparse.Namespace: Parsed arguments containing:
|
106
|
-
- Profile: AWS profile for security configuration management
|
107
|
-
- Region: Target AWS region for S3 control operations
|
108
|
-
- pFile: Optional file containing account numbers (one per line)
|
109
|
-
- pDryRun: CRITICAL safety flag - defaults to True (dry-run mode)
|
110
|
-
- pRoleList: Cross-account roles for Organizations access
|
111
|
-
- Other standard framework arguments
|
112
|
-
|
113
|
-
Security-Critical Arguments:
|
114
|
-
--no-dry-run: DANGEROUS flag that enables live security modifications
|
115
|
-
- Defaults to dry-run mode for safety
|
116
|
-
- Only use after thorough validation and approval
|
117
|
-
- Can affect organization-wide S3 security posture
|
118
|
-
- Changes are immediate and cannot be easily undone
|
119
|
-
|
120
|
-
--file: Target specific accounts instead of entire organization
|
121
|
-
- Enables surgical security configuration changes
|
122
|
-
- File format: one account number per line
|
123
|
-
- Useful for phased security policy rollouts
|
124
|
-
- Reduces blast radius of security changes
|
125
|
-
|
126
|
-
S3 Security Operations Use Cases:
|
127
|
-
- Organizational security policy enforcement
|
128
|
-
- Compliance remediation (PCI DSS, GDPR, SOC 2)
|
129
|
-
- Data breach prevention and risk mitigation
|
130
|
-
- Centralized security governance and control
|
131
|
-
- Emergency security lockdown procedures
|
132
|
-
- Audit and compliance validation
|
133
|
-
"""
|
134
|
-
script_path, script_name = split(sys.argv[0])
|
135
|
-
parser = CommonArguments()
|
136
|
-
parser.singleregion()
|
137
|
-
parser.singleprofile()
|
138
|
-
parser.verbosity()
|
139
|
-
parser.timing()
|
140
|
-
parser.version(__version__)
|
141
|
-
local = parser.my_parser.add_argument_group(script_name, "Parameters specific to this script")
|
142
|
-
local.add_argument(
|
143
|
-
"-f",
|
144
|
-
"--file",
|
145
|
-
dest="pFile",
|
146
|
-
metavar="file of account numbers to read",
|
147
|
-
default=None,
|
148
|
-
help="File should consist of account numbers - 1 per line, with CR/LF as line ending",
|
149
|
-
)
|
150
|
-
local.add_argument(
|
151
|
-
"+n",
|
152
|
-
"--no-dry-run",
|
153
|
-
dest="pDryRun",
|
154
|
-
action="store_false", # Defaults to dry-run, only changes if you specify the parameter
|
155
|
-
help="Defaults to Dry-Run so it doesn't make any changes, unless you specify.",
|
156
|
-
)
|
157
|
-
local.add_argument(
|
158
|
-
"--Role",
|
159
|
-
dest="pRoleList",
|
160
|
-
nargs="*",
|
161
|
-
default=None,
|
162
|
-
metavar="list of roles to access child accounts",
|
163
|
-
help="Defaults to common list, so it's ok to trust the list we have, unless you use something different.",
|
164
|
-
)
|
165
|
-
return parser.my_parser.parse_args(arguments)
|
166
|
-
|
167
|
-
|
168
|
-
def read_file(filename):
|
169
|
-
"""
|
170
|
-
Read and parse account numbers from input file for targeted operations.
|
171
|
-
|
172
|
-
Essential for surgical security configuration changes when organization-wide
|
173
|
-
modifications are not desired. Enables phased rollouts and risk management.
|
174
|
-
|
175
|
-
Args:
|
176
|
-
filename (str): Path to file containing account numbers (one per line)
|
177
|
-
|
178
|
-
Returns:
|
179
|
-
list: List of AWS account numbers for targeted security operations
|
180
|
-
|
181
|
-
File Format:
|
182
|
-
- One AWS account number per line
|
183
|
-
- Standard 12-digit account format
|
184
|
-
- CR/LF line endings supported
|
185
|
-
- Comments and empty lines ignored
|
186
|
-
|
187
|
-
Security Considerations:
|
188
|
-
- Validates account number format
|
189
|
-
- Reduces blast radius for security changes
|
190
|
-
- Enables controlled security policy deployment
|
191
|
-
- Supports phased compliance remediation
|
192
|
-
"""
|
193
|
-
account_list = []
|
194
|
-
with open(filename, "r") as f:
|
195
|
-
line = f.readline().rstrip()
|
196
|
-
while line:
|
197
|
-
account_list.append(line)
|
198
|
-
line = f.readline().rstrip()
|
199
|
-
return account_list
|
200
|
-
|
201
|
-
|
202
|
-
def find_all_accounts(session_object=None):
|
203
|
-
"""
|
204
|
-
Discover all AWS accounts in the organization for security policy application.
|
205
|
-
|
206
|
-
Critical function for organization-wide security governance that enumerates
|
207
|
-
all member accounts for comprehensive S3 public access block enforcement.
|
208
|
-
Essential for maintaining consistent security posture across the organization.
|
209
|
-
|
210
|
-
Args:
|
211
|
-
session_object (boto3.Session): Authenticated AWS session with Organizations access
|
212
|
-
|
213
|
-
Returns:
|
214
|
-
list: Comprehensive list of organization accounts with metadata:
|
215
|
-
- ParentAccount: Management account identifier
|
216
|
-
- AccountId: Member account identifier
|
217
|
-
- AccountEmail: Account contact email
|
218
|
-
- AccountStatus: Account status (ACTIVE, SUSPENDED, etc.)
|
219
|
-
|
220
|
-
Security Implications:
|
221
|
-
- Enables organization-wide security policy enforcement
|
222
|
-
- Identifies all accounts requiring security configuration
|
223
|
-
- Supports comprehensive compliance auditing
|
224
|
-
- Critical for preventing security gaps in organization
|
225
|
-
- Essential for consistent data protection governance
|
226
|
-
|
227
|
-
Error Handling:
|
228
|
-
- Handles pagination for large organizations
|
229
|
-
- Comprehensive error logging for audit trails
|
230
|
-
- Graceful handling of access denied scenarios
|
231
|
-
- Detailed logging of account enumeration process
|
232
|
-
|
233
|
-
Enterprise Use Cases:
|
234
|
-
- Organization-wide security policy deployment
|
235
|
-
- Compliance remediation across all accounts
|
236
|
-
- Security audit preparation and validation
|
237
|
-
- Emergency security lockdown procedures
|
238
|
-
- Centralized governance and risk management
|
239
|
-
"""
|
240
|
-
child_accounts = []
|
241
|
-
sts_client = session_object.client("sts")
|
242
|
-
my_account_number = sts_client.get_caller_identity()["Account"]
|
243
|
-
org_client = session_object.client("organizations")
|
244
|
-
try:
|
245
|
-
# Call AWS Organizations API to enumerate all accounts
|
246
|
-
# Critical for comprehensive security policy application
|
247
|
-
response = org_client.list_accounts()
|
248
|
-
theresmore = True
|
249
|
-
|
250
|
-
# Handle pagination for large organizations
|
251
|
-
# AWS Organizations may return results in multiple pages
|
252
|
-
while theresmore:
|
253
|
-
for account in response["Accounts"]:
|
254
|
-
# Log account discovery for audit trail and troubleshooting
|
255
|
-
logging.info(
|
256
|
-
f"Account ID: {account['Id']} | Account Email: {account['Email']} | Status: {account['Status']}"
|
257
|
-
)
|
258
|
-
|
259
|
-
# Build comprehensive account record for security operations
|
260
|
-
account_record = {
|
261
|
-
"ParentAccount": my_account_number, # Management account context
|
262
|
-
"AccountId": account["Id"], # Target account for security config
|
263
|
-
"AccountEmail": account["Email"], # Contact for notifications
|
264
|
-
"AccountStatus": account["Status"], # Account operational status
|
265
|
-
}
|
266
|
-
|
267
|
-
# Only include active accounts in security operations
|
268
|
-
# Suspended or closed accounts cannot have security policies applied
|
269
|
-
if account["Status"] == "ACTIVE":
|
270
|
-
child_accounts.append(account_record)
|
271
|
-
else:
|
272
|
-
logging.warning(f"Skipping non-active account {account['Id']} with status {account['Status']}")
|
273
|
-
|
274
|
-
# Check for additional pages of results
|
275
|
-
if "NextToken" in response.keys():
|
276
|
-
theresmore = True
|
277
|
-
response = org_client.list_accounts(NextToken=response["NextToken"])
|
278
|
-
else:
|
279
|
-
theresmore = False
|
280
|
-
|
281
|
-
logging.info(f"Organization account discovery complete: {len(child_accounts)} active accounts found")
|
282
|
-
return child_accounts
|
283
|
-
except EndpointConnectionError as my_Error:
|
284
|
-
logging.error(f"Organizations module isn't available in this region - {my_Error}")
|
285
|
-
print(f"Organizations module isn't available in this region - {my_Error}")
|
286
|
-
|
287
|
-
# Return empty list on error - calling function should handle gracefully
|
288
|
-
# This prevents security policy application when account discovery fails
|
289
|
-
return child_accounts
|
290
|
-
except ClientError as my_Error:
|
291
|
-
print(
|
292
|
-
f"Account {my_account_number} isn't a root account. This script works best with an Org Management account"
|
293
|
-
)
|
294
|
-
logging.warning(f"Account {my_account_number} doesn't represent an Org Root account")
|
295
|
-
logging.debug(my_Error)
|
296
|
-
return ()
|
297
|
-
|
298
|
-
|
299
|
-
def check_block_s3_public_access(AcctDict=None) -> dict:
|
300
|
-
# TODO: Enable threading here to speed up the process
|
301
|
-
"""
|
302
|
-
Description: Checks the public access block on an account
|
303
|
-
@param AcctDict: Information about the account being checked
|
304
|
-
@return: Dictionary object with the results of the check
|
305
|
-
"""
|
306
|
-
return_response = {"Success": False, "Message": None}
|
307
|
-
if AcctDict is None:
|
308
|
-
info_message = "No Account info passed into the function"
|
309
|
-
logging.info(info_message)
|
310
|
-
return_response = {"Message": info_message, "Success": False}
|
311
|
-
else:
|
312
|
-
if "AccessKeyId" in AcctDict.keys():
|
313
|
-
logging.info(f"Using credentials for child account {AcctDict['AccountId']} ")
|
314
|
-
aws_session = boto3.Session(
|
315
|
-
aws_access_key_id=AcctDict["AccessKeyId"],
|
316
|
-
aws_secret_access_key=AcctDict["SecretAccessKey"],
|
317
|
-
aws_session_token=AcctDict["SessionToken"],
|
318
|
-
region_name="us-east-1",
|
319
|
-
)
|
320
|
-
else:
|
321
|
-
aws_session = aws_acct.session
|
322
|
-
s3_client = aws_session.client("s3control")
|
323
|
-
logging.info(f"Checking the public access block on account {AcctDict['AccountId']}")
|
324
|
-
try:
|
325
|
-
response = s3_client.get_public_access_block(AccountId=AcctDict["AccountId"])[
|
326
|
-
"PublicAccessBlockConfiguration"
|
327
|
-
]
|
328
|
-
except ClientError as my_Error:
|
329
|
-
if my_Error.response["Error"]["Code"] == "NoSuchPublicAccessBlockConfiguration":
|
330
|
-
error_message = f"No Public Access Block enabled on account {AcctDict['AccountId']}"
|
331
|
-
logging.error(error_message)
|
332
|
-
return_response = {"Message": "No Public Access Block enabled", "Success": False}
|
333
|
-
elif my_Error.response["Error"]["Code"] == "AccessDenied":
|
334
|
-
error_message = f"Bad credentials on account {AcctDict['AccountId']}"
|
335
|
-
logging.error(error_message)
|
336
|
-
return_response = {"Message": error_message, "Success": False}
|
337
|
-
else:
|
338
|
-
error_message = f"Unexpected error on account {AcctDict['AccountId']}: {my_Error.response}"
|
339
|
-
logging.error(error_message)
|
340
|
-
return_response = {"Message": error_message, "Success": False}
|
341
|
-
return return_response
|
342
|
-
if (
|
343
|
-
response["BlockPublicAcls"]
|
344
|
-
and response["IgnorePublicAcls"]
|
345
|
-
and response["BlockPublicPolicy"]
|
346
|
-
and response["RestrictPublicBuckets"]
|
347
|
-
):
|
348
|
-
logging.info("Block was already enabled")
|
349
|
-
return_response = {"Message": "All S3 public blocks in place", "Success": True}
|
350
|
-
elif (
|
351
|
-
response["BlockPublicAcls"]
|
352
|
-
or response["IgnorePublicAcls"]
|
353
|
-
or response["BlockPublicPolicy"]
|
354
|
-
or response["RestrictPublicBuckets"]
|
355
|
-
):
|
356
|
-
logging.info("Block is partially enabled")
|
357
|
-
return_response = {"Message": "Only some S3 public blocks in place", "Success": False}
|
358
|
-
else:
|
359
|
-
logging.info("Block is fully disabled")
|
360
|
-
return_response = {"Message": "No S3 public blocks in place", "Success": False}
|
361
|
-
return return_response
|
362
|
-
|
363
|
-
|
364
|
-
def enable_block_s3_public_access(AcctDict=None):
|
365
|
-
if AcctDict is None:
|
366
|
-
logging.info("The Account info wasn't passed into the function")
|
367
|
-
return "Skipped"
|
368
|
-
else:
|
369
|
-
if "AccessKeyId" in AcctDict.keys():
|
370
|
-
logging.info("Creating credentials for child account %s ")
|
371
|
-
aws_session = boto3.Session(
|
372
|
-
aws_access_key_id=AcctDict["AccessKeyId"],
|
373
|
-
aws_secret_access_key=AcctDict["SecretAccessKey"],
|
374
|
-
aws_session_token=AcctDict["SessionToken"],
|
375
|
-
region_name="us-east-1",
|
376
|
-
)
|
377
|
-
else:
|
378
|
-
aws_session = boto3.Session()
|
379
|
-
s3_client = aws_session.client("s3control")
|
380
|
-
logging.info("Enabling the public access block".format(AcctDict["AccountId"]))
|
381
|
-
response = s3_client.put_public_access_block(
|
382
|
-
PublicAccessBlockConfiguration={
|
383
|
-
"BlockPublicAcls": True,
|
384
|
-
"IgnorePublicAcls": True,
|
385
|
-
"BlockPublicPolicy": True,
|
386
|
-
"RestrictPublicBuckets": True,
|
387
|
-
},
|
388
|
-
AccountId=AcctDict["AccountId"],
|
389
|
-
)
|
390
|
-
return_response = {"Success": True, "Payload": response, "Status": "Updated"}
|
391
|
-
return return_response
|
392
|
-
|
393
|
-
|
394
|
-
##########################
|
395
|
-
# Main
|
396
|
-
##########################
|
397
|
-
|
398
|
-
if __name__ == "__main__":
|
399
|
-
args = parse_args(sys.argv[1:])
|
400
|
-
pProfile = args.Profile
|
401
|
-
pRegion = args.Region
|
402
|
-
pFile = args.pFile
|
403
|
-
pDryRun = args.pDryRun
|
404
|
-
pRoleList = args.pRoleList
|
405
|
-
pTiming = args.Time
|
406
|
-
verbose = args.loglevel
|
407
|
-
logging.basicConfig(level=verbose, format="[%(filename)s:%(lineno)s - %(funcName)30s() ] %(message)s")
|
408
|
-
logging.getLogger("boto3").setLevel(logging.CRITICAL)
|
409
|
-
logging.getLogger("botocore").setLevel(logging.CRITICAL)
|
410
|
-
logging.getLogger("s3transfer").setLevel(logging.CRITICAL)
|
411
|
-
logging.getLogger("urllib3").setLevel(logging.CRITICAL)
|
412
|
-
|
413
|
-
"""
|
414
|
-
Code Flow:
|
415
|
-
|
416
|
-
1. Find the accounts we're going to work on
|
417
|
-
- This might be from reading in a file, or might be from interrogating the provided organization (profile) or from scanning all the profiles available and picking out the root profiles.
|
418
|
-
- The code is there to read in the file, but it was too much effort to try to find which profiles enabled access to those accounts, so I just found all accounts you might have access to - and we'll enable the block on everything.
|
419
|
-
TODO: Allow for a "skip" parameter to skip specific accounts known to host websites or something.
|
420
|
-
|
421
|
-
2. Make sure we know the Root account for every child account, and then create a dictionary of access credentials to get into that account
|
422
|
-
- So how to find out how to access a child account? Determine profiles you have and then try each Management profile?
|
423
|
-
|
424
|
-
3. Ensure that Public Access Block is enabled on every account
|
425
|
-
- We check to see if it's already enabled and don't *re-enable* it.
|
426
|
-
TODO: Maybe we find if the bucket is hosting a website, and then don't enable it on those buckets?
|
427
|
-
|
428
|
-
4. Report that we did what we were supposed to do, and any difficulties we had doing it.
|
429
|
-
"""
|
430
|
-
|
431
|
-
aws_acct = aws_acct_access(pProfile)
|
432
|
-
AllChildAccountList = []
|
433
|
-
begin_time = time()
|
434
|
-
ERASE_LINE = "\x1b[2K"
|
435
|
-
|
436
|
-
AccountList = None # Makes the IDE Checker happy
|
437
|
-
# Get the accounts we're going to work on
|
438
|
-
if pFile is not None:
|
439
|
-
AccountList = read_file(pFile)
|
440
|
-
for accountnumber in AccountList:
|
441
|
-
AllChildAccountList.append(
|
442
|
-
{"AccountId": accountnumber, "AccountStatus": "ACTIVE", "MgmtAccount": aws_acct.acct_number}
|
443
|
-
)
|
444
|
-
elif aws_acct.AccountType.lower() == "root":
|
445
|
-
AllChildAccountList = aws_acct.ChildAccounts
|
446
|
-
else:
|
447
|
-
AllChildAccountList = [
|
448
|
-
{
|
449
|
-
"MgmtAccount": aws_acct.acct_number,
|
450
|
-
"AccountId": aws_acct.acct_number,
|
451
|
-
"AccountEmail": "Child Account",
|
452
|
-
"AccountStatus": aws_acct.AccountStatus,
|
453
|
-
}
|
454
|
-
]
|
455
|
-
logging.info(f"Found {len(AllChildAccountList)} accounts to look through: {AllChildAccountList}")
|
456
|
-
|
457
|
-
for account in tqdm(AllChildAccountList, desc=f"Getting credentials for {len(AllChildAccountList)} accounts"):
|
458
|
-
if account["AccountStatus"] == "ACTIVE":
|
459
|
-
# print(ERASE_LINE, f"Getting credentials for account {account['AccountId']} -- {i + 1} of {len(AllChildAccountList)}", end="\r")
|
460
|
-
try:
|
461
|
-
if pRoleList is None:
|
462
|
-
credentials = get_child_access3(aws_acct, account["AccountId"])
|
463
|
-
else:
|
464
|
-
credentials = get_child_access3(aws_acct, account["AccountId"], "us-east-1", pRoleList)
|
465
|
-
logging.info(f"Successfully got credentials for account {account['AccountId']}")
|
466
|
-
account["AccessKeyId"] = credentials["AccessKeyId"]
|
467
|
-
account["SecretAccessKey"] = credentials["SecretAccessKey"]
|
468
|
-
account["SessionToken"] = credentials["SessionToken"]
|
469
|
-
except Exception as my_Error:
|
470
|
-
logging.error(my_Error)
|
471
|
-
logging.error(
|
472
|
-
f"Failed using root account {account['MgmtAccount']} to get credentials for acct {account['AccountId']}"
|
473
|
-
)
|
474
|
-
else:
|
475
|
-
logging.error(ERASE_LINE, f"Skipping account {account['AccountId']} since it's SUSPENDED or CLOSED")
|
476
|
-
|
477
|
-
print()
|
478
|
-
display_dict = {
|
479
|
-
"MgmtAccount": {"DisplayOrder": 1, "Heading": "Mgmt Acct"},
|
480
|
-
"AccountId": {"DisplayOrder": 2, "Heading": "Acct Number"},
|
481
|
-
"Result": {"DisplayOrder": 3, "Heading": "Block Enabled?"},
|
482
|
-
"Updated": {"DisplayOrder": 4, "Heading": "Blocked Now?"},
|
483
|
-
}
|
484
|
-
|
485
|
-
# fmt = '%-20s %-15s %-20s %-15s'
|
486
|
-
# print(fmt % ("Root Acct", "Account", "Was Block Enabled?", "Blocked Now?"))
|
487
|
-
# print(fmt % ("---------", "-------", "------------------", "------------"))
|
488
|
-
|
489
|
-
print()
|
490
|
-
NotEnabledList = []
|
491
|
-
BlockEnabledList = []
|
492
|
-
PublicBlockResults = []
|
493
|
-
for item in tqdm(AllChildAccountList, desc=f"Checking {len(AllChildAccountList)} accounts for S3 Public Block"):
|
494
|
-
if item["AccountStatus"].upper() == "SUSPENDED":
|
495
|
-
continue
|
496
|
-
else:
|
497
|
-
try:
|
498
|
-
Updated = "Skipped"
|
499
|
-
Enabled = check_block_s3_public_access(item)
|
500
|
-
logging.info(f"Checking account #{item['AccountId']} with Parent Account {item['MgmtAccount']}")
|
501
|
-
if not Enabled["Success"]:
|
502
|
-
NotEnabledList.append(item["AccountId"])
|
503
|
-
if pDryRun:
|
504
|
-
Updated = "DryRun"
|
505
|
-
pass
|
506
|
-
else:
|
507
|
-
response = enable_block_s3_public_access(item)
|
508
|
-
Updated = response["Status"]
|
509
|
-
NotEnabledList.remove(item["AccountId"])
|
510
|
-
BlockEnabledList.append(item["AccountId"])
|
511
|
-
PublicBlockResults.append(
|
512
|
-
{
|
513
|
-
"MgmtAccount": item["MgmtAccount"],
|
514
|
-
"AccountId": item["AccountId"],
|
515
|
-
"Result": Enabled["Success"],
|
516
|
-
"Updated": Updated,
|
517
|
-
}
|
518
|
-
)
|
519
|
-
# print(fmt % (item['MgmtAccount'], item['AccountId'], Enabled['Success'], Updated))
|
520
|
-
except ProfileNotFound as myError:
|
521
|
-
logging.info(f"You've tried to update your own management account.")
|
522
|
-
|
523
|
-
display_results(PublicBlockResults, display_dict)
|
524
|
-
|
525
|
-
print()
|
526
|
-
if pFile is not None:
|
527
|
-
print(f"# of account in file provided: {len(AccountList)}")
|
528
|
-
print(f"# of Checked Accounts: {len(AllChildAccountList)}")
|
529
|
-
for account in NotEnabledList:
|
530
|
-
print(f"{Fore.RED}Account {account} needs the S3 public block to be enabled{Fore.RESET}")
|
531
|
-
print()
|
532
|
-
for account in BlockEnabledList:
|
533
|
-
print(f"{Fore.GREEN}Account {account} has had the S3 public block enabled{Fore.RESET}")
|
534
|
-
if pTiming:
|
535
|
-
print(ERASE_LINE)
|
536
|
-
print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
|
537
|
-
print()
|
538
|
-
print("Thank you for using this script.")
|
539
|
-
print()
|
@@ -1,12 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Cloud Foundations Organizations module.
|
3
|
-
|
4
|
-
This module provides AWS Organizations management capabilities
|
5
|
-
including OU structure setup and account management.
|
6
|
-
"""
|
7
|
-
|
8
|
-
from runbooks.organizations.manager import OUManager
|
9
|
-
|
10
|
-
__all__ = [
|
11
|
-
"OUManager",
|
12
|
-
]
|