runbooks 0.7.0__py3-none-any.whl → 0.7.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. runbooks/__init__.py +87 -37
  2. runbooks/cfat/README.md +300 -49
  3. runbooks/cfat/__init__.py +2 -2
  4. runbooks/finops/__init__.py +1 -1
  5. runbooks/finops/cli.py +1 -1
  6. runbooks/inventory/collectors/__init__.py +8 -0
  7. runbooks/inventory/collectors/aws_management.py +791 -0
  8. runbooks/inventory/collectors/aws_networking.py +3 -3
  9. runbooks/main.py +3389 -782
  10. runbooks/operate/__init__.py +207 -0
  11. runbooks/operate/base.py +311 -0
  12. runbooks/operate/cloudformation_operations.py +619 -0
  13. runbooks/operate/cloudwatch_operations.py +496 -0
  14. runbooks/operate/dynamodb_operations.py +812 -0
  15. runbooks/operate/ec2_operations.py +926 -0
  16. runbooks/operate/iam_operations.py +569 -0
  17. runbooks/operate/s3_operations.py +1211 -0
  18. runbooks/operate/tagging_operations.py +655 -0
  19. runbooks/remediation/CLAUDE.md +100 -0
  20. runbooks/remediation/DOME9.md +218 -0
  21. runbooks/remediation/README.md +26 -0
  22. runbooks/remediation/Tests/__init__.py +0 -0
  23. runbooks/remediation/Tests/update_policy.py +74 -0
  24. runbooks/remediation/__init__.py +95 -0
  25. runbooks/remediation/acm_cert_expired_unused.py +98 -0
  26. runbooks/remediation/acm_remediation.py +875 -0
  27. runbooks/remediation/api_gateway_list.py +167 -0
  28. runbooks/remediation/base.py +643 -0
  29. runbooks/remediation/cloudtrail_remediation.py +908 -0
  30. runbooks/remediation/cloudtrail_s3_modifications.py +296 -0
  31. runbooks/remediation/cognito_active_users.py +78 -0
  32. runbooks/remediation/cognito_remediation.py +856 -0
  33. runbooks/remediation/cognito_user_password_reset.py +163 -0
  34. runbooks/remediation/commons.py +455 -0
  35. runbooks/remediation/dynamodb_optimize.py +155 -0
  36. runbooks/remediation/dynamodb_remediation.py +744 -0
  37. runbooks/remediation/dynamodb_server_side_encryption.py +108 -0
  38. runbooks/remediation/ec2_public_ips.py +134 -0
  39. runbooks/remediation/ec2_remediation.py +892 -0
  40. runbooks/remediation/ec2_subnet_disable_auto_ip_assignment.py +72 -0
  41. runbooks/remediation/ec2_unattached_ebs_volumes.py +448 -0
  42. runbooks/remediation/ec2_unused_security_groups.py +202 -0
  43. runbooks/remediation/kms_enable_key_rotation.py +651 -0
  44. runbooks/remediation/kms_remediation.py +717 -0
  45. runbooks/remediation/lambda_list.py +243 -0
  46. runbooks/remediation/lambda_remediation.py +971 -0
  47. runbooks/remediation/multi_account.py +569 -0
  48. runbooks/remediation/rds_instance_list.py +199 -0
  49. runbooks/remediation/rds_remediation.py +873 -0
  50. runbooks/remediation/rds_snapshot_list.py +192 -0
  51. runbooks/remediation/requirements.txt +118 -0
  52. runbooks/remediation/s3_block_public_access.py +159 -0
  53. runbooks/remediation/s3_bucket_public_access.py +143 -0
  54. runbooks/remediation/s3_disable_static_website_hosting.py +74 -0
  55. runbooks/remediation/s3_downloader.py +215 -0
  56. runbooks/remediation/s3_enable_access_logging.py +562 -0
  57. runbooks/remediation/s3_encryption.py +526 -0
  58. runbooks/remediation/s3_force_ssl_secure_policy.py +143 -0
  59. runbooks/remediation/s3_list.py +141 -0
  60. runbooks/remediation/s3_object_search.py +201 -0
  61. runbooks/remediation/s3_remediation.py +816 -0
  62. runbooks/remediation/scan_for_phrase.py +425 -0
  63. runbooks/remediation/workspaces_list.py +220 -0
  64. runbooks/security/__init__.py +9 -10
  65. runbooks/security/security_baseline_tester.py +4 -2
  66. runbooks-0.7.6.dist-info/METADATA +608 -0
  67. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/RECORD +84 -76
  68. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/entry_points.txt +0 -1
  69. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/top_level.txt +0 -1
  70. jupyter-agent/.env +0 -2
  71. jupyter-agent/.env.template +0 -2
  72. jupyter-agent/.gitattributes +0 -35
  73. jupyter-agent/.gradio/certificate.pem +0 -31
  74. jupyter-agent/README.md +0 -16
  75. jupyter-agent/__main__.log +0 -8
  76. jupyter-agent/app.py +0 -256
  77. jupyter-agent/cloudops-agent.png +0 -0
  78. jupyter-agent/ds-system-prompt.txt +0 -154
  79. jupyter-agent/jupyter-agent.png +0 -0
  80. jupyter-agent/llama3_template.jinja +0 -123
  81. jupyter-agent/requirements.txt +0 -9
  82. jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +0 -68
  83. jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +0 -91
  84. jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +0 -91
  85. jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +0 -57
  86. jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +0 -53
  87. jupyter-agent/tmp/jupyter-agent.ipynb +0 -27
  88. jupyter-agent/utils.py +0 -409
  89. runbooks/aws/__init__.py +0 -58
  90. runbooks/aws/dynamodb_operations.py +0 -231
  91. runbooks/aws/ec2_copy_image_cross-region.py +0 -195
  92. runbooks/aws/ec2_describe_instances.py +0 -202
  93. runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
  94. runbooks/aws/ec2_run_instances.py +0 -213
  95. runbooks/aws/ec2_start_stop_instances.py +0 -212
  96. runbooks/aws/ec2_terminate_instances.py +0 -143
  97. runbooks/aws/ec2_unused_eips.py +0 -196
  98. runbooks/aws/ec2_unused_volumes.py +0 -188
  99. runbooks/aws/s3_create_bucket.py +0 -142
  100. runbooks/aws/s3_list_buckets.py +0 -152
  101. runbooks/aws/s3_list_objects.py +0 -156
  102. runbooks/aws/s3_object_operations.py +0 -183
  103. runbooks/aws/tagging_lambda_handler.py +0 -183
  104. runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
  105. runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
  106. runbooks/inventory/aws_organization.png +0 -0
  107. runbooks/inventory/cfn_move_stack_instances.py +0 -1526
  108. runbooks/inventory/delete_s3_buckets_objects.py +0 -169
  109. runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
  110. runbooks/inventory/update_aws_actions.py +0 -173
  111. runbooks/inventory/update_cfn_stacksets.py +0 -1215
  112. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
  113. runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
  114. runbooks/inventory/update_s3_public_access_block.py +0 -539
  115. runbooks/organizations/__init__.py +0 -12
  116. runbooks/organizations/manager.py +0 -374
  117. runbooks-0.7.0.dist-info/METADATA +0 -375
  118. /runbooks/inventory/{tests → Tests}/common_test_data.py +0 -0
  119. /runbooks/inventory/{tests → Tests}/common_test_functions.py +0 -0
  120. /runbooks/inventory/{tests → Tests}/script_test_data.py +0 -0
  121. /runbooks/inventory/{tests → Tests}/setup.py +0 -0
  122. /runbooks/inventory/{tests → Tests}/src.py +0 -0
  123. /runbooks/inventory/{tests/test_inventory_modules.py → Tests/test_Inventory_Modules.py} +0 -0
  124. /runbooks/inventory/{tests → Tests}/test_cfn_describe_stacks.py +0 -0
  125. /runbooks/inventory/{tests → Tests}/test_ec2_describe_instances.py +0 -0
  126. /runbooks/inventory/{tests → Tests}/test_lambda_list_functions.py +0 -0
  127. /runbooks/inventory/{tests → Tests}/test_moto_integration_example.py +0 -0
  128. /runbooks/inventory/{tests → Tests}/test_org_list_accounts.py +0 -0
  129. /runbooks/inventory/{Inventory_Modules.py → inventory_modules.py} +0 -0
  130. /runbooks/{aws → operate}/tags.json +0 -0
  131. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/WHEEL +0 -0
  132. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/licenses/LICENSE +0 -0
@@ -1,478 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- import logging
5
- from time import time
6
-
7
- import Inventory_Modules
8
- from account_class import aws_acct_access
9
- from ArgumentsClass import CommonArguments
10
- from botocore.exceptions import ClientError
11
- from colorama import Fore, init
12
- from Inventory_Modules import get_credentials_for_accounts_in_org
13
-
14
- init()
15
- __version__ = "2023.05.10"
16
-
17
- parser = CommonArguments()
18
- parser.multiprofile()
19
- parser.multiregion()
20
- parser.roletouse()
21
- parser.extendedargs()
22
- parser.rootOnly()
23
- parser.timing()
24
- parser.verbosity()
25
- parser.version(__version__)
26
-
27
- group = parser.my_parser.add_mutually_exclusive_group(required=True)
28
- group.add_argument(
29
- "+r",
30
- "--RoleToAdd",
31
- dest="pRoleNameToAdd",
32
- metavar="role to create",
33
- default=None,
34
- help="Rolename to be added to a number of accounts",
35
- )
36
- group.add_argument(
37
- "-c",
38
- "--rolecheck",
39
- dest="pRoleNameToCheck",
40
- metavar="role to check to see if it exists",
41
- default=None,
42
- help="Rolename to be checked for existence",
43
- )
44
- group.add_argument(
45
- "--RoleToRemove",
46
- dest="pRoleNameToRemove",
47
- metavar="role to remove",
48
- default=None,
49
- help="Rolename to be removed from a number of accounts",
50
- )
51
- args = parser.my_parser.parse_args()
52
-
53
- pProfiles = args.Profiles
54
- pTiming = args.Time
55
- pSkipAccounts = args.SkipAccounts
56
- pSkipProfiles = args.SkipProfiles
57
- pRootOnly = args.RootOnly
58
- pAccounts = args.Accounts
59
- pRoleToUse = args.AccessRole
60
- pRoleNameToAdd = args.pRoleNameToAdd
61
- pRoleNameToRemove = args.pRoleNameToRemove
62
- pRoleNameToCheck = args.pRoleNameToCheck
63
- verbose = args.loglevel
64
- logging.basicConfig(level=args.loglevel, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
65
-
66
-
67
- ##########################
68
-
69
-
70
- def createrole(ocredentials, frole):
71
- import boto3
72
- import simplejson as json
73
-
74
- """
75
- ocredentials is an object with the following structure:
76
- - ['AccessKeyId'] holds the AWS_ACCESS_KEY
77
- - ['SecretAccessKey'] holds the AWS_SECRET_ACCESS_KEY
78
- - ['SessionToken'] holds the AWS_SESSION_TOKEN
79
- - ['AccountId'] holds the account number you're connecting to
80
- """
81
- Trust_Policy = {
82
- "Version": "2012-10-17",
83
- "Statement": [
84
- {
85
- "Effect": "Allow",
86
- "Principal": {"AWS": [f"arn:aws:iam::{ocredentials['MgmtAccount']}:root"]},
87
- "Action": "sts:AssumeRole",
88
- }
89
- ],
90
- }
91
-
92
- AdminPolicy = "arn:aws:iam::aws:policy/AdministratorAccess"
93
-
94
- Trust_Policy_json = json.dumps(Trust_Policy)
95
-
96
- session_iam = boto3.Session(
97
- aws_access_key_id=ocredentials["AccessKeyId"],
98
- aws_secret_access_key=ocredentials["SecretAccessKey"],
99
- aws_session_token=ocredentials["SessionToken"],
100
- region_name=ocredentials["Region"],
101
- )
102
-
103
- client_iam = session_iam.client("iam")
104
- try:
105
- response = client_iam.create_role(RoleName=frole, AssumeRolePolicyDocument=Trust_Policy_json)
106
- logging.info("Successfully created the blank role %s in account %s", frole, ocredentials["AccountId"])
107
- except client_iam.exceptions.LimitExceededException as my_Error:
108
- ErrorMessage = f"Limit Exceeded: {my_Error}"
109
- logging.error(ErrorMessage)
110
- return_response = {"Success": False, "ErrorMessage": ErrorMessage}
111
- except client_iam.exceptions.InvalidInputException as my_Error:
112
- ErrorMessage = f"Invalid Input: {my_Error}"
113
- logging.error(ErrorMessage)
114
- return_response = {"Success": False, "ErrorMessage": ErrorMessage}
115
- except client_iam.exceptions.EntityAlreadyExistsException as my_Error:
116
- ErrorMessage = f"Role already exists: {my_Error}"
117
- logging.error(ErrorMessage)
118
- return_response = {"Success": False, "ErrorMessage": ErrorMessage}
119
- except client_iam.exceptions.MalformedPolicyDocumentException as my_Error:
120
- ErrorMessage = f"Malformed role policy: {my_Error}"
121
- logging.error(ErrorMessage)
122
- return_response = {"Success": False, "ErrorMessage": ErrorMessage}
123
- except client_iam.exceptions.ConcurrentModificationException as my_Error:
124
- ErrorMessage = f"Concurrent operations: {my_Error}"
125
- logging.error(ErrorMessage)
126
- return_response = {"Success": False, "ErrorMessage": ErrorMessage}
127
- except client_iam.exceptions.ServiceFailureException as my_Error:
128
- ErrorMessage = f"Service Failure: {my_Error}"
129
- logging.error(ErrorMessage)
130
- return_response = {"Success": False, "ErrorMessage": ErrorMessage}
131
-
132
- try:
133
- response1 = client_iam.attach_role_policy(RoleName=frole, PolicyArn=AdminPolicy)
134
- print(
135
- f"{ERASE_LINE}We've successfully added the role{Fore.GREEN} {frole} {Fore.RESET}to account"
136
- f"{Fore.GREEN} {ocredentials['AccountId']} {Fore.RESET}with admin rights, "
137
- f"trusting the Management Account {Fore.GREEN}{ocredentials['MgmtAccount']}{Fore.RESET} "
138
- f"in profile {Fore.GREEN}{ocredentials['ParentProfile']}{Fore.RESET}."
139
- )
140
- except client_iam.exceptions.NoSuchEntityException as my_Error:
141
- ErrorMessage = f"No such policy: {my_Error}"
142
- logging.error(ErrorMessage)
143
- return_response = {"Success": False, "ErrorMessage": ErrorMessage}
144
- except client_iam.exceptions.LimitExceededException as my_Error:
145
- ErrorMessage = f"No such policy: {my_Error}"
146
- logging.error(ErrorMessage)
147
- return_response = {"Success": False, "ErrorMessage": ErrorMessage}
148
- except client_iam.exceptions.InvalidInputException as my_Error:
149
- ErrorMessage = f"No such policy: {my_Error}"
150
- logging.error(ErrorMessage)
151
- return_response = {"Success": False, "ErrorMessage": ErrorMessage}
152
- except client_iam.exceptions.UnmodifiableEntityException as my_Error:
153
- ErrorMessage = f"No such policy: {my_Error}"
154
- logging.error(ErrorMessage)
155
- return_response = {"Success": False, "ErrorMessage": ErrorMessage}
156
- except client_iam.exceptions.PolicyNotAttachableException as my_Error:
157
- ErrorMessage = f"No such policy: {my_Error}"
158
- logging.error(ErrorMessage)
159
- return_response = {"Success": False, "ErrorMessage": ErrorMessage}
160
- except client_iam.exceptions.ServiceFailureException as my_Error:
161
- ErrorMessage = f"No such policy: {my_Error}"
162
- logging.error(ErrorMessage)
163
- return_response = {"Success": False, "ErrorMessage": ErrorMessage}
164
- except ClientError as my_Error:
165
- if my_Error.response["Error"]["Code"] == "EntityAlreadyExists":
166
- print(f"Role {frole} already exists in account {ocredentials['AccountId']}. Skipping.")
167
- print(my_Error)
168
- pass
169
-
170
-
171
- def removerole(ocredentials, frole):
172
- import boto3
173
-
174
- """
175
- ocredentials is an object with the following structure:
176
- - ['AccessKeyId'] holds the AWS_ACCESS_KEY
177
- - ['SecretAccessKey'] holds the AWS_SECRET_ACCESS_KEY
178
- - ['SessionToken'] holds the AWS_SESSION_TOKEN
179
- - ['AccountId'] holds the account number you're connecting to
180
- """
181
- return_response = {"Success": False, "ErrorMessage": ""}
182
- session_iam = boto3.Session(
183
- aws_access_key_id=ocredentials["AccessKeyId"],
184
- aws_secret_access_key=ocredentials["SecretAccessKey"],
185
- aws_session_token=ocredentials["SessionToken"],
186
- )
187
-
188
- client_iam = session_iam.client("iam")
189
- AdminPolicy = "arn:aws:iam::aws:policy/AdministratorAccess"
190
-
191
- try:
192
- # We need to list the policies attached (whether inline or managed)
193
- # TODO: Both of these calls below should allow for pagination
194
- attached_managed_policies = client_iam.list_attached_role_policies(RoleName=frole)
195
- """
196
- {
197
- 'AttachedPolicies': [
198
- {
199
- 'PolicyName': 'string',
200
- 'PolicyArn': 'string'
201
- },
202
- ],
203
- 'IsTruncated': True|False,
204
- 'Marker': 'string'
205
- }
206
- """
207
-
208
- attached_inline_policies = client_iam.list_role_policies(RoleName=frole)
209
- """
210
- {
211
- 'PolicyNames': [
212
- 'string',
213
- ],
214
- 'IsTruncated': True|False,
215
- 'Marker': 'string'
216
- }
217
- """
218
-
219
- # Then we need to detach/ delete the policy we find
220
- for managed_policy in attached_managed_policies["AttachedPolicies"]:
221
- try:
222
- response1 = client_iam.detach_role_policy(RoleName=frole, PolicyArn=managed_policy["PolicyArn"])
223
- logging.info(
224
- f"Successfully removed the managed policy {managed_policy['PolicyName']} from role {frole}"
225
- )
226
- return_response["Success"] = True
227
- except (
228
- client_iam.exceptions.NoSuchEntityException,
229
- client_iam.exceptions.InvalidInputException,
230
- client_iam.exceptions.ServiceFailureException,
231
- ) as my_Error:
232
- logging.error(f"Error Message: {my_Error}")
233
- return_response["ErrorMessage"] = str(my_Error)
234
- return_response["Success"] = False
235
- if return_response["Success"]:
236
- continue
237
- else:
238
- return return_response
239
-
240
- for inline_policy in attached_inline_policies["PolicyNames"]:
241
- try:
242
- inline_role_deletion = client_iam.delete_role_policy(RoleName=frole, PolicyName=inline_policy)
243
- logging.info(f"Successfully removed the inline policy {inline_policy} from role {frole}")
244
- return_response["Success"] = True
245
- except (
246
- client_iam.exceptions.NoSuchEntityException,
247
- client_iam.exceptions.LimitExceededException,
248
- client_iam.exceptions.UnmodifiableEntityException,
249
- client_iam.exceptions.ServiceFailureException,
250
- ) as my_Error:
251
- logging.error(f"Error Message: {my_Error}")
252
- return_response["ErrorMessage"] = str(my_Error)
253
- return_response["Success"] = False
254
- if return_response["Success"]:
255
- continue
256
- else:
257
- return return_response
258
-
259
- # Only then we can we delete the role
260
- try:
261
- response = client_iam.delete_role(RoleName=frole)
262
- logging.info(f"Successfully removed the role {frole}")
263
- return_response["Success"] = True
264
- except (
265
- client_iam.exceptions.NoSuchEntityException,
266
- client_iam.exceptions.DeleteConflictException,
267
- client_iam.exceptions.LimitExceededException,
268
- client_iam.exceptions.UnmodifiableEntityException,
269
- client_iam.exceptions.ConcurrentModificationException,
270
- client_iam.exceptions.ServiceFailureException,
271
- ) as my_Error:
272
- logging.error(f"Error Message: {my_Error}")
273
- return_response["ErrorMessage"] = str(my_Error)
274
- return_response["Success"] = False
275
- if return_response["Success"]:
276
- pass
277
- else:
278
- return return_response
279
-
280
- print(
281
- f"{ERASE_LINE}We've successfully removed the role{Fore.GREEN} {frole} {Fore.RESET}"
282
- f"from account{Fore.GREEN} {ocredentials['AccountId']} {Fore.RESET}"
283
- )
284
- except ClientError as my_Error:
285
- print(my_Error)
286
- pass
287
-
288
-
289
- def roleexists(ocredentials, frole):
290
- import boto3
291
-
292
- session_iam = boto3.Session(
293
- aws_access_key_id=ocredentials["AccessKeyId"],
294
- aws_secret_access_key=ocredentials["SecretAccessKey"],
295
- aws_session_token=ocredentials["SessionToken"],
296
- )
297
-
298
- client_iam = session_iam.client("iam")
299
- try:
300
- logging.info(f"{ERASE_LINE}Checking Account {ocredentials['AccountId']} for Role {frole}")
301
- response = client_iam.get_role(RoleName=frole)
302
- return True
303
- except ClientError as my_Error:
304
- if (my_Error.response["Error"]["Code"]) == "NoSuchEntity":
305
- logging.warning("Role %s doesn't exist in account %s", frole, ocredentials["AccountId"])
306
- return False
307
-
308
-
309
- def get_credentials(fProfileList, fSkipAccounts, fRootOnly, fAccounts, fRegionList, fRolesToUse):
310
- """
311
- fProfiles: This is a list (0..n) of profiles to be searched through
312
- fSkipAccounts: This is a list (0..n) of accounts that shouldn't be checked or impacted
313
- fRootOnly: This is a flag (True/ False) as to whether this script should impact this account only, or the Child accounts as well
314
- fAccounts: This is a list (0..n) of Account Numbers which this script should be limited to
315
- fRegionList: This is a list (1..n) of regions which this script should be run against.
316
- fRolesToUse: This is a list (0..n) of roles to try to access the child accounts, assuming the role used isn't a commonly used one.
317
- Commonly Used roles: [OrganizationAccountAccessRole, AWSCloudFormationStackSetExecutionRole, AWSControlTowerExecution, Owner]
318
-
319
- Returned Values:
320
- AccountList: A list of dictionaries, containing information about the accounts themselves - created by the "ChildAccounts" function of aws_acct_access from the account_class
321
- AllCredentials: A list of dictionaries of all the info and credentials for all child accounts, including those that we weren't able to get credentials for.
322
- """
323
- AccountList = []
324
- AllCredentials = []
325
-
326
- if pProfiles is None: # Default use case from the classes
327
- print("Using the default profile - gathering info")
328
- aws_acct = aws_acct_access()
329
- RegionList = Inventory_Modules.get_regions3(aws_acct, fRegionList)
330
- # This should populate the list "AllCreds" with the credentials for the relevant accounts.
331
- logging.info(f"Queueing default profile for credentials")
332
- profile = "default"
333
- AllCredentials.extend(
334
- get_credentials_for_accounts_in_org(
335
- aws_acct, fSkipAccounts, fRootOnly, fAccounts, profile, RegionList, fRolesToUse
336
- )
337
- )
338
- # TODO: There's a use-case here where oen of more of the accounts in 'fAccounts' doesn't show up in the list of accounts found by the profiles specified
339
- # In that case - it would be nice if this script pointed that out. Right now - it does not, yet.
340
- AccountList = aws_acct.ChildAccounts
341
- else:
342
- print(f"Capturing info for {len(ProfileList)} requested profiles {ProfileList}")
343
- for profile in fProfileList:
344
- # Eventually - getting credentials for a single account may require passing in the region in which it's valid, but not yet.
345
- try:
346
- aws_acct = aws_acct_access(profile)
347
- print(f"Validating {len(aws_acct.ChildAccounts)} accounts within {profile} profile now... ")
348
- RegionList = Inventory_Modules.get_regions3(aws_acct, fRegionList)
349
- logging.info(f"Queueing {profile} for credentials")
350
- # This should populate the list "AllCredentials" with the credentials for the relevant accounts.
351
- AllCredentials.extend(
352
- get_credentials_for_accounts_in_org(
353
- aws_acct, fSkipAccounts, fRootOnly, fAccounts, profile, RegionList, fRolesToUse
354
- )
355
- )
356
- AccountList.extend(aws_acct.ChildAccounts)
357
- except AttributeError as my_Error:
358
- logging.error(f"Profile {profile} didn't work... Skipping")
359
- continue
360
- return AllCredentials, AccountList
361
-
362
-
363
- ##########################
364
-
365
- ERASE_LINE = "\x1b[2K"
366
-
367
- if pTiming:
368
- begin_time = time()
369
-
370
- AllCredentials = []
371
- AccountList = []
372
- RegionList = ["us-east-1"]
373
- Results = []
374
-
375
- ProfileList = Inventory_Modules.get_profiles(fSkipProfiles=pSkipProfiles, fprofiles=pProfiles)
376
-
377
- AllCredentials, AccountList = get_credentials(ProfileList, pSkipAccounts, pRootOnly, pAccounts, RegionList, pRoleToUse)
378
- AccountNum = len(set([acct["AccountId"] for acct in AllCredentials if "AccountId" in acct]))
379
-
380
- print()
381
- UpdatedAccounts = 0
382
-
383
- # If the user specified only one account to check, and that account isn't found within the credentials found, this line will alert them of that fact.
384
- # However, if they specified multiple accounts to check, and SOME of them appeared, then they won't be notified of the ones that did NOT appear
385
- if not AllCredentials:
386
- # print(f"{Fore.RED}The account{'' if len(pAccounts) == 1 else 's'} you requested to check {pAccounts} doesn't appear to be within the profiles you specified.{Fore.RESET}")
387
- print(
388
- f"{Fore.RED}The account you requested to check '{pAccounts}' doesn't appear to be within the profiles you specified.{Fore.RESET}"
389
- )
390
-
391
- for cred in AllCredentials:
392
- # account_credentials = Inventory_Modules.get_child_access3(aws_acct, cred['AccountId'], fRoleList=pRolesToUse)
393
- if not cred["Success"]:
394
- print(
395
- f"Something failed in getting credentials for account {cred['AccountId']}\n"
396
- f"We tried this list of roles '{cred['RolesTried']}', but none worked\n"
397
- f"Error Message: {cred['ErrorMessage']}"
398
- )
399
- continue
400
- # print(f"Checking account {cred['AccountId']} using role {cred['Role']}", end='\r')
401
- if cred["Role"] == pRoleNameToRemove:
402
- print(
403
- f"{Fore.RED}We gained access to this account using the role you specified to remove.\n"
404
- f"Is this definitely what you want to do?{Fore.RESET}"
405
- )
406
- # TODO: We ask a question here, but don't wait for an answer...
407
- # Checking to see if the role already exists
408
- if pRoleNameToCheck is not None:
409
- logging.info(f"Checking to see if role {pRoleNameToCheck} exists in account {cred['AccountId']}")
410
- if roleexists(cred, pRoleNameToCheck):
411
- Results.append({"AccountId": cred["AccountId"], "Role": pRoleNameToCheck, "Result": "Role Exists"})
412
- UpdatedAccounts += 1
413
- else:
414
- Results.append({"AccountId": cred["AccountId"], "Role": pRoleNameToCheck, "Result": "Nonexistent Role"})
415
- # If we're supposed to add the role and it already exists
416
- elif pRoleNameToAdd is not None and roleexists(cred, pRoleNameToAdd):
417
- logging.warning(f"Role {pRoleNameToAdd} already exists")
418
- continue
419
- # If we're supposed to remove the role and the role exists AND it's not the role we used to access the cred
420
- elif pRoleNameToRemove is not None and roleexists(cred, pRoleNameToRemove) and not (cred["Role"] == pRoleNameToAdd):
421
- logging.warning(f"Removing role {pRoleNameToRemove} from account {cred['AccountId']}")
422
- removerole(cred, pRoleNameToRemove)
423
- Results.append({"AccountId": cred["AccountId"], "Role": pRoleNameToRemove, "Result": "Role Removed"})
424
- UpdatedAccounts += 1
425
- # If we're supposed to add the role
426
- elif pRoleNameToAdd is not None:
427
- createrole(cred, pRoleNameToAdd)
428
- Results.append({"AccountId": cred["AccountId"], "Role": pRoleNameToAdd, "Result": "Role Created"})
429
- UpdatedAccounts += 1
430
-
431
- sorted_Results = sorted(Results, key=lambda d: (d["AccountId"]))
432
- print()
433
- # if verbose < 50:
434
- # print(f"You supplied profiles including the following {len(AccountList)} accounts: {[item['AccountId'] for item in AccountList]}")
435
- print()
436
- if pAccounts is not None:
437
- print(f"You asked to check account{'' if len(pAccounts) == 1 else 's'} {pAccounts} under your supplied profiles")
438
- else:
439
- print(f"We found {AccountNum} accounts provided within the profiles you provided")
440
- if verbose < 50:
441
- print(f"Of these, we successfully found creds for {len(Results)} accounts using ", end="")
442
- if pRoleToUse:
443
- print(f"the roles '{pRoleToUse}' you supplied")
444
- else:
445
- print(f"the roles we commonly use for access")
446
-
447
- MissingAccounts = [item["AccountId"] for item in AllCredentials if not item["Success"]]
448
- if len(MissingAccounts) > 0:
449
- print()
450
- print(
451
- f"{Fore.RED}We were unsuccessful when checking the following {len(MissingAccounts)} accounts: {MissingAccounts}{Fore.RESET}"
452
- )
453
- logging.warning(f"List of failed accounts:")
454
- for item in AllCredentials:
455
- logging.error(f"\t\t{item['AccountId']}")
456
- logging.warning(f"\t\t\tRoles Tried: {item['RolesTried']}")
457
- logging.info(f"\t\t\t\tRegions: {item['Region']}")
458
-
459
- if pRoleNameToCheck is not None:
460
- print(f"We found {UpdatedAccounts} accounts that included the '{pRoleNameToCheck}' role")
461
- if verbose <= 40:
462
- for i in sorted_Results:
463
- print(f"\tLooking for role '{i['Role']}' in Account '{i['AccountId']}': {i['Result']}")
464
- # MissingAccounts = [item['AccountId'] for item in Results if not (item['Result'] == 'Role Exists')]
465
- # if len(MissingAccounts) > 0:
466
- # print(f"{Fore.RED}We didn't find {pRoleNameToCheck} in the following accounts: {MissingAccounts}{Fore.RESET}")
467
- elif pRoleNameToAdd is not None:
468
- print(f"We updated {UpdatedAccounts} accounts to add the {pRoleNameToAdd} role")
469
- elif pRoleNameToRemove is not None:
470
- print(f"We updated {UpdatedAccounts} accounts to remove the {pRoleNameToRemove} role")
471
-
472
- if pTiming:
473
- print(ERASE_LINE)
474
- print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
475
-
476
- print()
477
- print("Thanks for using the tool.")
478
- print()