runbooks 0.7.0__py3-none-any.whl → 0.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- runbooks/__init__.py +87 -37
- runbooks/cfat/README.md +300 -49
- runbooks/cfat/__init__.py +2 -2
- runbooks/finops/__init__.py +1 -1
- runbooks/finops/cli.py +1 -1
- runbooks/inventory/collectors/__init__.py +8 -0
- runbooks/inventory/collectors/aws_management.py +791 -0
- runbooks/inventory/collectors/aws_networking.py +3 -3
- runbooks/main.py +3389 -782
- runbooks/operate/__init__.py +207 -0
- runbooks/operate/base.py +311 -0
- runbooks/operate/cloudformation_operations.py +619 -0
- runbooks/operate/cloudwatch_operations.py +496 -0
- runbooks/operate/dynamodb_operations.py +812 -0
- runbooks/operate/ec2_operations.py +926 -0
- runbooks/operate/iam_operations.py +569 -0
- runbooks/operate/s3_operations.py +1211 -0
- runbooks/operate/tagging_operations.py +655 -0
- runbooks/remediation/CLAUDE.md +100 -0
- runbooks/remediation/DOME9.md +218 -0
- runbooks/remediation/README.md +26 -0
- runbooks/remediation/Tests/__init__.py +0 -0
- runbooks/remediation/Tests/update_policy.py +74 -0
- runbooks/remediation/__init__.py +95 -0
- runbooks/remediation/acm_cert_expired_unused.py +98 -0
- runbooks/remediation/acm_remediation.py +875 -0
- runbooks/remediation/api_gateway_list.py +167 -0
- runbooks/remediation/base.py +643 -0
- runbooks/remediation/cloudtrail_remediation.py +908 -0
- runbooks/remediation/cloudtrail_s3_modifications.py +296 -0
- runbooks/remediation/cognito_active_users.py +78 -0
- runbooks/remediation/cognito_remediation.py +856 -0
- runbooks/remediation/cognito_user_password_reset.py +163 -0
- runbooks/remediation/commons.py +455 -0
- runbooks/remediation/dynamodb_optimize.py +155 -0
- runbooks/remediation/dynamodb_remediation.py +744 -0
- runbooks/remediation/dynamodb_server_side_encryption.py +108 -0
- runbooks/remediation/ec2_public_ips.py +134 -0
- runbooks/remediation/ec2_remediation.py +892 -0
- runbooks/remediation/ec2_subnet_disable_auto_ip_assignment.py +72 -0
- runbooks/remediation/ec2_unattached_ebs_volumes.py +448 -0
- runbooks/remediation/ec2_unused_security_groups.py +202 -0
- runbooks/remediation/kms_enable_key_rotation.py +651 -0
- runbooks/remediation/kms_remediation.py +717 -0
- runbooks/remediation/lambda_list.py +243 -0
- runbooks/remediation/lambda_remediation.py +971 -0
- runbooks/remediation/multi_account.py +569 -0
- runbooks/remediation/rds_instance_list.py +199 -0
- runbooks/remediation/rds_remediation.py +873 -0
- runbooks/remediation/rds_snapshot_list.py +192 -0
- runbooks/remediation/requirements.txt +118 -0
- runbooks/remediation/s3_block_public_access.py +159 -0
- runbooks/remediation/s3_bucket_public_access.py +143 -0
- runbooks/remediation/s3_disable_static_website_hosting.py +74 -0
- runbooks/remediation/s3_downloader.py +215 -0
- runbooks/remediation/s3_enable_access_logging.py +562 -0
- runbooks/remediation/s3_encryption.py +526 -0
- runbooks/remediation/s3_force_ssl_secure_policy.py +143 -0
- runbooks/remediation/s3_list.py +141 -0
- runbooks/remediation/s3_object_search.py +201 -0
- runbooks/remediation/s3_remediation.py +816 -0
- runbooks/remediation/scan_for_phrase.py +425 -0
- runbooks/remediation/workspaces_list.py +220 -0
- runbooks/security/__init__.py +9 -10
- runbooks/security/security_baseline_tester.py +4 -2
- runbooks-0.7.6.dist-info/METADATA +608 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/RECORD +84 -76
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/entry_points.txt +0 -1
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/top_level.txt +0 -1
- jupyter-agent/.env +0 -2
- jupyter-agent/.env.template +0 -2
- jupyter-agent/.gitattributes +0 -35
- jupyter-agent/.gradio/certificate.pem +0 -31
- jupyter-agent/README.md +0 -16
- jupyter-agent/__main__.log +0 -8
- jupyter-agent/app.py +0 -256
- jupyter-agent/cloudops-agent.png +0 -0
- jupyter-agent/ds-system-prompt.txt +0 -154
- jupyter-agent/jupyter-agent.png +0 -0
- jupyter-agent/llama3_template.jinja +0 -123
- jupyter-agent/requirements.txt +0 -9
- jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +0 -68
- jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +0 -91
- jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +0 -91
- jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +0 -57
- jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +0 -53
- jupyter-agent/tmp/jupyter-agent.ipynb +0 -27
- jupyter-agent/utils.py +0 -409
- runbooks/aws/__init__.py +0 -58
- runbooks/aws/dynamodb_operations.py +0 -231
- runbooks/aws/ec2_copy_image_cross-region.py +0 -195
- runbooks/aws/ec2_describe_instances.py +0 -202
- runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
- runbooks/aws/ec2_run_instances.py +0 -213
- runbooks/aws/ec2_start_stop_instances.py +0 -212
- runbooks/aws/ec2_terminate_instances.py +0 -143
- runbooks/aws/ec2_unused_eips.py +0 -196
- runbooks/aws/ec2_unused_volumes.py +0 -188
- runbooks/aws/s3_create_bucket.py +0 -142
- runbooks/aws/s3_list_buckets.py +0 -152
- runbooks/aws/s3_list_objects.py +0 -156
- runbooks/aws/s3_object_operations.py +0 -183
- runbooks/aws/tagging_lambda_handler.py +0 -183
- runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
- runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
- runbooks/inventory/aws_organization.png +0 -0
- runbooks/inventory/cfn_move_stack_instances.py +0 -1526
- runbooks/inventory/delete_s3_buckets_objects.py +0 -169
- runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
- runbooks/inventory/update_aws_actions.py +0 -173
- runbooks/inventory/update_cfn_stacksets.py +0 -1215
- runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
- runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
- runbooks/inventory/update_s3_public_access_block.py +0 -539
- runbooks/organizations/__init__.py +0 -12
- runbooks/organizations/manager.py +0 -374
- runbooks-0.7.0.dist-info/METADATA +0 -375
- /runbooks/inventory/{tests → Tests}/common_test_data.py +0 -0
- /runbooks/inventory/{tests → Tests}/common_test_functions.py +0 -0
- /runbooks/inventory/{tests → Tests}/script_test_data.py +0 -0
- /runbooks/inventory/{tests → Tests}/setup.py +0 -0
- /runbooks/inventory/{tests → Tests}/src.py +0 -0
- /runbooks/inventory/{tests/test_inventory_modules.py → Tests/test_Inventory_Modules.py} +0 -0
- /runbooks/inventory/{tests → Tests}/test_cfn_describe_stacks.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_ec2_describe_instances.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_lambda_list_functions.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_moto_integration_example.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_org_list_accounts.py +0 -0
- /runbooks/inventory/{Inventory_Modules.py → inventory_modules.py} +0 -0
- /runbooks/{aws → operate}/tags.json +0 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/WHEEL +0 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/licenses/LICENSE +0 -0
@@ -1,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()
|