runbooks 0.2.5__py3-none-any.whl → 0.7.0__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 (249) hide show
  1. conftest.py +26 -0
  2. jupyter-agent/.env +2 -0
  3. jupyter-agent/.env.template +2 -0
  4. jupyter-agent/.gitattributes +35 -0
  5. jupyter-agent/.gradio/certificate.pem +31 -0
  6. jupyter-agent/README.md +16 -0
  7. jupyter-agent/__main__.log +8 -0
  8. jupyter-agent/app.py +256 -0
  9. jupyter-agent/cloudops-agent.png +0 -0
  10. jupyter-agent/ds-system-prompt.txt +154 -0
  11. jupyter-agent/jupyter-agent.png +0 -0
  12. jupyter-agent/llama3_template.jinja +123 -0
  13. jupyter-agent/requirements.txt +9 -0
  14. jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +68 -0
  15. jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +91 -0
  16. jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +91 -0
  17. jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +57 -0
  18. jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +53 -0
  19. jupyter-agent/tmp/jupyter-agent.ipynb +27 -0
  20. jupyter-agent/utils.py +409 -0
  21. runbooks/__init__.py +71 -3
  22. runbooks/__main__.py +13 -0
  23. runbooks/aws/ec2_describe_instances.py +1 -1
  24. runbooks/aws/ec2_run_instances.py +8 -2
  25. runbooks/aws/ec2_start_stop_instances.py +17 -4
  26. runbooks/aws/ec2_unused_volumes.py +5 -1
  27. runbooks/aws/s3_create_bucket.py +4 -2
  28. runbooks/aws/s3_list_objects.py +6 -1
  29. runbooks/aws/tagging_lambda_handler.py +13 -2
  30. runbooks/aws/tags.json +12 -0
  31. runbooks/base.py +353 -0
  32. runbooks/cfat/README.md +49 -0
  33. runbooks/cfat/__init__.py +74 -0
  34. runbooks/cfat/app.ts +644 -0
  35. runbooks/cfat/assessment/__init__.py +40 -0
  36. runbooks/cfat/assessment/asana-import.csv +39 -0
  37. runbooks/cfat/assessment/cfat-checks.csv +31 -0
  38. runbooks/cfat/assessment/cfat.txt +520 -0
  39. runbooks/cfat/assessment/collectors.py +200 -0
  40. runbooks/cfat/assessment/jira-import.csv +39 -0
  41. runbooks/cfat/assessment/runner.py +387 -0
  42. runbooks/cfat/assessment/validators.py +290 -0
  43. runbooks/cfat/cli.py +103 -0
  44. runbooks/cfat/docs/asana-import.csv +24 -0
  45. runbooks/cfat/docs/cfat-checks.csv +31 -0
  46. runbooks/cfat/docs/cfat.txt +335 -0
  47. runbooks/cfat/docs/checks-output.png +0 -0
  48. runbooks/cfat/docs/cloudshell-console-run.png +0 -0
  49. runbooks/cfat/docs/cloudshell-download.png +0 -0
  50. runbooks/cfat/docs/cloudshell-output.png +0 -0
  51. runbooks/cfat/docs/downloadfile.png +0 -0
  52. runbooks/cfat/docs/jira-import.csv +24 -0
  53. runbooks/cfat/docs/open-cloudshell.png +0 -0
  54. runbooks/cfat/docs/report-header.png +0 -0
  55. runbooks/cfat/models.py +1026 -0
  56. runbooks/cfat/package-lock.json +5116 -0
  57. runbooks/cfat/package.json +38 -0
  58. runbooks/cfat/report.py +496 -0
  59. runbooks/cfat/reporting/__init__.py +46 -0
  60. runbooks/cfat/reporting/exporters.py +337 -0
  61. runbooks/cfat/reporting/formatters.py +496 -0
  62. runbooks/cfat/reporting/templates.py +135 -0
  63. runbooks/cfat/run-assessment.sh +23 -0
  64. runbooks/cfat/runner.py +69 -0
  65. runbooks/cfat/src/actions/check-cloudtrail-existence.ts +43 -0
  66. runbooks/cfat/src/actions/check-config-existence.ts +37 -0
  67. runbooks/cfat/src/actions/check-control-tower.ts +37 -0
  68. runbooks/cfat/src/actions/check-ec2-existence.ts +46 -0
  69. runbooks/cfat/src/actions/check-iam-users.ts +50 -0
  70. runbooks/cfat/src/actions/check-legacy-cur.ts +30 -0
  71. runbooks/cfat/src/actions/check-org-cloudformation.ts +30 -0
  72. runbooks/cfat/src/actions/check-vpc-existence.ts +43 -0
  73. runbooks/cfat/src/actions/create-asanaimport.ts +14 -0
  74. runbooks/cfat/src/actions/create-backlog.ts +372 -0
  75. runbooks/cfat/src/actions/create-jiraimport.ts +15 -0
  76. runbooks/cfat/src/actions/create-report.ts +616 -0
  77. runbooks/cfat/src/actions/define-account-type.ts +51 -0
  78. runbooks/cfat/src/actions/get-enabled-org-policy-types.ts +40 -0
  79. runbooks/cfat/src/actions/get-enabled-org-services.ts +26 -0
  80. runbooks/cfat/src/actions/get-idc-info.ts +34 -0
  81. runbooks/cfat/src/actions/get-org-da-accounts.ts +34 -0
  82. runbooks/cfat/src/actions/get-org-details.ts +35 -0
  83. runbooks/cfat/src/actions/get-org-member-accounts.ts +44 -0
  84. runbooks/cfat/src/actions/get-org-ous.ts +35 -0
  85. runbooks/cfat/src/actions/get-regions.ts +22 -0
  86. runbooks/cfat/src/actions/zip-assessment.ts +27 -0
  87. runbooks/cfat/src/types/index.d.ts +147 -0
  88. runbooks/cfat/tests/__init__.py +141 -0
  89. runbooks/cfat/tests/test_cli.py +340 -0
  90. runbooks/cfat/tests/test_integration.py +290 -0
  91. runbooks/cfat/tests/test_models.py +505 -0
  92. runbooks/cfat/tests/test_reporting.py +354 -0
  93. runbooks/cfat/tsconfig.json +16 -0
  94. runbooks/cfat/webpack.config.cjs +27 -0
  95. runbooks/config.py +260 -0
  96. runbooks/finops/README.md +337 -0
  97. runbooks/finops/__init__.py +86 -0
  98. runbooks/finops/aws_client.py +245 -0
  99. runbooks/finops/cli.py +151 -0
  100. runbooks/finops/cost_processor.py +410 -0
  101. runbooks/finops/dashboard_runner.py +448 -0
  102. runbooks/finops/helpers.py +355 -0
  103. runbooks/finops/main.py +14 -0
  104. runbooks/finops/profile_processor.py +174 -0
  105. runbooks/finops/types.py +66 -0
  106. runbooks/finops/visualisations.py +80 -0
  107. runbooks/inventory/.gitignore +354 -0
  108. runbooks/inventory/ArgumentsClass.py +261 -0
  109. runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +619 -0
  110. runbooks/inventory/Inventory_Modules.py +6130 -0
  111. runbooks/inventory/LandingZone/delete_lz.py +1075 -0
  112. runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +738 -0
  113. runbooks/inventory/README.md +1320 -0
  114. runbooks/inventory/__init__.py +62 -0
  115. runbooks/inventory/account_class.py +532 -0
  116. runbooks/inventory/all_my_instances_wrapper.py +123 -0
  117. runbooks/inventory/aws_decorators.py +201 -0
  118. runbooks/inventory/aws_organization.png +0 -0
  119. runbooks/inventory/cfn_move_stack_instances.py +1526 -0
  120. runbooks/inventory/check_cloudtrail_compliance.py +614 -0
  121. runbooks/inventory/check_controltower_readiness.py +1107 -0
  122. runbooks/inventory/check_landingzone_readiness.py +711 -0
  123. runbooks/inventory/cloudtrail.md +727 -0
  124. runbooks/inventory/collectors/__init__.py +20 -0
  125. runbooks/inventory/collectors/aws_compute.py +518 -0
  126. runbooks/inventory/collectors/aws_networking.py +275 -0
  127. runbooks/inventory/collectors/base.py +222 -0
  128. runbooks/inventory/core/__init__.py +19 -0
  129. runbooks/inventory/core/collector.py +303 -0
  130. runbooks/inventory/core/formatter.py +296 -0
  131. runbooks/inventory/delete_s3_buckets_objects.py +169 -0
  132. runbooks/inventory/discovery.md +81 -0
  133. runbooks/inventory/draw_org_structure.py +748 -0
  134. runbooks/inventory/ec2_vpc_utils.py +341 -0
  135. runbooks/inventory/find_cfn_drift_detection.py +272 -0
  136. runbooks/inventory/find_cfn_orphaned_stacks.py +719 -0
  137. runbooks/inventory/find_cfn_stackset_drift.py +733 -0
  138. runbooks/inventory/find_ec2_security_groups.py +669 -0
  139. runbooks/inventory/find_landingzone_versions.py +201 -0
  140. runbooks/inventory/find_vpc_flow_logs.py +1221 -0
  141. runbooks/inventory/inventory.sh +659 -0
  142. runbooks/inventory/list_cfn_stacks.py +558 -0
  143. runbooks/inventory/list_cfn_stackset_operation_results.py +252 -0
  144. runbooks/inventory/list_cfn_stackset_operations.py +734 -0
  145. runbooks/inventory/list_cfn_stacksets.py +453 -0
  146. runbooks/inventory/list_config_recorders_delivery_channels.py +681 -0
  147. runbooks/inventory/list_ds_directories.py +354 -0
  148. runbooks/inventory/list_ec2_availability_zones.py +286 -0
  149. runbooks/inventory/list_ec2_ebs_volumes.py +244 -0
  150. runbooks/inventory/list_ec2_instances.py +425 -0
  151. runbooks/inventory/list_ecs_clusters_and_tasks.py +562 -0
  152. runbooks/inventory/list_elbs_load_balancers.py +411 -0
  153. runbooks/inventory/list_enis_network_interfaces.py +526 -0
  154. runbooks/inventory/list_guardduty_detectors.py +568 -0
  155. runbooks/inventory/list_iam_policies.py +404 -0
  156. runbooks/inventory/list_iam_roles.py +518 -0
  157. runbooks/inventory/list_iam_saml_providers.py +359 -0
  158. runbooks/inventory/list_lambda_functions.py +882 -0
  159. runbooks/inventory/list_org_accounts.py +446 -0
  160. runbooks/inventory/list_org_accounts_users.py +354 -0
  161. runbooks/inventory/list_rds_db_instances.py +406 -0
  162. runbooks/inventory/list_route53_hosted_zones.py +318 -0
  163. runbooks/inventory/list_servicecatalog_provisioned_products.py +575 -0
  164. runbooks/inventory/list_sns_topics.py +360 -0
  165. runbooks/inventory/list_ssm_parameters.py +402 -0
  166. runbooks/inventory/list_vpc_subnets.py +433 -0
  167. runbooks/inventory/list_vpcs.py +422 -0
  168. runbooks/inventory/lockdown_cfn_stackset_role.py +224 -0
  169. runbooks/inventory/models/__init__.py +24 -0
  170. runbooks/inventory/models/account.py +192 -0
  171. runbooks/inventory/models/inventory.py +309 -0
  172. runbooks/inventory/models/resource.py +247 -0
  173. runbooks/inventory/recover_cfn_stack_ids.py +205 -0
  174. runbooks/inventory/requirements.txt +12 -0
  175. runbooks/inventory/run_on_multi_accounts.py +211 -0
  176. runbooks/inventory/tests/common_test_data.py +3661 -0
  177. runbooks/inventory/tests/common_test_functions.py +204 -0
  178. runbooks/inventory/tests/setup.py +24 -0
  179. runbooks/inventory/tests/src.py +18 -0
  180. runbooks/inventory/tests/test_cfn_describe_stacks.py +208 -0
  181. runbooks/inventory/tests/test_ec2_describe_instances.py +162 -0
  182. runbooks/inventory/tests/test_inventory_modules.py +55 -0
  183. runbooks/inventory/tests/test_lambda_list_functions.py +86 -0
  184. runbooks/inventory/tests/test_moto_integration_example.py +273 -0
  185. runbooks/inventory/tests/test_org_list_accounts.py +49 -0
  186. runbooks/inventory/update_aws_actions.py +173 -0
  187. runbooks/inventory/update_cfn_stacksets.py +1215 -0
  188. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +294 -0
  189. runbooks/inventory/update_iam_roles_cross_accounts.py +478 -0
  190. runbooks/inventory/update_s3_public_access_block.py +539 -0
  191. runbooks/inventory/utils/__init__.py +23 -0
  192. runbooks/inventory/utils/aws_helpers.py +510 -0
  193. runbooks/inventory/utils/threading_utils.py +493 -0
  194. runbooks/inventory/utils/validation.py +682 -0
  195. runbooks/inventory/verify_ec2_security_groups.py +1430 -0
  196. runbooks/main.py +1004 -0
  197. runbooks/organizations/__init__.py +12 -0
  198. runbooks/organizations/manager.py +374 -0
  199. runbooks/security/README.md +447 -0
  200. runbooks/security/__init__.py +71 -0
  201. runbooks/{security_baseline → security}/checklist/alternate_contacts.py +8 -1
  202. runbooks/{security_baseline → security}/checklist/bucket_public_access.py +4 -1
  203. runbooks/{security_baseline → security}/checklist/cloudwatch_alarm_configuration.py +9 -2
  204. runbooks/{security_baseline → security}/checklist/guardduty_enabled.py +9 -2
  205. runbooks/{security_baseline → security}/checklist/multi_region_instance_usage.py +5 -1
  206. runbooks/{security_baseline → security}/checklist/root_access_key.py +6 -1
  207. runbooks/{security_baseline → security}/config-origin.json +1 -1
  208. runbooks/{security_baseline → security}/config.json +1 -1
  209. runbooks/{security_baseline → security}/permission.json +1 -1
  210. runbooks/{security_baseline → security}/report_generator.py +10 -2
  211. runbooks/{security_baseline → security}/report_template_en.html +7 -7
  212. runbooks/{security_baseline → security}/report_template_jp.html +7 -7
  213. runbooks/{security_baseline → security}/report_template_kr.html +12 -12
  214. runbooks/{security_baseline → security}/report_template_vn.html +7 -7
  215. runbooks/{security_baseline → security}/run_script.py +8 -2
  216. runbooks/{security_baseline → security}/security_baseline_tester.py +12 -4
  217. runbooks/{security_baseline → security}/utils/common.py +5 -1
  218. runbooks/utils/__init__.py +204 -0
  219. runbooks-0.7.0.dist-info/METADATA +375 -0
  220. runbooks-0.7.0.dist-info/RECORD +249 -0
  221. {runbooks-0.2.5.dist-info → runbooks-0.7.0.dist-info}/WHEEL +1 -1
  222. runbooks-0.7.0.dist-info/entry_points.txt +7 -0
  223. runbooks-0.7.0.dist-info/licenses/LICENSE +201 -0
  224. runbooks-0.7.0.dist-info/top_level.txt +3 -0
  225. runbooks/python101/calculator.py +0 -34
  226. runbooks/python101/config.py +0 -1
  227. runbooks/python101/exceptions.py +0 -16
  228. runbooks/python101/file_manager.py +0 -218
  229. runbooks/python101/toolkit.py +0 -153
  230. runbooks-0.2.5.dist-info/METADATA +0 -439
  231. runbooks-0.2.5.dist-info/RECORD +0 -61
  232. runbooks-0.2.5.dist-info/entry_points.txt +0 -3
  233. runbooks-0.2.5.dist-info/top_level.txt +0 -1
  234. /runbooks/{security_baseline/__init__.py → inventory/tests/script_test_data.py} +0 -0
  235. /runbooks/{security_baseline → security}/checklist/__init__.py +0 -0
  236. /runbooks/{security_baseline → security}/checklist/account_level_bucket_public_access.py +0 -0
  237. /runbooks/{security_baseline → security}/checklist/direct_attached_policy.py +0 -0
  238. /runbooks/{security_baseline → security}/checklist/iam_password_policy.py +0 -0
  239. /runbooks/{security_baseline → security}/checklist/iam_user_mfa.py +0 -0
  240. /runbooks/{security_baseline → security}/checklist/multi_region_trail.py +0 -0
  241. /runbooks/{security_baseline → security}/checklist/root_mfa.py +0 -0
  242. /runbooks/{security_baseline → security}/checklist/root_usage.py +0 -0
  243. /runbooks/{security_baseline → security}/checklist/trail_enabled.py +0 -0
  244. /runbooks/{security_baseline → security}/checklist/trusted_advisor.py +0 -0
  245. /runbooks/{security_baseline → security}/utils/__init__.py +0 -0
  246. /runbooks/{security_baseline → security}/utils/enums.py +0 -0
  247. /runbooks/{security_baseline → security}/utils/language.py +0 -0
  248. /runbooks/{security_baseline → security}/utils/level_const.py +0 -0
  249. /runbooks/{security_baseline → security}/utils/permission_list.py +0 -0
@@ -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()