runbooks 0.2.3__py3-none-any.whl → 0.6.1__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.
- conftest.py +26 -0
- jupyter-agent/.env.template +2 -0
- jupyter-agent/.gitattributes +35 -0
- jupyter-agent/README.md +16 -0
- jupyter-agent/app.py +256 -0
- jupyter-agent/cloudops-agent.png +0 -0
- jupyter-agent/ds-system-prompt.txt +154 -0
- jupyter-agent/jupyter-agent.png +0 -0
- jupyter-agent/llama3_template.jinja +123 -0
- jupyter-agent/requirements.txt +9 -0
- jupyter-agent/utils.py +409 -0
- runbooks/__init__.py +71 -3
- runbooks/__main__.py +13 -0
- runbooks/aws/ec2_describe_instances.py +1 -1
- runbooks/aws/ec2_run_instances.py +8 -2
- runbooks/aws/ec2_start_stop_instances.py +17 -4
- runbooks/aws/ec2_unused_volumes.py +5 -1
- runbooks/aws/s3_create_bucket.py +4 -2
- runbooks/aws/s3_list_objects.py +6 -1
- runbooks/aws/tagging_lambda_handler.py +13 -2
- runbooks/aws/tags.json +12 -0
- runbooks/base.py +353 -0
- runbooks/cfat/README.md +49 -0
- runbooks/cfat/__init__.py +74 -0
- runbooks/cfat/app.ts +644 -0
- runbooks/cfat/assessment/__init__.py +40 -0
- runbooks/cfat/assessment/asana-import.csv +39 -0
- runbooks/cfat/assessment/cfat-checks.csv +31 -0
- runbooks/cfat/assessment/cfat.txt +520 -0
- runbooks/cfat/assessment/collectors.py +200 -0
- runbooks/cfat/assessment/jira-import.csv +39 -0
- runbooks/cfat/assessment/runner.py +387 -0
- runbooks/cfat/assessment/validators.py +290 -0
- runbooks/cfat/cli.py +103 -0
- runbooks/cfat/docs/asana-import.csv +24 -0
- runbooks/cfat/docs/cfat-checks.csv +31 -0
- runbooks/cfat/docs/cfat.txt +335 -0
- runbooks/cfat/docs/checks-output.png +0 -0
- runbooks/cfat/docs/cloudshell-console-run.png +0 -0
- runbooks/cfat/docs/cloudshell-download.png +0 -0
- runbooks/cfat/docs/cloudshell-output.png +0 -0
- runbooks/cfat/docs/downloadfile.png +0 -0
- runbooks/cfat/docs/jira-import.csv +24 -0
- runbooks/cfat/docs/open-cloudshell.png +0 -0
- runbooks/cfat/docs/report-header.png +0 -0
- runbooks/cfat/models.py +1026 -0
- runbooks/cfat/package-lock.json +5116 -0
- runbooks/cfat/package.json +38 -0
- runbooks/cfat/report.py +496 -0
- runbooks/cfat/reporting/__init__.py +46 -0
- runbooks/cfat/reporting/exporters.py +337 -0
- runbooks/cfat/reporting/formatters.py +496 -0
- runbooks/cfat/reporting/templates.py +135 -0
- runbooks/cfat/run-assessment.sh +23 -0
- runbooks/cfat/runner.py +69 -0
- runbooks/cfat/src/actions/check-cloudtrail-existence.ts +43 -0
- runbooks/cfat/src/actions/check-config-existence.ts +37 -0
- runbooks/cfat/src/actions/check-control-tower.ts +37 -0
- runbooks/cfat/src/actions/check-ec2-existence.ts +46 -0
- runbooks/cfat/src/actions/check-iam-users.ts +50 -0
- runbooks/cfat/src/actions/check-legacy-cur.ts +30 -0
- runbooks/cfat/src/actions/check-org-cloudformation.ts +30 -0
- runbooks/cfat/src/actions/check-vpc-existence.ts +43 -0
- runbooks/cfat/src/actions/create-asanaimport.ts +14 -0
- runbooks/cfat/src/actions/create-backlog.ts +372 -0
- runbooks/cfat/src/actions/create-jiraimport.ts +15 -0
- runbooks/cfat/src/actions/create-report.ts +616 -0
- runbooks/cfat/src/actions/define-account-type.ts +51 -0
- runbooks/cfat/src/actions/get-enabled-org-policy-types.ts +40 -0
- runbooks/cfat/src/actions/get-enabled-org-services.ts +26 -0
- runbooks/cfat/src/actions/get-idc-info.ts +34 -0
- runbooks/cfat/src/actions/get-org-da-accounts.ts +34 -0
- runbooks/cfat/src/actions/get-org-details.ts +35 -0
- runbooks/cfat/src/actions/get-org-member-accounts.ts +44 -0
- runbooks/cfat/src/actions/get-org-ous.ts +35 -0
- runbooks/cfat/src/actions/get-regions.ts +22 -0
- runbooks/cfat/src/actions/zip-assessment.ts +27 -0
- runbooks/cfat/src/types/index.d.ts +147 -0
- runbooks/cfat/tests/__init__.py +141 -0
- runbooks/cfat/tests/test_cli.py +340 -0
- runbooks/cfat/tests/test_integration.py +290 -0
- runbooks/cfat/tests/test_models.py +505 -0
- runbooks/cfat/tests/test_reporting.py +354 -0
- runbooks/cfat/tsconfig.json +16 -0
- runbooks/cfat/webpack.config.cjs +27 -0
- runbooks/config.py +260 -0
- runbooks/finops/__init__.py +88 -0
- runbooks/finops/aws_client.py +245 -0
- runbooks/finops/cli.py +151 -0
- runbooks/finops/cost_processor.py +410 -0
- runbooks/finops/dashboard_runner.py +448 -0
- runbooks/finops/helpers.py +355 -0
- runbooks/finops/main.py +14 -0
- runbooks/finops/profile_processor.py +174 -0
- runbooks/finops/types.py +66 -0
- runbooks/finops/visualisations.py +80 -0
- runbooks/inventory/.gitignore +354 -0
- runbooks/inventory/ArgumentsClass.py +261 -0
- runbooks/inventory/Inventory_Modules.py +6130 -0
- runbooks/inventory/LandingZone/delete_lz.py +1075 -0
- runbooks/inventory/README.md +1320 -0
- runbooks/inventory/__init__.py +62 -0
- runbooks/inventory/account_class.py +532 -0
- runbooks/inventory/all_my_instances_wrapper.py +123 -0
- runbooks/inventory/aws_decorators.py +201 -0
- runbooks/inventory/cfn_move_stack_instances.py +1526 -0
- runbooks/inventory/check_cloudtrail_compliance.py +614 -0
- runbooks/inventory/check_controltower_readiness.py +1107 -0
- runbooks/inventory/check_landingzone_readiness.py +711 -0
- runbooks/inventory/cloudtrail.md +727 -0
- runbooks/inventory/collectors/__init__.py +20 -0
- runbooks/inventory/collectors/aws_compute.py +518 -0
- runbooks/inventory/collectors/aws_networking.py +275 -0
- runbooks/inventory/collectors/base.py +222 -0
- runbooks/inventory/core/__init__.py +19 -0
- runbooks/inventory/core/collector.py +303 -0
- runbooks/inventory/core/formatter.py +296 -0
- runbooks/inventory/delete_s3_buckets_objects.py +169 -0
- runbooks/inventory/discovery.md +81 -0
- runbooks/inventory/draw_org_structure.py +748 -0
- runbooks/inventory/ec2_vpc_utils.py +341 -0
- runbooks/inventory/find_cfn_drift_detection.py +272 -0
- runbooks/inventory/find_cfn_orphaned_stacks.py +719 -0
- runbooks/inventory/find_cfn_stackset_drift.py +733 -0
- runbooks/inventory/find_ec2_security_groups.py +669 -0
- runbooks/inventory/find_landingzone_versions.py +201 -0
- runbooks/inventory/find_vpc_flow_logs.py +1221 -0
- runbooks/inventory/inventory.sh +659 -0
- runbooks/inventory/list_cfn_stacks.py +558 -0
- runbooks/inventory/list_cfn_stackset_operation_results.py +252 -0
- runbooks/inventory/list_cfn_stackset_operations.py +734 -0
- runbooks/inventory/list_cfn_stacksets.py +453 -0
- runbooks/inventory/list_config_recorders_delivery_channels.py +681 -0
- runbooks/inventory/list_ds_directories.py +354 -0
- runbooks/inventory/list_ec2_availability_zones.py +286 -0
- runbooks/inventory/list_ec2_ebs_volumes.py +244 -0
- runbooks/inventory/list_ec2_instances.py +425 -0
- runbooks/inventory/list_ecs_clusters_and_tasks.py +562 -0
- runbooks/inventory/list_elbs_load_balancers.py +411 -0
- runbooks/inventory/list_enis_network_interfaces.py +526 -0
- runbooks/inventory/list_guardduty_detectors.py +568 -0
- runbooks/inventory/list_iam_policies.py +404 -0
- runbooks/inventory/list_iam_roles.py +518 -0
- runbooks/inventory/list_iam_saml_providers.py +359 -0
- runbooks/inventory/list_lambda_functions.py +882 -0
- runbooks/inventory/list_org_accounts.py +446 -0
- runbooks/inventory/list_org_accounts_users.py +354 -0
- runbooks/inventory/list_rds_db_instances.py +406 -0
- runbooks/inventory/list_route53_hosted_zones.py +318 -0
- runbooks/inventory/list_servicecatalog_provisioned_products.py +575 -0
- runbooks/inventory/list_sns_topics.py +360 -0
- runbooks/inventory/list_ssm_parameters.py +402 -0
- runbooks/inventory/list_vpc_subnets.py +433 -0
- runbooks/inventory/list_vpcs.py +422 -0
- runbooks/inventory/lockdown_cfn_stackset_role.py +224 -0
- runbooks/inventory/models/__init__.py +24 -0
- runbooks/inventory/models/account.py +192 -0
- runbooks/inventory/models/inventory.py +309 -0
- runbooks/inventory/models/resource.py +247 -0
- runbooks/inventory/recover_cfn_stack_ids.py +205 -0
- runbooks/inventory/requirements.txt +12 -0
- runbooks/inventory/run_on_multi_accounts.py +211 -0
- runbooks/inventory/tests/common_test_data.py +3661 -0
- runbooks/inventory/tests/common_test_functions.py +204 -0
- runbooks/inventory/tests/script_test_data.py +0 -0
- runbooks/inventory/tests/setup.py +24 -0
- runbooks/inventory/tests/src.py +18 -0
- runbooks/inventory/tests/test_cfn_describe_stacks.py +208 -0
- runbooks/inventory/tests/test_ec2_describe_instances.py +162 -0
- runbooks/inventory/tests/test_inventory_modules.py +55 -0
- runbooks/inventory/tests/test_lambda_list_functions.py +86 -0
- runbooks/inventory/tests/test_moto_integration_example.py +273 -0
- runbooks/inventory/tests/test_org_list_accounts.py +49 -0
- runbooks/inventory/update_aws_actions.py +173 -0
- runbooks/inventory/update_cfn_stacksets.py +1215 -0
- runbooks/inventory/update_cloudwatch_logs_retention_policy.py +294 -0
- runbooks/inventory/update_iam_roles_cross_accounts.py +478 -0
- runbooks/inventory/update_s3_public_access_block.py +539 -0
- runbooks/inventory/utils/__init__.py +23 -0
- runbooks/inventory/utils/aws_helpers.py +510 -0
- runbooks/inventory/utils/threading_utils.py +493 -0
- runbooks/inventory/utils/validation.py +682 -0
- runbooks/inventory/verify_ec2_security_groups.py +1430 -0
- runbooks/main.py +785 -0
- runbooks/organizations/__init__.py +12 -0
- runbooks/organizations/manager.py +374 -0
- runbooks/security_baseline/README.md +324 -0
- runbooks/security_baseline/checklist/alternate_contacts.py +8 -1
- runbooks/security_baseline/checklist/bucket_public_access.py +4 -1
- runbooks/security_baseline/checklist/cloudwatch_alarm_configuration.py +9 -2
- runbooks/security_baseline/checklist/guardduty_enabled.py +9 -2
- runbooks/security_baseline/checklist/multi_region_instance_usage.py +5 -1
- runbooks/security_baseline/checklist/root_access_key.py +6 -1
- runbooks/security_baseline/config-origin.json +1 -1
- runbooks/security_baseline/config.json +1 -1
- runbooks/security_baseline/permission.json +1 -1
- runbooks/security_baseline/report_generator.py +10 -2
- runbooks/security_baseline/report_template_en.html +8 -8
- runbooks/security_baseline/report_template_jp.html +8 -8
- runbooks/security_baseline/report_template_kr.html +13 -13
- runbooks/security_baseline/report_template_vn.html +8 -8
- runbooks/security_baseline/requirements.txt +7 -0
- runbooks/security_baseline/run_script.py +8 -2
- runbooks/security_baseline/security_baseline_tester.py +10 -2
- runbooks/security_baseline/utils/common.py +5 -1
- runbooks/utils/__init__.py +204 -0
- runbooks-0.6.1.dist-info/METADATA +373 -0
- runbooks-0.6.1.dist-info/RECORD +237 -0
- {runbooks-0.2.3.dist-info → runbooks-0.6.1.dist-info}/WHEEL +1 -1
- runbooks-0.6.1.dist-info/entry_points.txt +7 -0
- runbooks-0.6.1.dist-info/licenses/LICENSE +201 -0
- runbooks-0.6.1.dist-info/top_level.txt +3 -0
- runbooks/python101/calculator.py +0 -34
- runbooks/python101/config.py +0 -1
- runbooks/python101/exceptions.py +0 -16
- runbooks/python101/file_manager.py +0 -218
- runbooks/python101/toolkit.py +0 -153
- runbooks-0.2.3.dist-info/METADATA +0 -435
- runbooks-0.2.3.dist-info/RECORD +0 -61
- runbooks-0.2.3.dist-info/entry_points.txt +0 -3
- runbooks-0.2.3.dist-info/top_level.txt +0 -1
@@ -0,0 +1,446 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
AWS Organizations Account Inventory
|
4
|
+
|
5
|
+
A comprehensive AWS Organizations account discovery tool that provides detailed visibility
|
6
|
+
into multi-account structures across all accessible Management Accounts. Supports account
|
7
|
+
status analysis, organizational hierarchy mapping, and cross-organization account lookup.
|
8
|
+
|
9
|
+
**AWS API Mapping**: `organizations.list_accounts()`, `organizations.describe_organization()`
|
10
|
+
|
11
|
+
Features:
|
12
|
+
- Multi-organization account discovery
|
13
|
+
- Management Account identification and validation
|
14
|
+
- Account status tracking (ACTIVE, SUSPENDED, etc.)
|
15
|
+
- Cross-organization account lookup by ID
|
16
|
+
- Short-form and detailed organizational views
|
17
|
+
- Root profile discovery and listing
|
18
|
+
- Account hierarchy visualization
|
19
|
+
|
20
|
+
Compatibility:
|
21
|
+
- AWS Organizations with cross-account roles
|
22
|
+
- AWS Control Tower managed accounts
|
23
|
+
- Multiple AWS Organizations access
|
24
|
+
- AWS Account Factory provisioned accounts
|
25
|
+
|
26
|
+
Example:
|
27
|
+
Discover all accounts across organizations:
|
28
|
+
```bash
|
29
|
+
python org_list_accounts.py --profile my-org-profile
|
30
|
+
```
|
31
|
+
|
32
|
+
Short form listing for quick overview:
|
33
|
+
```bash
|
34
|
+
python org_list_accounts.py --profile my-profile --short
|
35
|
+
```
|
36
|
+
|
37
|
+
Find which organization contains specific accounts:
|
38
|
+
```bash
|
39
|
+
python org_list_accounts.py --acct 123456789012 987654321098
|
40
|
+
```
|
41
|
+
|
42
|
+
List only root profiles:
|
43
|
+
```bash
|
44
|
+
python org_list_accounts.py --rootonly
|
45
|
+
```
|
46
|
+
|
47
|
+
Use Cases:
|
48
|
+
- AWS Organizations discovery and mapping
|
49
|
+
- Account governance and compliance auditing
|
50
|
+
- Cross-organization account tracking
|
51
|
+
- Management Account validation
|
52
|
+
- Account migration planning
|
53
|
+
|
54
|
+
Requirements:
|
55
|
+
- IAM permissions: `organizations:ListAccounts`, `organizations:DescribeOrganization`
|
56
|
+
- AWS Organizations access (Management Account or delegated admin)
|
57
|
+
- Python 3.8+ with required dependencies
|
58
|
+
|
59
|
+
Author:
|
60
|
+
AWS Cloud Foundations Team
|
61
|
+
|
62
|
+
Version:
|
63
|
+
2024.05.08
|
64
|
+
"""
|
65
|
+
|
66
|
+
import logging
|
67
|
+
import sys
|
68
|
+
from os.path import split
|
69
|
+
from time import time
|
70
|
+
|
71
|
+
from ArgumentsClass import CommonArguments
|
72
|
+
|
73
|
+
# from botocore.exceptions import ClientError, NoCredentialsError, InvalidConfigError
|
74
|
+
from colorama import Fore, Style, init
|
75
|
+
from Inventory_Modules import display_results, get_org_accounts_from_profiles, get_profiles
|
76
|
+
|
77
|
+
init()
|
78
|
+
__version__ = "2024.05.08"
|
79
|
+
ERASE_LINE = "\x1b[2K"
|
80
|
+
begin_time = time()
|
81
|
+
|
82
|
+
|
83
|
+
# TODO: If they provide a profile that isn't a root profile, you should find out which org it belongs to, and then show the org for that.
|
84
|
+
# This will be difficult, since we don't know which profile that belongs to. Hmmm...
|
85
|
+
|
86
|
+
|
87
|
+
##################
|
88
|
+
# Functions
|
89
|
+
##################
|
90
|
+
def parse_args(f_arguments):
|
91
|
+
"""
|
92
|
+
Parse and validate command-line arguments for AWS Organizations account discovery.
|
93
|
+
|
94
|
+
Configures the argument parser with Organizations-specific options including
|
95
|
+
profile management, output formatting, and account lookup capabilities.
|
96
|
+
Uses the standardized CommonArguments framework for consistency.
|
97
|
+
|
98
|
+
Args:
|
99
|
+
f_arguments (list): Command-line arguments to parse (typically sys.argv[1:])
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
argparse.Namespace: Parsed arguments containing:
|
103
|
+
- Profiles: AWS profiles for multi-organization access
|
104
|
+
- RootOnly: Flag to display only Management Accounts
|
105
|
+
- pShortform: Brief output format (profiles only, not child accounts)
|
106
|
+
- accountList: Specific account IDs to lookup across organizations
|
107
|
+
- SkipProfiles: Profiles to exclude from discovery
|
108
|
+
- Filename: Output file prefix for CSV export
|
109
|
+
- Time: Enable execution timing measurements
|
110
|
+
- Other standard framework arguments
|
111
|
+
|
112
|
+
Script-Specific Arguments:
|
113
|
+
--short/-s/-q: Enables brief output showing only profile-level information
|
114
|
+
without detailed child account enumeration. Improves performance
|
115
|
+
for large organizations where only high-level view is needed.
|
116
|
+
|
117
|
+
--acct/-A: Cross-organization account lookup feature. Accepts multiple
|
118
|
+
account IDs and determines which organization each belongs to.
|
119
|
+
Essential for account governance and migration planning.
|
120
|
+
|
121
|
+
Use Cases:
|
122
|
+
- Quick organization overview: --short for high-level visibility
|
123
|
+
- Account location discovery: --acct 123456789012 to find parent org
|
124
|
+
- Management account audit: --rootonly for governance review
|
125
|
+
- Comprehensive inventory: default mode for complete account listing
|
126
|
+
"""
|
127
|
+
script_path, script_name = split(sys.argv[0])
|
128
|
+
parser = CommonArguments()
|
129
|
+
|
130
|
+
# Enable multi-profile support for cross-organization discovery
|
131
|
+
parser.multiprofile()
|
132
|
+
|
133
|
+
# Add extended arguments (skip accounts, skip profiles, etc.)
|
134
|
+
parser.extendedargs()
|
135
|
+
|
136
|
+
# Enable root-only filtering for Management Account focus
|
137
|
+
parser.rootOnly()
|
138
|
+
|
139
|
+
# Add execution timing capabilities
|
140
|
+
parser.timing()
|
141
|
+
|
142
|
+
# Enable CSV file export functionality
|
143
|
+
parser.save_to_file()
|
144
|
+
|
145
|
+
# Configure logging verbosity levels
|
146
|
+
parser.verbosity()
|
147
|
+
|
148
|
+
# Set script version for --version flag
|
149
|
+
parser.version(__version__)
|
150
|
+
|
151
|
+
# Add script-specific argument group
|
152
|
+
local = parser.my_parser.add_argument_group(script_name, "Parameters specific to this script")
|
153
|
+
|
154
|
+
# Short-form display option for performance and readability
|
155
|
+
local.add_argument(
|
156
|
+
"-s",
|
157
|
+
"-q",
|
158
|
+
"--short",
|
159
|
+
help="Display only brief listing of the profile accounts, and not the Child Accounts under them",
|
160
|
+
action="store_const",
|
161
|
+
dest="pShortform",
|
162
|
+
const=True,
|
163
|
+
default=False,
|
164
|
+
)
|
165
|
+
|
166
|
+
# Cross-organization account lookup capability
|
167
|
+
local.add_argument(
|
168
|
+
"-A", "--acct", help="Find which Org this account is a part of", nargs="*", dest="accountList", default=None
|
169
|
+
)
|
170
|
+
|
171
|
+
return parser.my_parser.parse_args(f_arguments)
|
172
|
+
|
173
|
+
|
174
|
+
def all_my_orgs(
|
175
|
+
f_Profiles: list,
|
176
|
+
f_SkipProfiles: list,
|
177
|
+
f_AccountList: list,
|
178
|
+
f_Timing: bool,
|
179
|
+
f_RootOnly: bool,
|
180
|
+
f_SaveFilename: str,
|
181
|
+
f_Shortform: bool,
|
182
|
+
f_verbose,
|
183
|
+
):
|
184
|
+
"""
|
185
|
+
Execute comprehensive AWS Organizations discovery across multiple management accounts.
|
186
|
+
|
187
|
+
This is the core orchestration function that discovers and maps AWS Organizations
|
188
|
+
hierarchies, identifies management accounts, enumerates child accounts, and provides
|
189
|
+
detailed organizational visibility across multiple AWS Organizations.
|
190
|
+
|
191
|
+
Args:
|
192
|
+
f_Profiles (list): AWS profiles to analyze for organization membership
|
193
|
+
f_SkipProfiles (list): Profiles to exclude from discovery process
|
194
|
+
f_AccountList (list): Specific account IDs to lookup across organizations
|
195
|
+
f_Timing (bool): Enable execution timing measurements and display
|
196
|
+
f_RootOnly (bool): Limit output to Management Accounts only
|
197
|
+
f_SaveFilename (str): Output file prefix for CSV export (None for console only)
|
198
|
+
f_Shortform (bool): Brief output format excluding child account details
|
199
|
+
f_verbose: Logging verbosity level for detailed operational logging
|
200
|
+
|
201
|
+
Returns:
|
202
|
+
dict: Comprehensive organizational data containing:
|
203
|
+
- OrgsFound: List of Management Account IDs discovered
|
204
|
+
- StandAloneAccounts: Non-organizational standalone accounts
|
205
|
+
- ClosedAccounts: Suspended or closed account IDs
|
206
|
+
- FailedProfiles: Profiles that failed authentication/access
|
207
|
+
- AccountList: Complete account inventory with metadata
|
208
|
+
|
209
|
+
Processing Workflow:
|
210
|
+
1. Profile Resolution: Convert profile names to validated credential sets
|
211
|
+
2. Organization Discovery: Query each profile for organizational context
|
212
|
+
3. Account Enumeration: List all child accounts for Management Accounts
|
213
|
+
4. Status Analysis: Identify suspended, closed, or problematic accounts
|
214
|
+
5. Output Generation: Format results for console display or CSV export
|
215
|
+
6. Cross-Reference Lookup: Match requested account IDs to organizations
|
216
|
+
|
217
|
+
Output Formats:
|
218
|
+
- Console Mode: Formatted tables with colored output for status highlighting
|
219
|
+
- CSV Mode: Pipe-delimited files suitable for data analysis and reporting
|
220
|
+
- Short Mode: Profile-level summary without child account enumeration
|
221
|
+
- Lookup Mode: Targeted account-to-organization mapping
|
222
|
+
|
223
|
+
Error Handling:
|
224
|
+
- Profile failures are logged and tracked but don't stop processing
|
225
|
+
- Authentication errors are captured with detailed error messages
|
226
|
+
- Missing organizations are handled gracefully with fallback behavior
|
227
|
+
- API rate limits and throttling are managed through sequential processing
|
228
|
+
|
229
|
+
Performance Considerations:
|
230
|
+
- Sequential profile processing (no threading due to AWS API complexity)
|
231
|
+
- Cached organization data to avoid redundant API calls
|
232
|
+
- Progress indicators for long-running discovery operations
|
233
|
+
- Memory-efficient handling of large organizational hierarchies
|
234
|
+
|
235
|
+
Security Features:
|
236
|
+
- Read-only operations with minimal required permissions
|
237
|
+
- No credential storage or caching beyond execution scope
|
238
|
+
- Audit trail through comprehensive logging
|
239
|
+
- Safe handling of cross-account access patterns
|
240
|
+
"""
|
241
|
+
ProfileList = get_profiles(fSkipProfiles=f_SkipProfiles, fprofiles=f_Profiles)
|
242
|
+
# print("Capturing info for supplied profiles")
|
243
|
+
logging.info(f"These profiles were requested {f_Profiles}.")
|
244
|
+
logging.warning(f"These profiles are being checked {ProfileList}.")
|
245
|
+
print(f"Please bear with us as we run through {len(ProfileList)} profiles")
|
246
|
+
AllProfileAccounts = get_org_accounts_from_profiles(ProfileList)
|
247
|
+
AccountList = []
|
248
|
+
FailedProfiles = []
|
249
|
+
OrgsFound = []
|
250
|
+
|
251
|
+
# Print out the results
|
252
|
+
if f_Timing:
|
253
|
+
print()
|
254
|
+
print(f"It's taken {Fore.GREEN}{time() - begin_time:.2f}{Fore.RESET} seconds to find profile accounts...")
|
255
|
+
print()
|
256
|
+
fmt = "%-23s %-15s %-15s %-12s %-10s"
|
257
|
+
print("<------------------------------------>")
|
258
|
+
print(fmt % ("Profile Name", "Account Number", "Payer Org Acct", "Org ID", "Root Acct?"))
|
259
|
+
print(fmt % ("------------", "--------------", "--------------", "------", "----------"))
|
260
|
+
|
261
|
+
for item in AllProfileAccounts:
|
262
|
+
if not item["Success"]:
|
263
|
+
# If the profile failed, don't print anything and continue on.
|
264
|
+
FailedProfiles.append(item["profile"])
|
265
|
+
logging.error(f"{item['profile']} errored. Message: {item['ErrorMessage']}")
|
266
|
+
else:
|
267
|
+
if item["RootAcct"]:
|
268
|
+
# If the account is a root account, capture it for display later
|
269
|
+
OrgsFound.append(item["MgmtAccount"])
|
270
|
+
# Print results for all profiles
|
271
|
+
item["AccountId"] = item["aws_acct"].acct_number
|
272
|
+
item["AccountStatus"] = item["aws_acct"].AccountStatus
|
273
|
+
# item['AccountEmail'] = item['aws_acct'].
|
274
|
+
try:
|
275
|
+
if f_RootOnly and not item["RootAcct"]:
|
276
|
+
# If we're only looking for root accounts, and this isn't one, don't print anything and continue on.
|
277
|
+
continue
|
278
|
+
else:
|
279
|
+
logging.info(f"{item['profile']} was successful.")
|
280
|
+
print(
|
281
|
+
f"{Fore.RED if item['RootAcct'] else ''}{item['profile']:23s} {item['aws_acct'].acct_number:15s} {item['MgmtAccount']:15s} {str(item['OrgId']):12s} {item['RootAcct']}{Fore.RESET}"
|
282
|
+
)
|
283
|
+
except TypeError as my_Error:
|
284
|
+
logging.error(f"Error - {my_Error} on {item}")
|
285
|
+
pass
|
286
|
+
|
287
|
+
"""
|
288
|
+
If I create a dictionary from the Root Accts and Root Profiles Lists -
|
289
|
+
I can use that to determine which profile belongs to the root user of my (child) account.
|
290
|
+
But this dictionary is only guaranteed to be valid after ALL profiles have been checked,
|
291
|
+
so... it doesn't solve our issue - unless we don't write anything to the screen until *everything* is done,
|
292
|
+
and we keep all output in another dictionary - where we can populate the missing data at the end...
|
293
|
+
but that takes a long time, since nothing would be sent to the screen in the meantime.
|
294
|
+
"""
|
295
|
+
|
296
|
+
print(ERASE_LINE)
|
297
|
+
print("-------------------")
|
298
|
+
|
299
|
+
if f_Shortform:
|
300
|
+
# The user specified "short-form" which means they don't want any information on child accounts.
|
301
|
+
return_response = {
|
302
|
+
"OrgsFound": OrgsFound,
|
303
|
+
"FailedProfiles": FailedProfiles,
|
304
|
+
"AllProfileAccounts": AllProfileAccounts,
|
305
|
+
}
|
306
|
+
else:
|
307
|
+
NumOfOrgAccounts = 0
|
308
|
+
ClosedAccounts = []
|
309
|
+
FailedAccounts = 0
|
310
|
+
account = dict()
|
311
|
+
ProfileNameLength = len("Organization's Profile")
|
312
|
+
|
313
|
+
for item in AllProfileAccounts:
|
314
|
+
# AllProfileAccounts holds the list of account class objects of the accounts associated with the profiles it found.
|
315
|
+
if item["Success"] and not item["RootAcct"]:
|
316
|
+
account.update(item["aws_acct"].ChildAccounts[0])
|
317
|
+
account.update({"Profile": item["profile"]})
|
318
|
+
AccountList.append(account.copy())
|
319
|
+
elif item["Success"] and item["RootAcct"]:
|
320
|
+
for child_acct in item["aws_acct"].ChildAccounts:
|
321
|
+
account.update(child_acct)
|
322
|
+
account.update({"Profile": item["profile"]})
|
323
|
+
ProfileNameLength = max(len(item["profile"]), ProfileNameLength)
|
324
|
+
AccountList.append(account.copy())
|
325
|
+
if not child_acct["AccountStatus"] == "ACTIVE":
|
326
|
+
ClosedAccounts.append(child_acct["AccountId"])
|
327
|
+
|
328
|
+
NumOfOrgAccounts += len(item["aws_acct"].ChildAccounts)
|
329
|
+
elif not item["Success"]:
|
330
|
+
FailedAccounts += 1
|
331
|
+
continue
|
332
|
+
|
333
|
+
# Display results on screen
|
334
|
+
if f_SaveFilename is None:
|
335
|
+
fmt = "%-23s %-15s"
|
336
|
+
print()
|
337
|
+
print(fmt % ("Organization's Profile", "Root Account"))
|
338
|
+
print(fmt % ("----------------------", "------------"))
|
339
|
+
for item in AllProfileAccounts:
|
340
|
+
if item["Success"] and item["RootAcct"]:
|
341
|
+
print(
|
342
|
+
f"{item['profile']:{ProfileNameLength}s} {Style.BRIGHT}{item['MgmtAccount']:15s}{Style.RESET_ALL}"
|
343
|
+
)
|
344
|
+
print(
|
345
|
+
f"\t{'Child Account Number':{len('Child Account Number')}s} {'Child Account Status':{len('Child Account Status')}s} {'Child Email Address'}"
|
346
|
+
)
|
347
|
+
for child_acct in item["aws_acct"].ChildAccounts:
|
348
|
+
print(
|
349
|
+
f"\t{Fore.RED if not child_acct['AccountStatus'] == 'ACTIVE' else ''}{child_acct['AccountId']:{len('Child Account Number')}s} {child_acct['AccountStatus']:{len('Child Account Status')}s} {child_acct['AccountEmail']}{Fore.RESET}"
|
350
|
+
)
|
351
|
+
|
352
|
+
elif f_SaveFilename is not None:
|
353
|
+
# The user specified a file name, which means they want a (pipe-delimited) CSV file with the relevant output.
|
354
|
+
display_dict = {
|
355
|
+
"MgmtAccount": {"DisplayOrder": 1, "Heading": "Parent Acct"},
|
356
|
+
"AccountId": {"DisplayOrder": 2, "Heading": "Account Number"},
|
357
|
+
"AccountStatus": {"DisplayOrder": 3, "Heading": "Account Status", "Condition": ["SUSPENDED", "CLOSED"]},
|
358
|
+
"AccountEmail": {"DisplayOrder": 4, "Heading": "Email"},
|
359
|
+
}
|
360
|
+
if pRootOnly:
|
361
|
+
sorted_Results = sorted(AllProfileAccounts, key=lambda d: (d["MgmtAccount"], d["AccountId"]))
|
362
|
+
else:
|
363
|
+
sorted_Results = sorted(AccountList, key=lambda d: (d["MgmtAccount"], d["AccountId"]))
|
364
|
+
display_results(sorted_Results, display_dict, "None", f_SaveFilename)
|
365
|
+
|
366
|
+
StandAloneAccounts = [
|
367
|
+
x["AccountId"]
|
368
|
+
for x in AccountList
|
369
|
+
if x["MgmtAccount"] == x["AccountId"] and x["AccountEmail"] == "Not an Org Management Account"
|
370
|
+
]
|
371
|
+
FailedProfiles = [i["profile"] for i in AllProfileAccounts if not i["Success"]]
|
372
|
+
OrgsFound = [i["MgmtAccount"] for i in AllProfileAccounts if i["RootAcct"]]
|
373
|
+
StandAloneAccounts.sort()
|
374
|
+
FailedProfiles.sort()
|
375
|
+
OrgsFound.sort()
|
376
|
+
ClosedAccounts.sort()
|
377
|
+
|
378
|
+
print()
|
379
|
+
print(f"Number of Organizations: {len(OrgsFound)}")
|
380
|
+
print(f"Number of Organization Accounts: {NumOfOrgAccounts}")
|
381
|
+
print(f"Number of Standalone Accounts: {len(StandAloneAccounts)}")
|
382
|
+
print(f"Number of suspended or closed accounts: {len(ClosedAccounts)}")
|
383
|
+
print(f"Number of profiles that failed: {len(FailedProfiles)}")
|
384
|
+
if f_verbose < 50:
|
385
|
+
print("----------------------")
|
386
|
+
print(f"The following accounts are the Org Accounts: {OrgsFound}")
|
387
|
+
print(f"The following accounts are Standalone: {StandAloneAccounts}") if len(
|
388
|
+
StandAloneAccounts
|
389
|
+
) > 0 else None
|
390
|
+
print(f"The following accounts are closed or suspended: {ClosedAccounts}") if len(
|
391
|
+
ClosedAccounts
|
392
|
+
) > 0 else None
|
393
|
+
print(f"The following profiles failed: {FailedProfiles}") if len(FailedProfiles) > 0 else None
|
394
|
+
print("----------------------")
|
395
|
+
print()
|
396
|
+
return_response = {
|
397
|
+
"OrgsFound": OrgsFound,
|
398
|
+
"StandAloneAccounts": StandAloneAccounts,
|
399
|
+
"ClosedAccounts": ClosedAccounts,
|
400
|
+
"FailedProfiles": FailedProfiles,
|
401
|
+
"AccountList": AccountList,
|
402
|
+
}
|
403
|
+
|
404
|
+
if f_AccountList is not None:
|
405
|
+
print(f"Found the requested account number{'' if len(AccountList) == 1 else 's'}:")
|
406
|
+
for acct in AccountList:
|
407
|
+
if acct["AccountId"] in f_AccountList:
|
408
|
+
print(
|
409
|
+
f"Profile: {acct['Profile']} | Org: {acct['MgmtAccount']} | Account: {acct['AccountId']} | Status: {acct['AccountStatus']} | Email: {acct['AccountEmail']}"
|
410
|
+
)
|
411
|
+
|
412
|
+
return return_response
|
413
|
+
|
414
|
+
|
415
|
+
##################
|
416
|
+
# Main
|
417
|
+
##################
|
418
|
+
|
419
|
+
if __name__ == "__main__":
|
420
|
+
args = parse_args(sys.argv[1:])
|
421
|
+
|
422
|
+
pProfiles = args.Profiles
|
423
|
+
pRootOnly = args.RootOnly
|
424
|
+
pTiming = args.Time
|
425
|
+
pSkipAccounts = args.SkipAccounts
|
426
|
+
pSkipProfiles = args.SkipProfiles
|
427
|
+
verbose = args.loglevel
|
428
|
+
pSaveFilename = args.Filename
|
429
|
+
pShortform = args.pShortform
|
430
|
+
pAccountList = args.accountList
|
431
|
+
logging.basicConfig(
|
432
|
+
level=verbose, format="[%(filename)s:%(lineno)s - %(processName)s %(threadName)s %(funcName)20s() ] %(message)s"
|
433
|
+
)
|
434
|
+
logging.getLogger("boto3").setLevel(logging.CRITICAL)
|
435
|
+
logging.getLogger("botocore").setLevel(logging.CRITICAL)
|
436
|
+
logging.getLogger("s3transfer").setLevel(logging.CRITICAL)
|
437
|
+
logging.getLogger("urllib3").setLevel(logging.CRITICAL)
|
438
|
+
|
439
|
+
all_my_orgs(pProfiles, pSkipProfiles, pAccountList, pTiming, pRootOnly, pSaveFilename, pShortform, verbose)
|
440
|
+
|
441
|
+
print()
|
442
|
+
if pTiming:
|
443
|
+
print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
|
444
|
+
print()
|
445
|
+
print("Thanks for using this script")
|
446
|
+
print()
|