runbooks 0.2.5__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.
Files changed (221) hide show
  1. conftest.py +26 -0
  2. jupyter-agent/.env.template +2 -0
  3. jupyter-agent/.gitattributes +35 -0
  4. jupyter-agent/README.md +16 -0
  5. jupyter-agent/app.py +256 -0
  6. jupyter-agent/cloudops-agent.png +0 -0
  7. jupyter-agent/ds-system-prompt.txt +154 -0
  8. jupyter-agent/jupyter-agent.png +0 -0
  9. jupyter-agent/llama3_template.jinja +123 -0
  10. jupyter-agent/requirements.txt +9 -0
  11. jupyter-agent/utils.py +409 -0
  12. runbooks/__init__.py +71 -3
  13. runbooks/__main__.py +13 -0
  14. runbooks/aws/ec2_describe_instances.py +1 -1
  15. runbooks/aws/ec2_run_instances.py +8 -2
  16. runbooks/aws/ec2_start_stop_instances.py +17 -4
  17. runbooks/aws/ec2_unused_volumes.py +5 -1
  18. runbooks/aws/s3_create_bucket.py +4 -2
  19. runbooks/aws/s3_list_objects.py +6 -1
  20. runbooks/aws/tagging_lambda_handler.py +13 -2
  21. runbooks/aws/tags.json +12 -0
  22. runbooks/base.py +353 -0
  23. runbooks/cfat/README.md +49 -0
  24. runbooks/cfat/__init__.py +74 -0
  25. runbooks/cfat/app.ts +644 -0
  26. runbooks/cfat/assessment/__init__.py +40 -0
  27. runbooks/cfat/assessment/asana-import.csv +39 -0
  28. runbooks/cfat/assessment/cfat-checks.csv +31 -0
  29. runbooks/cfat/assessment/cfat.txt +520 -0
  30. runbooks/cfat/assessment/collectors.py +200 -0
  31. runbooks/cfat/assessment/jira-import.csv +39 -0
  32. runbooks/cfat/assessment/runner.py +387 -0
  33. runbooks/cfat/assessment/validators.py +290 -0
  34. runbooks/cfat/cli.py +103 -0
  35. runbooks/cfat/docs/asana-import.csv +24 -0
  36. runbooks/cfat/docs/cfat-checks.csv +31 -0
  37. runbooks/cfat/docs/cfat.txt +335 -0
  38. runbooks/cfat/docs/checks-output.png +0 -0
  39. runbooks/cfat/docs/cloudshell-console-run.png +0 -0
  40. runbooks/cfat/docs/cloudshell-download.png +0 -0
  41. runbooks/cfat/docs/cloudshell-output.png +0 -0
  42. runbooks/cfat/docs/downloadfile.png +0 -0
  43. runbooks/cfat/docs/jira-import.csv +24 -0
  44. runbooks/cfat/docs/open-cloudshell.png +0 -0
  45. runbooks/cfat/docs/report-header.png +0 -0
  46. runbooks/cfat/models.py +1026 -0
  47. runbooks/cfat/package-lock.json +5116 -0
  48. runbooks/cfat/package.json +38 -0
  49. runbooks/cfat/report.py +496 -0
  50. runbooks/cfat/reporting/__init__.py +46 -0
  51. runbooks/cfat/reporting/exporters.py +337 -0
  52. runbooks/cfat/reporting/formatters.py +496 -0
  53. runbooks/cfat/reporting/templates.py +135 -0
  54. runbooks/cfat/run-assessment.sh +23 -0
  55. runbooks/cfat/runner.py +69 -0
  56. runbooks/cfat/src/actions/check-cloudtrail-existence.ts +43 -0
  57. runbooks/cfat/src/actions/check-config-existence.ts +37 -0
  58. runbooks/cfat/src/actions/check-control-tower.ts +37 -0
  59. runbooks/cfat/src/actions/check-ec2-existence.ts +46 -0
  60. runbooks/cfat/src/actions/check-iam-users.ts +50 -0
  61. runbooks/cfat/src/actions/check-legacy-cur.ts +30 -0
  62. runbooks/cfat/src/actions/check-org-cloudformation.ts +30 -0
  63. runbooks/cfat/src/actions/check-vpc-existence.ts +43 -0
  64. runbooks/cfat/src/actions/create-asanaimport.ts +14 -0
  65. runbooks/cfat/src/actions/create-backlog.ts +372 -0
  66. runbooks/cfat/src/actions/create-jiraimport.ts +15 -0
  67. runbooks/cfat/src/actions/create-report.ts +616 -0
  68. runbooks/cfat/src/actions/define-account-type.ts +51 -0
  69. runbooks/cfat/src/actions/get-enabled-org-policy-types.ts +40 -0
  70. runbooks/cfat/src/actions/get-enabled-org-services.ts +26 -0
  71. runbooks/cfat/src/actions/get-idc-info.ts +34 -0
  72. runbooks/cfat/src/actions/get-org-da-accounts.ts +34 -0
  73. runbooks/cfat/src/actions/get-org-details.ts +35 -0
  74. runbooks/cfat/src/actions/get-org-member-accounts.ts +44 -0
  75. runbooks/cfat/src/actions/get-org-ous.ts +35 -0
  76. runbooks/cfat/src/actions/get-regions.ts +22 -0
  77. runbooks/cfat/src/actions/zip-assessment.ts +27 -0
  78. runbooks/cfat/src/types/index.d.ts +147 -0
  79. runbooks/cfat/tests/__init__.py +141 -0
  80. runbooks/cfat/tests/test_cli.py +340 -0
  81. runbooks/cfat/tests/test_integration.py +290 -0
  82. runbooks/cfat/tests/test_models.py +505 -0
  83. runbooks/cfat/tests/test_reporting.py +354 -0
  84. runbooks/cfat/tsconfig.json +16 -0
  85. runbooks/cfat/webpack.config.cjs +27 -0
  86. runbooks/config.py +260 -0
  87. runbooks/finops/__init__.py +88 -0
  88. runbooks/finops/aws_client.py +245 -0
  89. runbooks/finops/cli.py +151 -0
  90. runbooks/finops/cost_processor.py +410 -0
  91. runbooks/finops/dashboard_runner.py +448 -0
  92. runbooks/finops/helpers.py +355 -0
  93. runbooks/finops/main.py +14 -0
  94. runbooks/finops/profile_processor.py +174 -0
  95. runbooks/finops/types.py +66 -0
  96. runbooks/finops/visualisations.py +80 -0
  97. runbooks/inventory/.gitignore +354 -0
  98. runbooks/inventory/ArgumentsClass.py +261 -0
  99. runbooks/inventory/Inventory_Modules.py +6130 -0
  100. runbooks/inventory/LandingZone/delete_lz.py +1075 -0
  101. runbooks/inventory/README.md +1320 -0
  102. runbooks/inventory/__init__.py +62 -0
  103. runbooks/inventory/account_class.py +532 -0
  104. runbooks/inventory/all_my_instances_wrapper.py +123 -0
  105. runbooks/inventory/aws_decorators.py +201 -0
  106. runbooks/inventory/cfn_move_stack_instances.py +1526 -0
  107. runbooks/inventory/check_cloudtrail_compliance.py +614 -0
  108. runbooks/inventory/check_controltower_readiness.py +1107 -0
  109. runbooks/inventory/check_landingzone_readiness.py +711 -0
  110. runbooks/inventory/cloudtrail.md +727 -0
  111. runbooks/inventory/collectors/__init__.py +20 -0
  112. runbooks/inventory/collectors/aws_compute.py +518 -0
  113. runbooks/inventory/collectors/aws_networking.py +275 -0
  114. runbooks/inventory/collectors/base.py +222 -0
  115. runbooks/inventory/core/__init__.py +19 -0
  116. runbooks/inventory/core/collector.py +303 -0
  117. runbooks/inventory/core/formatter.py +296 -0
  118. runbooks/inventory/delete_s3_buckets_objects.py +169 -0
  119. runbooks/inventory/discovery.md +81 -0
  120. runbooks/inventory/draw_org_structure.py +748 -0
  121. runbooks/inventory/ec2_vpc_utils.py +341 -0
  122. runbooks/inventory/find_cfn_drift_detection.py +272 -0
  123. runbooks/inventory/find_cfn_orphaned_stacks.py +719 -0
  124. runbooks/inventory/find_cfn_stackset_drift.py +733 -0
  125. runbooks/inventory/find_ec2_security_groups.py +669 -0
  126. runbooks/inventory/find_landingzone_versions.py +201 -0
  127. runbooks/inventory/find_vpc_flow_logs.py +1221 -0
  128. runbooks/inventory/inventory.sh +659 -0
  129. runbooks/inventory/list_cfn_stacks.py +558 -0
  130. runbooks/inventory/list_cfn_stackset_operation_results.py +252 -0
  131. runbooks/inventory/list_cfn_stackset_operations.py +734 -0
  132. runbooks/inventory/list_cfn_stacksets.py +453 -0
  133. runbooks/inventory/list_config_recorders_delivery_channels.py +681 -0
  134. runbooks/inventory/list_ds_directories.py +354 -0
  135. runbooks/inventory/list_ec2_availability_zones.py +286 -0
  136. runbooks/inventory/list_ec2_ebs_volumes.py +244 -0
  137. runbooks/inventory/list_ec2_instances.py +425 -0
  138. runbooks/inventory/list_ecs_clusters_and_tasks.py +562 -0
  139. runbooks/inventory/list_elbs_load_balancers.py +411 -0
  140. runbooks/inventory/list_enis_network_interfaces.py +526 -0
  141. runbooks/inventory/list_guardduty_detectors.py +568 -0
  142. runbooks/inventory/list_iam_policies.py +404 -0
  143. runbooks/inventory/list_iam_roles.py +518 -0
  144. runbooks/inventory/list_iam_saml_providers.py +359 -0
  145. runbooks/inventory/list_lambda_functions.py +882 -0
  146. runbooks/inventory/list_org_accounts.py +446 -0
  147. runbooks/inventory/list_org_accounts_users.py +354 -0
  148. runbooks/inventory/list_rds_db_instances.py +406 -0
  149. runbooks/inventory/list_route53_hosted_zones.py +318 -0
  150. runbooks/inventory/list_servicecatalog_provisioned_products.py +575 -0
  151. runbooks/inventory/list_sns_topics.py +360 -0
  152. runbooks/inventory/list_ssm_parameters.py +402 -0
  153. runbooks/inventory/list_vpc_subnets.py +433 -0
  154. runbooks/inventory/list_vpcs.py +422 -0
  155. runbooks/inventory/lockdown_cfn_stackset_role.py +224 -0
  156. runbooks/inventory/models/__init__.py +24 -0
  157. runbooks/inventory/models/account.py +192 -0
  158. runbooks/inventory/models/inventory.py +309 -0
  159. runbooks/inventory/models/resource.py +247 -0
  160. runbooks/inventory/recover_cfn_stack_ids.py +205 -0
  161. runbooks/inventory/requirements.txt +12 -0
  162. runbooks/inventory/run_on_multi_accounts.py +211 -0
  163. runbooks/inventory/tests/common_test_data.py +3661 -0
  164. runbooks/inventory/tests/common_test_functions.py +204 -0
  165. runbooks/inventory/tests/script_test_data.py +0 -0
  166. runbooks/inventory/tests/setup.py +24 -0
  167. runbooks/inventory/tests/src.py +18 -0
  168. runbooks/inventory/tests/test_cfn_describe_stacks.py +208 -0
  169. runbooks/inventory/tests/test_ec2_describe_instances.py +162 -0
  170. runbooks/inventory/tests/test_inventory_modules.py +55 -0
  171. runbooks/inventory/tests/test_lambda_list_functions.py +86 -0
  172. runbooks/inventory/tests/test_moto_integration_example.py +273 -0
  173. runbooks/inventory/tests/test_org_list_accounts.py +49 -0
  174. runbooks/inventory/update_aws_actions.py +173 -0
  175. runbooks/inventory/update_cfn_stacksets.py +1215 -0
  176. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +294 -0
  177. runbooks/inventory/update_iam_roles_cross_accounts.py +478 -0
  178. runbooks/inventory/update_s3_public_access_block.py +539 -0
  179. runbooks/inventory/utils/__init__.py +23 -0
  180. runbooks/inventory/utils/aws_helpers.py +510 -0
  181. runbooks/inventory/utils/threading_utils.py +493 -0
  182. runbooks/inventory/utils/validation.py +682 -0
  183. runbooks/inventory/verify_ec2_security_groups.py +1430 -0
  184. runbooks/main.py +785 -0
  185. runbooks/organizations/__init__.py +12 -0
  186. runbooks/organizations/manager.py +374 -0
  187. runbooks/security_baseline/README.md +324 -0
  188. runbooks/security_baseline/checklist/alternate_contacts.py +8 -1
  189. runbooks/security_baseline/checklist/bucket_public_access.py +4 -1
  190. runbooks/security_baseline/checklist/cloudwatch_alarm_configuration.py +9 -2
  191. runbooks/security_baseline/checklist/guardduty_enabled.py +9 -2
  192. runbooks/security_baseline/checklist/multi_region_instance_usage.py +5 -1
  193. runbooks/security_baseline/checklist/root_access_key.py +6 -1
  194. runbooks/security_baseline/config-origin.json +1 -1
  195. runbooks/security_baseline/config.json +1 -1
  196. runbooks/security_baseline/permission.json +1 -1
  197. runbooks/security_baseline/report_generator.py +10 -2
  198. runbooks/security_baseline/report_template_en.html +7 -7
  199. runbooks/security_baseline/report_template_jp.html +7 -7
  200. runbooks/security_baseline/report_template_kr.html +12 -12
  201. runbooks/security_baseline/report_template_vn.html +7 -7
  202. runbooks/security_baseline/requirements.txt +7 -0
  203. runbooks/security_baseline/run_script.py +8 -2
  204. runbooks/security_baseline/security_baseline_tester.py +10 -2
  205. runbooks/security_baseline/utils/common.py +5 -1
  206. runbooks/utils/__init__.py +204 -0
  207. runbooks-0.6.1.dist-info/METADATA +373 -0
  208. runbooks-0.6.1.dist-info/RECORD +237 -0
  209. {runbooks-0.2.5.dist-info → runbooks-0.6.1.dist-info}/WHEEL +1 -1
  210. runbooks-0.6.1.dist-info/entry_points.txt +7 -0
  211. runbooks-0.6.1.dist-info/licenses/LICENSE +201 -0
  212. runbooks-0.6.1.dist-info/top_level.txt +3 -0
  213. runbooks/python101/calculator.py +0 -34
  214. runbooks/python101/config.py +0 -1
  215. runbooks/python101/exceptions.py +0 -16
  216. runbooks/python101/file_manager.py +0 -218
  217. runbooks/python101/toolkit.py +0 -153
  218. runbooks-0.2.5.dist-info/METADATA +0 -439
  219. runbooks-0.2.5.dist-info/RECORD +0 -61
  220. runbooks-0.2.5.dist-info/entry_points.txt +0 -3
  221. runbooks-0.2.5.dist-info/top_level.txt +0 -1
@@ -0,0 +1,518 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ AWS IAM Role Inventory and Management Tool
4
+
5
+ CRITICAL SECURITY WARNING: This script can DELETE IAM roles across multiple AWS accounts.
6
+ Exercise extreme caution when using deletion features in production environments.
7
+
8
+ Purpose:
9
+ Discovers and inventories IAM roles across AWS Organizations with optional deletion
10
+ capabilities. Provides comprehensive role analysis with fragment-based filtering
11
+ and bulk management operations for enterprise IAM governance.
12
+
13
+ AWS API Operations:
14
+ - iam.list_roles(): Primary API for role discovery
15
+ - iam.list_attached_role_policies(): Policy attachment analysis
16
+ - iam.detach_role_policy(): Policy detachment for deletion
17
+ - iam.delete_role_policy(): Inline policy cleanup
18
+ - iam.delete_role(): Role deletion operation
19
+
20
+ Destructive Operations:
21
+ The +delete/+d flag enables IRREVERSIBLE role deletion across multiple accounts.
22
+ Includes safety confirmations and force options for automation scenarios.
23
+
24
+ Security Features:
25
+ - Multi-stage confirmation for destructive operations
26
+ - Comprehensive policy detachment before role deletion
27
+ - Force flag with delay for automation safety
28
+ - Detailed audit logging of all operations
29
+
30
+ Usage:
31
+ python list_iam_roles.py -p <profile> --fragment <role_name_fragment>
32
+ python list_iam_roles.py -p <profile> --fragment <fragment> +delete
33
+
34
+ Author: AWS Cloud Foundations Team
35
+ Version: 2023.11.06
36
+ Maintained: IAM Security Team
37
+ """
38
+
39
+ import logging
40
+ import sys
41
+ from time import sleep, time
42
+
43
+ import boto3
44
+ from ArgumentsClass import CommonArguments
45
+ from botocore.exceptions import ClientError
46
+ from colorama import Fore, init
47
+ from Inventory_Modules import display_results, find_in, get_all_credentials
48
+
49
+ init()
50
+ __version__ = "2023.11.06"
51
+
52
+
53
+ ###########################
54
+ def parse_args(args):
55
+ """
56
+ Parse and validate command-line arguments for IAM role inventory operations.
57
+
58
+ Configures the argument parser with IAM-specific options including role fragment
59
+ filtering, deletion capabilities, and safety controls. Uses the standardized
60
+ CommonArguments framework for consistency across inventory scripts.
61
+
62
+ Args:
63
+ args (list): Command-line arguments to parse (typically sys.argv[1:])
64
+
65
+ Returns:
66
+ argparse.Namespace: Parsed arguments containing:
67
+ - Profiles: AWS profiles for multi-account access
68
+ - Regions: Target AWS regions (IAM is global but needed for sessions)
69
+ - Fragments: Role name fragments for filtering
70
+ - pDelete: DANGEROUS flag enabling role deletion
71
+ - Force: Skip confirmation prompts for automation
72
+ - Exact: Use exact matching instead of substring matching
73
+ - Other standard framework arguments
74
+
75
+ Security Note:
76
+ The pDelete parameter enables destructive operations that can impact
77
+ AWS account security posture. Use with extreme caution.
78
+ """
79
+ parser = CommonArguments()
80
+ parser.my_parser.description = (
81
+ "We're going to find all roles within any of the accounts we have access to, given the profile(s) provided."
82
+ )
83
+ parser.multiprofile()
84
+ parser.multiregion()
85
+ parser.extendedargs()
86
+ parser.fragment()
87
+ parser.deletion()
88
+ parser.rootOnly()
89
+ parser.verbosity()
90
+ parser.timing()
91
+ parser.save_to_file()
92
+ parser.version(__version__)
93
+ parser.my_parser.add_argument(
94
+ "+d",
95
+ "+delete",
96
+ dest="pDelete",
97
+ action="store_const",
98
+ const=True,
99
+ default=False,
100
+ help="Whether you'd like to delete that specified role.",
101
+ )
102
+ return parser.my_parser.parse_args(args)
103
+
104
+
105
+ def my_delete_role(fRoleList):
106
+ """
107
+ Execute comprehensive IAM role deletion with proper cleanup sequence.
108
+
109
+ CRITICAL SECURITY OPERATION: This function performs IRREVERSIBLE deletion
110
+ of IAM roles including all attached policies. Must follow AWS IAM deletion
111
+ sequence: detach managed policies -> delete inline policies -> delete role.
112
+
113
+ Args:
114
+ fRoleList (dict): Role deletion context containing:
115
+ - AccessKeyId, SecretAccessKey, SessionToken: AWS credentials
116
+ - Region: AWS region for session establishment
117
+ - RoleName: IAM role name to delete
118
+ - AccountId: Target AWS account (for logging)
119
+
120
+ Returns:
121
+ bool: True if deletion successful, False if any step failed
122
+
123
+ Deletion Sequence:
124
+ 1. Establish authenticated IAM session using provided credentials
125
+ 2. List and detach all AWS managed and customer-managed policies
126
+ 3. List and delete all inline policies attached to the role
127
+ 4. Delete the IAM role itself (only works after all policies removed)
128
+
129
+ Error Handling:
130
+ - ClientError: AWS API errors (permissions, throttling, dependencies)
131
+ - Logs all errors for audit trail and troubleshooting
132
+ - Returns False on any failure to prevent inconsistent state
133
+
134
+ Security Implications:
135
+ - Role deletion affects all services and resources using the role
136
+ - Cannot be undone - permanent loss of role and policy associations
137
+ - May break applications, services, or automation depending on role
138
+
139
+ AWS IAM Constraints:
140
+ - Cannot delete role with attached policies (managed or inline)
141
+ - Cannot delete role if it has active sessions or is being assumed
142
+ - Some AWS service-linked roles cannot be deleted
143
+ """
144
+ # Establish authenticated IAM session using provided credentials
145
+ iam_session = boto3.Session(
146
+ aws_access_key_id=fRoleList["AccessKeyId"],
147
+ aws_secret_access_key=fRoleList["SecretAccessKey"],
148
+ aws_session_token=fRoleList["SessionToken"],
149
+ region_name=fRoleList["Region"],
150
+ )
151
+ iam_client = iam_session.client("iam")
152
+
153
+ try:
154
+ # Step 1: Detach all managed policies (AWS managed and customer managed)
155
+ # These are policies attached via PolicyArn (not inline)
156
+ attached_role_policies = iam_client.list_attached_role_policies(RoleName=fRoleList["RoleName"])[
157
+ "AttachedPolicies"
158
+ ]
159
+
160
+ for _ in range(len(attached_role_policies)):
161
+ response = iam_client.detach_role_policy(
162
+ RoleName=fRoleList["RoleName"], PolicyArn=attached_role_policies[_]["PolicyArn"]
163
+ )
164
+ logging.info(f"Detached policy {attached_role_policies[_]['PolicyArn']} from role {fRoleList['RoleName']}")
165
+
166
+ # Step 2: Delete all inline policies (policies embedded directly in the role)
167
+ # These are policy documents stored as part of the role definition
168
+ inline_role_policies = iam_client.list_role_policies(RoleName=fRoleList["RoleName"])["PolicyNames"]
169
+
170
+ for _ in range(len(inline_role_policies)):
171
+ response = iam_client.delete_role_policy(
172
+ RoleName=fRoleList["RoleName"],
173
+ PolicyName=inline_role_policies[_], # Fixed: was accessing dict incorrectly
174
+ )
175
+ logging.info(f"Deleted inline policy {inline_role_policies[_]} from role {fRoleList['RoleName']}")
176
+
177
+ # Step 3: Delete the IAM role itself (only works after all policies removed)
178
+ response = iam_client.delete_role(RoleName=fRoleList["RoleName"])
179
+ logging.warning(
180
+ f"DELETED IAM role {fRoleList['RoleName']} from account {fRoleList.get('AccountId', 'unknown')}"
181
+ )
182
+
183
+ return True
184
+
185
+ except ClientError as my_Error:
186
+ # Log detailed error for troubleshooting and audit purposes
187
+ logging.error(f"Failed to delete IAM role {fRoleList['RoleName']}: {my_Error}")
188
+
189
+ # Common failure scenarios:
190
+ # - Role has active sessions or is being assumed
191
+ # - Role has instance profiles attached
192
+ # - Permissions insufficient for deletion operations
193
+ # - Role is service-linked and managed by AWS service
194
+
195
+ return False
196
+
197
+
198
+ def find_and_collect_roles_across_accounts(fAllCredentials: list, frole_fragments: list) -> list:
199
+ """
200
+ Execute sequential IAM role discovery across multiple AWS accounts and regions.
201
+
202
+ WARNING: This function processes accounts sequentially and should be enhanced
203
+ with multi-threading for better performance in large AWS Organizations.
204
+
205
+ Performs comprehensive IAM role inventory across all provided accounts,
206
+ with optional fragment-based filtering for targeted role discovery.
207
+ Handles AWS IAM API pagination and provides real-time progress feedback.
208
+
209
+ Args:
210
+ fAllCredentials (list): List of credential dictionaries containing:
211
+ - AccessKeyId, SecretAccessKey, SessionToken: AWS credentials
212
+ - Region: AWS region (IAM is global, but needed for session)
213
+ - AccountNumber: AWS account ID for role attribution
214
+ - MgmtAccount: Management account identifier
215
+ - Success: Boolean indicating credential validation status
216
+
217
+ frole_fragments (list): Optional role name fragments for filtering.
218
+ If None, returns all roles. If provided, filters by fragment matching.
219
+
220
+ Returns:
221
+ list: Comprehensive list of IAM role dictionaries containing:
222
+ - RoleName: IAM role name
223
+ - AccountId: Source AWS account ID
224
+ - MgmtAcct: Management account identifier
225
+ - Region: AWS region (for session context)
226
+ - AccessKeyId, SecretAccessKey, SessionToken: Credentials for operations
227
+
228
+ Performance Considerations:
229
+ - Sequential processing may be slow for large Organizations
230
+ - IAM list_roles() API supports pagination for accounts with many roles
231
+ - Real-time progress display shows per-account role counts
232
+
233
+ Error Handling:
234
+ - Skips accounts where credential validation failed
235
+ - AuthFailure: Logs and continues with remaining accounts
236
+ - ClientError: Handles AWS API errors gracefully
237
+ - Pagination: Properly handles truncated IAM responses
238
+
239
+ Filtering Logic:
240
+ - If frole_fragments is None: Returns all discovered roles
241
+ - If frole_fragments provided: Uses find_in() with exact/partial matching
242
+ - Exact matching controlled by global pExact flag
243
+
244
+ TODO: Implement multi-threading pattern similar to other inventory scripts
245
+ for improved performance across large AWS Organizations.
246
+ """
247
+ print()
248
+ if pFragments is None:
249
+ print(f"Listing out all roles across {len(fAllCredentials)} accounts")
250
+ print()
251
+ elif pExact:
252
+ print(
253
+ f"Looking for a role {Fore.RED}exactly{Fore.RESET} named one of these strings {frole_fragments} across {len(fAllCredentials)} accounts"
254
+ )
255
+ print()
256
+ else:
257
+ print(
258
+ f"Looking for a role containing one of these strings {frole_fragments} across {len(fAllCredentials)} accounts"
259
+ )
260
+ print()
261
+
262
+ # Initialize role collection list for aggregating results across all accounts
263
+ Roles = []
264
+
265
+ # Sequential processing of each account (TODO: convert to multi-threading)
266
+ for account in fAllCredentials:
267
+ # Skip accounts where credential validation failed during setup
268
+ if account["Success"]:
269
+ # Establish authenticated IAM session for this account
270
+ # Note: IAM is a global service but session requires region parameter
271
+ iam_session = boto3.Session(
272
+ aws_access_key_id=account["AccessKeyId"],
273
+ aws_secret_access_key=account["SecretAccessKey"],
274
+ aws_session_token=account["SessionToken"],
275
+ region_name=account["Region"], # Required for session, though IAM is global
276
+ )
277
+ iam_client = iam_session.client("iam")
278
+ else:
279
+ # Skip this account due to credential validation failure
280
+ continue
281
+
282
+ try:
283
+ # Call AWS IAM API to list roles in this account
284
+ # Default limit is 100 roles per call, pagination handled below
285
+ response = iam_client.list_roles()
286
+
287
+ # Process first page of roles
288
+ for i in range(len(response["Roles"])):
289
+ # Create standardized role record with metadata and credentials
290
+ # Credentials included to enable subsequent operations (deletion, etc.)
291
+ Roles.append(
292
+ {
293
+ # AWS credentials for role operations
294
+ "AccessKeyId": account["AccessKeyId"],
295
+ "SecretAccessKey": account["SecretAccessKey"],
296
+ "SessionToken": account["SessionToken"],
297
+ # Organizational and account context
298
+ "MgmtAcct": account["MgmtAccount"],
299
+ "Region": account["Region"],
300
+ "AccountId": account["AccountNumber"],
301
+ # IAM role metadata from AWS API response
302
+ "RoleName": response["Roles"][i]["RoleName"],
303
+ }
304
+ )
305
+
306
+ # Track role count for progress display
307
+ num_of_roles_in_account = len(response["Roles"])
308
+
309
+ # Handle AWS IAM API pagination for accounts with >100 roles
310
+ # Enterprise accounts often have hundreds of roles requiring pagination
311
+ while response["IsTruncated"]:
312
+ # Get next page using pagination marker
313
+ response = iam_client.list_roles(Marker=response["Marker"])
314
+
315
+ # Process additional roles from paginated response
316
+ for i in range(len(response["Roles"])):
317
+ Roles.append(
318
+ {
319
+ "AccessKeyId": account["AccessKeyId"],
320
+ "SecretAccessKey": account["SecretAccessKey"],
321
+ "SessionToken": account["SessionToken"],
322
+ "MgmtAcct": account["MgmtAccount"],
323
+ "Region": account["Region"],
324
+ "AccountId": account["AccountNumber"],
325
+ "RoleName": response["Roles"][i]["RoleName"],
326
+ }
327
+ )
328
+
329
+ # Update role count (note: this shows last page count, not total)
330
+ num_of_roles_in_account += len(response["Roles"])
331
+
332
+ # Display real-time progress with role count per account
333
+ print(f"Found {num_of_roles_in_account} roles in account {account['AccountNumber']}", end="\r")
334
+
335
+ except ClientError as my_Error:
336
+ # Handle AWS IAM API errors with appropriate logging
337
+ if "AuthFailure" in str(my_Error):
338
+ print(f"\nAuthorization Failure for account {account['AccountId']}")
339
+ # Common causes: insufficient IAM permissions, account access issues
340
+ else:
341
+ print(f"\nError: {my_Error}")
342
+ # Other potential errors: throttling, service unavailability, etc.
343
+
344
+ # Apply role name filtering based on provided fragments
345
+ if pFragments is None:
346
+ # No filtering requested - return all discovered roles
347
+ found_roles = Roles
348
+ else:
349
+ # Filter roles using fragment matching (exact or partial based on pExact flag)
350
+ # find_in() handles both exact string matching and substring matching
351
+ found_roles = [x for x in Roles if find_in([x["RoleName"]], pFragments, pExact)]
352
+
353
+ return found_roles
354
+
355
+
356
+ def delete_roles(roles_to_delete):
357
+ """
358
+ Execute batch IAM role deletion with comprehensive safety controls.
359
+
360
+ CRITICAL SECURITY FUNCTION: This orchestrates IRREVERSIBLE deletion of IAM roles
361
+ across multiple AWS accounts. Implements multiple safety layers including
362
+ confirmation prompts, force flags, and detailed audit logging.
363
+
364
+ Args:
365
+ roles_to_delete (list): List of role dictionaries to delete, each containing:
366
+ - RoleName: IAM role name for deletion
367
+ - AccountId: Target AWS account ID
368
+ - Credential information for authenticated API calls
369
+
370
+ Safety Control Flow:
371
+ 1. Fragment Validation: Refuses deletion if no role fragments specified
372
+ 2. Interactive Confirmation: Prompts user for explicit confirmation
373
+ 3. Force Mode: Automated confirmation with safety delay
374
+ 4. Individual Deletion: Calls my_delete_role() for each role
375
+ 5. Result Tracking: Updates role records with deletion status
376
+
377
+ Security Features:
378
+ - Prevents accidental bulk deletion without fragment specification
379
+ - Interactive confirmation shows exact count and scope
380
+ - Force mode includes mandatory delay allowing Ctrl-C abort
381
+ - Per-role deletion status tracking for audit purposes
382
+ - Comprehensive logging of all deletion attempts
383
+
384
+ Deletion Modes:
385
+ - Interactive Mode: User must confirm each batch deletion
386
+ - Force Mode: Automated with {time_to_sleep} second safety delay
387
+ - Dry Run: No deletion if pDelete flag not set
388
+
389
+ Side Effects:
390
+ - Modifies roles_to_delete list by adding 'Action' field
391
+ - Logs all deletion attempts at INFO level
392
+ - Displays progress and confirmation prompts to stdout
393
+
394
+ Error Handling:
395
+ - Individual role deletion failures are logged but don't stop batch
396
+ - Failed deletions marked with "delete failed" status
397
+ - Successful deletions marked with "deleted" status
398
+
399
+ Enterprise Considerations:
400
+ - Supports bulk operations across hundreds of roles
401
+ - Maintains audit trail for compliance requirements
402
+ - Provides abort mechanisms for operational safety
403
+ """
404
+ confirm = False
405
+
406
+ if pDelete:
407
+ # Safety Check #1: Prevent bulk deletion without specific targeting
408
+ if pFragments is None:
409
+ print(
410
+ f"You asked to delete roles, but didn't give a specific role to delete, so we're not going to delete anything."
411
+ )
412
+
413
+ # Safety Check #2: Interactive confirmation for targeted deletions
414
+ elif len(roles_to_delete) > 0 and not pForce:
415
+ print(
416
+ f"Your specified role fragment matched at least 1 role.\n"
417
+ f"Please confirm you want to really delete all {len(roles_to_delete)} roles found"
418
+ )
419
+ confirm = (
420
+ input(
421
+ f"Really delete {len(roles_to_delete)} across {len(AllCredentials)} accounts. Are you still sure? (y/n): "
422
+ ).lower()
423
+ == "y"
424
+ )
425
+
426
+ # Safety Check #3: Force mode with mandatory safety delay
427
+ elif pForce and len(roles_to_delete) > 0:
428
+ print(
429
+ f"You specified a fragment that matched multiple roles.\n"
430
+ f"And you specified the 'FORCE' parameter - so we're not asking again, BUT we'll wait {time_to_sleep} seconds to give you the option to Ctrl-C here..."
431
+ )
432
+ # Mandatory delay provides abort opportunity even in automation
433
+ sleep(time_to_sleep)
434
+
435
+ # Execute batch deletion if safety controls are satisfied
436
+ if (pDelete and confirm) or (pDelete and pForce):
437
+ for i in range(len(roles_to_delete)):
438
+ # Log each deletion attempt for audit purposes
439
+ logging.info(
440
+ f"Deleting role {roles_to_delete[i]['RoleName']} from account {roles_to_delete[i]['AccountId']}"
441
+ )
442
+
443
+ # Attempt individual role deletion
444
+ result = my_delete_role(roles_to_delete[i])
445
+
446
+ # Update role record with deletion status for reporting
447
+ if result:
448
+ roles_to_delete[i].update({"Action": "deleted"})
449
+ else:
450
+ roles_to_delete[i].update({"Action": "delete failed"})
451
+
452
+
453
+ ###########################
454
+
455
+ if __name__ == "__main__":
456
+ args = parse_args(sys.argv[1:])
457
+ pProfiles = args.Profiles
458
+ pRegionList = args.Regions
459
+ # pRole = args.pRole
460
+ pFragments = args.Fragments
461
+ pAccounts = args.Accounts
462
+ pSkipAccounts = args.SkipAccounts
463
+ pSkipProfiles = args.SkipProfiles
464
+ pDelete = args.pDelete
465
+ pForce = args.Force
466
+ pExact = args.Exact
467
+ pRootOnly = args.RootOnly
468
+ pFilename = args.Filename
469
+ pTiming = args.Time
470
+ verbose = args.loglevel
471
+ # Setup logging levels
472
+ logging.basicConfig(level=verbose, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
473
+ logging.getLogger("boto3").setLevel(logging.CRITICAL)
474
+ logging.getLogger("botocore").setLevel(logging.CRITICAL)
475
+ logging.getLogger("s3transfer").setLevel(logging.CRITICAL)
476
+ logging.getLogger("urllib3").setLevel(logging.CRITICAL)
477
+
478
+ ERASE_LINE = "\x1b[2K"
479
+ time_to_sleep = 5
480
+ begin_time = time()
481
+
482
+ print()
483
+
484
+ # Get credentials for all Child Accounts
485
+ AllCredentials = get_all_credentials(
486
+ pProfiles, pTiming, pSkipProfiles, pSkipAccounts, pRootOnly, pAccounts, pRegionList
487
+ )
488
+ # Collect the stacksets, AccountList and RegionList involved
489
+ all_found_roles = find_and_collect_roles_across_accounts(AllCredentials, pFragments)
490
+ # Display the information we've found this far
491
+ sorted_Results = sorted(all_found_roles, key=lambda d: (d["MgmtAcct"], d["AccountId"], d["RoleName"]))
492
+ display_dict = {
493
+ "AccountId": {"DisplayOrder": 2, "Heading": "Account Number"},
494
+ "MgmtAcct": {"DisplayOrder": 1, "Heading": "Parent Acct"},
495
+ "RoleName": {"DisplayOrder": 3, "Heading": "Role Name"},
496
+ "Action": {"DisplayOrder": 4, "Heading": "Action Taken"},
497
+ }
498
+
499
+ display_results(sorted_Results, display_dict, "No Action", pFilename)
500
+
501
+ # Modify stacks, if requested
502
+ if pDelete:
503
+ delete_roles(sorted_Results)
504
+
505
+ print()
506
+ if pFragments is None:
507
+ print(f"Found {len(sorted_Results)} roles across {len(AllCredentials)} accounts")
508
+ else:
509
+ print(
510
+ f"Found {len(sorted_Results)} instances where role containing {pFragments} was found across {len(AllCredentials)} accounts"
511
+ )
512
+
513
+ if pTiming:
514
+ print(ERASE_LINE)
515
+ print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
516
+ print()
517
+ print("Thanks for using this script...")
518
+ print()