runbooks 1.1.5__py3-none-any.whl → 1.1.7__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 +1 -1
- runbooks/cli/commands/finops.py +29 -5
- runbooks/cli/commands/inventory.py +40 -87
- runbooks/common/accuracy_validator.py +6 -12
- runbooks/common/cli_decorators.py +61 -0
- runbooks/common/mcp_integration.py +38 -7
- runbooks/common/rich_utils.py +116 -2
- runbooks/inventory/CLAUDE.md +1 -1
- runbooks/inventory/aws_decorators.py +2 -3
- runbooks/inventory/check_cloudtrail_compliance.py +2 -4
- runbooks/inventory/check_controltower_readiness.py +152 -151
- runbooks/inventory/check_landingzone_readiness.py +85 -84
- runbooks/inventory/core/formatter.py +11 -0
- runbooks/inventory/draw_org_structure.py +8 -9
- runbooks/inventory/ec2_vpc_utils.py +2 -2
- runbooks/inventory/find_cfn_drift_detection.py +5 -7
- runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
- runbooks/inventory/find_cfn_stackset_drift.py +5 -6
- runbooks/inventory/find_ec2_security_groups.py +48 -42
- runbooks/inventory/find_landingzone_versions.py +4 -6
- runbooks/inventory/find_vpc_flow_logs.py +7 -9
- runbooks/inventory/inventory_modules.py +103 -91
- runbooks/inventory/list_cfn_stacks.py +9 -10
- runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
- runbooks/inventory/list_cfn_stackset_operations.py +79 -57
- runbooks/inventory/list_cfn_stacksets.py +8 -10
- runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
- runbooks/inventory/list_ds_directories.py +65 -53
- runbooks/inventory/list_ec2_availability_zones.py +2 -4
- runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
- runbooks/inventory/list_ec2_instances.py +23 -28
- runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
- runbooks/inventory/list_elbs_load_balancers.py +22 -20
- runbooks/inventory/list_enis_network_interfaces.py +26 -33
- runbooks/inventory/list_guardduty_detectors.py +2 -4
- runbooks/inventory/list_iam_policies.py +2 -4
- runbooks/inventory/list_iam_roles.py +5 -7
- runbooks/inventory/list_iam_saml_providers.py +4 -6
- runbooks/inventory/list_lambda_functions.py +38 -38
- runbooks/inventory/list_org_accounts.py +6 -8
- runbooks/inventory/list_org_accounts_users.py +55 -44
- runbooks/inventory/list_rds_db_instances.py +31 -33
- runbooks/inventory/list_route53_hosted_zones.py +3 -5
- runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
- runbooks/inventory/list_sns_topics.py +2 -4
- runbooks/inventory/list_ssm_parameters.py +4 -7
- runbooks/inventory/list_vpc_subnets.py +2 -4
- runbooks/inventory/list_vpcs.py +7 -10
- runbooks/inventory/mcp_inventory_validator.py +5 -3
- runbooks/inventory/organizations_discovery.py +8 -4
- runbooks/inventory/recover_cfn_stack_ids.py +7 -8
- runbooks/inventory/requirements.txt +0 -1
- runbooks/inventory/rich_inventory_display.py +2 -2
- runbooks/inventory/run_on_multi_accounts.py +3 -5
- runbooks/inventory/unified_validation_engine.py +3 -2
- runbooks/inventory/verify_ec2_security_groups.py +1 -1
- runbooks/inventory/vpc_analyzer.py +3 -2
- runbooks/inventory/vpc_dependency_analyzer.py +2 -2
- runbooks/validation/terraform_drift_detector.py +16 -5
- {runbooks-1.1.5.dist-info → runbooks-1.1.7.dist-info}/METADATA +3 -4
- {runbooks-1.1.5.dist-info → runbooks-1.1.7.dist-info}/RECORD +65 -65
- {runbooks-1.1.5.dist-info → runbooks-1.1.7.dist-info}/WHEEL +0 -0
- {runbooks-1.1.5.dist-info → runbooks-1.1.7.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.5.dist-info → runbooks-1.1.7.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.5.dist-info → runbooks-1.1.7.dist-info}/top_level.txt +0 -0
@@ -12,10 +12,9 @@ import Inventory_Modules
|
|
12
12
|
from account_class import aws_acct_access
|
13
13
|
from ArgumentsClass import CommonArguments
|
14
14
|
from botocore.exceptions import ClientError
|
15
|
-
from
|
16
|
-
from
|
15
|
+
from runbooks.common.rich_utils import console, print_success, print_error, print_info
|
16
|
+
from runbooks.common.rich_utils import console, create_table
|
17
17
|
|
18
|
-
init()
|
19
18
|
__version__ = "2024.05.18"
|
20
19
|
|
21
20
|
script_path, script_name = os.path.split(sys.argv[0])
|
@@ -86,32 +85,32 @@ def intersection(lst1, lst2):
|
|
86
85
|
def explain_script():
|
87
86
|
print("This script does the following... ")
|
88
87
|
print(
|
89
|
-
f"
|
88
|
+
f"[blue] 0. Checks to ensure you have the necessary cross-account role access to the child account."
|
90
89
|
)
|
91
|
-
print(f"
|
92
|
-
print(f"
|
93
|
-
print(f" to see if there's already a
|
90
|
+
print(f"[blue] 1. This check previously checked for default VPCs, but has since been removed.")
|
91
|
+
print(f"[blue] 2. Checks the child account in each of the regions")
|
92
|
+
print(f" to see if there's already a [red]Config Recorder and Delivery Channel enabled...")
|
94
93
|
print(
|
95
|
-
f"
|
94
|
+
f"[blue] 3. Checks that there isn't a duplicate [red]CloudTrail trail in the account."
|
96
95
|
)
|
97
96
|
print(
|
98
|
-
f"
|
97
|
+
f"[blue] 4. This check previously checked for the presence of GuardDuty within this account, but has since been removed."
|
99
98
|
)
|
100
99
|
print(
|
101
|
-
f"
|
100
|
+
f"[blue] 5. This child account [red]must exist within the Parent Organization."
|
102
101
|
)
|
103
102
|
print(" If it doesn't - then you must move it into this Org - this script can't do that for you.")
|
104
103
|
print(
|
105
|
-
f"
|
104
|
+
f"[blue] 6. The target account [red]can't exist within an already managed OU."
|
106
105
|
)
|
107
106
|
print(" If it does - then you're already managing this account with Control Tower and just don't know it.")
|
108
|
-
print(f"
|
107
|
+
print(f"[blue] 7. Looking for [red]SNS Topics with duplicate names.")
|
109
108
|
print(" If found, we can delete them, but you probably want to do that manually - to be sure.")
|
110
|
-
print(f"
|
109
|
+
print(f"[blue] 8. Looking for [red]Lambda Functions with duplicate names.")
|
111
110
|
print(" If found, we can delete them, but you probably want to do that manually - to be sure.")
|
112
|
-
print(f"
|
111
|
+
print(f"[blue] 9. Looking for [red]IAM Roles with duplicate names.")
|
113
112
|
print(" If found, we can delete them, but you probably want to do that manually - to be sure.")
|
114
|
-
print(f"
|
113
|
+
print(f"[blue] 10. Looking for duplicate [red]CloudWatch Log Groups.")
|
115
114
|
print(" If found, we can delete them, but you probably want to do that manually - to be sure.")
|
116
115
|
print()
|
117
116
|
print("Since this script is fairly new - All comments or suggestions are enthusiastically encouraged")
|
@@ -166,7 +165,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
166
165
|
]
|
167
166
|
# TODO: I don't use this next variable, but eventually I intend to supply the JSON code needed to update a role with.
|
168
167
|
json_formatted_str_TP = ""
|
169
|
-
print(f"{
|
168
|
+
print(f"[blue]{Step}:") if verbose < 50 else None
|
170
169
|
print(
|
171
170
|
f"Confirming we have the necessary cross-account access to account {fChildAccountId} in region {fRegion}"
|
172
171
|
) if verbose < 50 else None
|
@@ -206,7 +205,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
206
205
|
finally:
|
207
206
|
if account_credentials["AccessError"]:
|
208
207
|
print(
|
209
|
-
f"
|
208
|
+
f"[red]We weren't able to connect to the Child Account {fChildAccountId} from this Management Account {aws_account.acct_number}. Please check the role Trust Policy and re-run this script."
|
210
209
|
)
|
211
210
|
print(
|
212
211
|
f"The following list of roles were tried, but none were allowed access to account {fChildAccountId} using the {aws_account.acct_number} account in region {fRegion}"
|
@@ -219,10 +218,10 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
219
218
|
logging.info("Was able to successfully connect using the credentials... ")
|
220
219
|
print() if verbose < 50 else None
|
221
220
|
print(
|
222
|
-
f"Confirmed the role {
|
223
|
-
f" exists in account {
|
221
|
+
f"Confirmed the role [green]{account_credentials['Role']}"
|
222
|
+
f" exists in account [green]{fChildAccountId} and trusts the Management Account"
|
224
223
|
) if verbose < 50 else None
|
225
|
-
print(f"
|
224
|
+
print(f"[green]** Step 0 completed without issues") if verbose < 50 else None
|
226
225
|
print() if verbose < 50 else None
|
227
226
|
|
228
227
|
"""
|
@@ -231,7 +230,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
231
230
|
"""
|
232
231
|
Step = "Step1"
|
233
232
|
serviceName = "config.amazonaws.com"
|
234
|
-
print(f"{
|
233
|
+
print(f"[blue]{Step}:") if verbose < 50 else None
|
235
234
|
print(
|
236
235
|
f"Checking Org Account {fChildAccountId} to see if Config is enabled at the Org Level\n"
|
237
236
|
f"Which means we only run this step if this is the Root Account\n"
|
@@ -256,7 +255,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
256
255
|
|
257
256
|
if fFixRun:
|
258
257
|
logging.warning(
|
259
|
-
f"
|
258
|
+
f"[red]Found {serviceName} is enabled as an Organizational Service and you've requested that we remedy that for you... "
|
260
259
|
)
|
261
260
|
ProcessStatus[Step]["Success"] = False
|
262
261
|
ProcessStatus[Step]["IssuesFound"] += 1
|
@@ -278,25 +277,25 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
278
277
|
logging.error(DelConfigService["ErrorMessage"])
|
279
278
|
|
280
279
|
if ProcessStatus[Step]["Success"]:
|
281
|
-
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues
|
280
|
+
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues") if verbose < 50 else None
|
282
281
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
283
282
|
print(
|
284
|
-
f"{ERASE_LINE + Fore.GREEN}** {Step} found {ProcessStatus[Step]['IssuesFound']} issue, but we were able to fix it
|
283
|
+
f"{ERASE_LINE + Fore.GREEN}** {Step} found {ProcessStatus[Step]['IssuesFound']} issue, but we were able to fix it"
|
285
284
|
) if verbose < 50 else None
|
286
285
|
ProcessStatus[Step]["Success"] = True
|
287
286
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
288
287
|
print(
|
289
|
-
f"{ERASE_LINE + Fore.RED}** {Step} completed, but there was {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blocker found that wasn't fixed
|
288
|
+
f"{ERASE_LINE + Fore.RED}** {Step} completed, but there was {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blocker found that wasn't fixed"
|
290
289
|
) if verbose < 50 else None
|
291
290
|
else:
|
292
|
-
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found
|
291
|
+
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found") if verbose < 50 else None
|
293
292
|
print() if verbose < 50 else None
|
294
293
|
|
295
294
|
# Step 2
|
296
295
|
# This part will check the Config Recorder and Delivery Channel. If they have one, we need to delete it, so we can create another. We'll ask whether this is ok before we delete.
|
297
296
|
Step = "Step2"
|
298
297
|
try:
|
299
|
-
print(f"{
|
298
|
+
print(f"[blue]{Step}:") if verbose < 50 else None
|
300
299
|
print(
|
301
300
|
f" Checking account {fChildAccountId} for a Config Recorders and Delivery Channels in any region"
|
302
301
|
) if verbose < 50 else None
|
@@ -340,7 +339,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
340
339
|
|
341
340
|
for _ in range(len(ConfigList)):
|
342
341
|
logging.warning(
|
343
|
-
f"
|
342
|
+
f"[red]Found a config recorder for account %s in region %s",
|
344
343
|
ConfigList[_]["AccountID"],
|
345
344
|
ConfigList[_]["Region"] + Fore.RESET,
|
346
345
|
)
|
@@ -361,7 +360,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
361
360
|
ProcessStatus[Step]["IssuesFixed"] += 1
|
362
361
|
for _ in range(len(DeliveryChanList)):
|
363
362
|
logging.warning(
|
364
|
-
f"
|
363
|
+
f"[red]Found a delivery channel for account {DeliveryChanList[_]['AccountID']} in region {DeliveryChanList[_]['Region']}"
|
365
364
|
)
|
366
365
|
ProcessStatus[Step]["Success"] = False
|
367
366
|
ProcessStatus[Step]["IssuesFound"] += 1
|
@@ -380,18 +379,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
380
379
|
ProcessStatus[Step]["IssuesFixed"] += 1
|
381
380
|
|
382
381
|
if ProcessStatus[Step]["Success"]:
|
383
|
-
print(f"{ERASE_LINE + Fore.GREEN}** Step 2 completed with no issues
|
382
|
+
print(f"{ERASE_LINE + Fore.GREEN}** Step 2 completed with no issues") if verbose < 50 else None
|
384
383
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
385
384
|
print(
|
386
|
-
f"{ERASE_LINE + Fore.GREEN}** Step 2 found {ProcessStatus[Step]['IssuesFound']} issues, but they were fixed by deleting the existing Config Recorders and Delivery Channels
|
385
|
+
f"{ERASE_LINE + Fore.GREEN}** Step 2 found {ProcessStatus[Step]['IssuesFound']} issues, but they were fixed by deleting the existing Config Recorders and Delivery Channels"
|
387
386
|
) if verbose < 50 else None
|
388
387
|
ProcessStatus[Step]["Success"] = True
|
389
388
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
390
389
|
print(
|
391
|
-
f"{ERASE_LINE + Fore.RED}** Step 2 completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} items found that weren't deleted
|
390
|
+
f"{ERASE_LINE + Fore.RED}** Step 2 completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} items found that weren't deleted"
|
392
391
|
) if verbose < 50 else None
|
393
392
|
else:
|
394
|
-
print(f"{ERASE_LINE + Fore.RED}** Step 2 completed with blockers found
|
393
|
+
print(f"{ERASE_LINE + Fore.RED}** Step 2 completed with blockers found") if verbose < 50 else None
|
395
394
|
print() if verbose < 50 else None
|
396
395
|
|
397
396
|
# Step 3
|
@@ -399,7 +398,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
399
398
|
Step = "Step3"
|
400
399
|
CTtrails2 = []
|
401
400
|
try:
|
402
|
-
print(f"{
|
401
|
+
print(f"[blue]{Step}:") if verbose < 50 else None
|
403
402
|
print(
|
404
403
|
f" Checking account {fChildAccountId} for a specially named CloudTrail in all regions"
|
405
404
|
) if verbose < 50 else None
|
@@ -422,7 +421,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
422
421
|
|
423
422
|
for _ in range(len(CTtrails2)):
|
424
423
|
logging.warning(
|
425
|
-
f"
|
424
|
+
f"[red]Found a CloudTrail trail for account {fChildAccountId} in region {CTtrails2[_]['HomeRegion']} named {CTtrails2[_]['Name']}"
|
426
425
|
)
|
427
426
|
ProcessStatus[Step]["IssuesFound"] += 1
|
428
427
|
ProcessStatus[Step]["ProblemsFound"].extend(CTtrails2)
|
@@ -435,18 +434,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
435
434
|
print(my_Error)
|
436
435
|
|
437
436
|
if ProcessStatus[Step]["Success"]:
|
438
|
-
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues
|
437
|
+
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues") if verbose < 50 else None
|
439
438
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
440
439
|
print(
|
441
|
-
f"{ERASE_LINE + Fore.GREEN}** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but they were fixed by deleting the existing CloudTrail trail names
|
440
|
+
f"{ERASE_LINE + Fore.GREEN}** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but they were fixed by deleting the existing CloudTrail trail names"
|
442
441
|
) if verbose < 50 else None
|
443
442
|
ProcessStatus[Step]["Success"] = True
|
444
443
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
445
444
|
print(
|
446
|
-
f"{ERASE_LINE + Fore.RED}** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} trail names found that wasn't deleted
|
445
|
+
f"{ERASE_LINE + Fore.RED}** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} trail names found that wasn't deleted"
|
447
446
|
) if verbose < 50 else None
|
448
447
|
else:
|
449
|
-
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found
|
448
|
+
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found") if verbose < 50 else None
|
450
449
|
print() if verbose < 50 else None
|
451
450
|
|
452
451
|
""" Step 4
|
@@ -515,7 +514,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
515
514
|
"""
|
516
515
|
# try:
|
517
516
|
Step = "Step5"
|
518
|
-
print(f"{
|
517
|
+
print(f"[blue]{Step}:") if verbose < 50 else None
|
519
518
|
print(" Checking that the account is part of the AWS Organization.") if verbose < 50 else None
|
520
519
|
if not (fChildAccountId in OrgAccountList):
|
521
520
|
print() if verbose < 50 else None
|
@@ -529,18 +528,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
529
528
|
print("Which it is - so ...") if verbose < 50 else None
|
530
529
|
|
531
530
|
if ProcessStatus[Step]["Success"]:
|
532
|
-
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues
|
531
|
+
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues") if verbose < 50 else None
|
533
532
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
534
533
|
print(
|
535
|
-
f"{ERASE_LINE + Fore.GREEN}** {Step} found {ProcessStatus[Step]['IssuesFound']} issue, but we were able to fix it
|
534
|
+
f"{ERASE_LINE + Fore.GREEN}** {Step} found {ProcessStatus[Step]['IssuesFound']} issue, but we were able to fix it"
|
536
535
|
) if verbose < 50 else None
|
537
536
|
ProcessStatus[Step]["Success"] = True
|
538
537
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
539
538
|
print(
|
540
|
-
f"{ERASE_LINE + Fore.RED}** {Step} completed, but there was {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blocker found that wasn't fixed
|
539
|
+
f"{ERASE_LINE + Fore.RED}** {Step} completed, but there was {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blocker found that wasn't fixed"
|
541
540
|
) if verbose < 50 else None
|
542
541
|
else:
|
543
|
-
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found
|
542
|
+
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found") if verbose < 50 else None
|
544
543
|
print() if verbose < 50 else None
|
545
544
|
|
546
545
|
# Step 6
|
@@ -550,7 +549,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
550
549
|
|
551
550
|
Step = "Step6"
|
552
551
|
try:
|
553
|
-
print(f"{
|
552
|
+
print(f"[blue]{Step}:") if verbose < 50 else None
|
554
553
|
print(
|
555
554
|
f" Checking account {fChildAccountId} to make sure it's not already in a Control-Tower managed OU"
|
556
555
|
) if verbose < 50 else None
|
@@ -567,13 +566,13 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
567
566
|
# Checking for SNS Topics
|
568
567
|
Step = "Step7"
|
569
568
|
try:
|
570
|
-
print(f"{
|
569
|
+
print(f"[blue]{Step}:") if verbose < 50 else None
|
571
570
|
print(
|
572
571
|
f" Checking account {fChildAccountId} for any SNS topics containing the 'controltower' string"
|
573
572
|
) if verbose < 50 else None
|
574
573
|
SNSTopics2 = []
|
575
574
|
logging.warning(
|
576
|
-
"Checking account %s in region %s for", fChildAccountId, f"{fRegion + Fore.RED} SNS Topics
|
575
|
+
"Checking account %s in region %s for", fChildAccountId, f"{fRegion + Fore.RED} SNS Topics"
|
577
576
|
)
|
578
577
|
print(
|
579
578
|
ERASE_LINE, f"Checking account {fChildAccountId} in region {fRegion} for SNS Topics", end="\r"
|
@@ -596,31 +595,31 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
596
595
|
ProcessStatus[Step]["Success"] = False
|
597
596
|
|
598
597
|
if ProcessStatus[Step]["Success"]:
|
599
|
-
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues
|
598
|
+
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues") if verbose < 50 else None
|
600
599
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
601
600
|
print(
|
602
|
-
f"{ERASE_LINE + Fore.GREEN}** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but we were able to remove the offending SNS Topics
|
601
|
+
f"{ERASE_LINE + Fore.GREEN}** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but we were able to remove the offending SNS Topics"
|
603
602
|
) if verbose < 50 else None
|
604
603
|
ProcessStatus[Step]["Success"] = True
|
605
604
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
606
605
|
print(
|
607
|
-
f"{ERASE_LINE + Fore.RED}** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blockers found that wasn't fixed
|
606
|
+
f"{ERASE_LINE + Fore.RED}** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blockers found that wasn't fixed"
|
608
607
|
) if verbose < 50 else None
|
609
608
|
else:
|
610
|
-
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found
|
609
|
+
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found") if verbose < 50 else None
|
611
610
|
print() if verbose < 50 else None
|
612
611
|
|
613
612
|
# Step 8
|
614
613
|
# Checking for Lambda functions
|
615
614
|
Step = "Step8"
|
616
615
|
try:
|
617
|
-
print(f"{
|
616
|
+
print(f"[blue]{Step}:") if verbose < 50 else None
|
618
617
|
print(
|
619
618
|
f" Checking account {fChildAccountId} for any Lambda functions containing the 'controltower' string"
|
620
619
|
) if verbose < 50 else None
|
621
620
|
LambdaFunctions2 = []
|
622
621
|
logging.warning(
|
623
|
-
f"Checking account %s in region %s for
|
622
|
+
f"Checking account %s in region %s for [red]Lambda functions", fChildAccountId, fRegion
|
624
623
|
)
|
625
624
|
print(
|
626
625
|
ERASE_LINE, f"Checking account {fChildAccountId} in region {fRegion} for Lambda Functions", end="\r"
|
@@ -651,18 +650,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
651
650
|
ProcessStatus[Step]["Success"] = False
|
652
651
|
|
653
652
|
if ProcessStatus[Step]["Success"]:
|
654
|
-
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues
|
653
|
+
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues") if verbose < 50 else None
|
655
654
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
656
655
|
print(
|
657
|
-
f"{ERASE_LINE + Fore.GREEN}** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but we were able to remove the offending Lambda Functions
|
656
|
+
f"{ERASE_LINE + Fore.GREEN}** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but we were able to remove the offending Lambda Functions"
|
658
657
|
) if verbose < 50 else None
|
659
658
|
ProcessStatus[Step]["Success"] = True
|
660
659
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
661
660
|
print(
|
662
|
-
f"{ERASE_LINE + Fore.RED}** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blockers found that wasn't fixed
|
661
|
+
f"{ERASE_LINE + Fore.RED}** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blockers found that wasn't fixed"
|
663
662
|
) if verbose < 50 else None
|
664
663
|
else:
|
665
|
-
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found
|
664
|
+
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found") if verbose < 50 else None
|
666
665
|
print() if verbose < 50 else None
|
667
666
|
|
668
667
|
# Step 9
|
@@ -670,12 +669,12 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
670
669
|
# TODO: Need to find a way to only run this once, instead of for every region.
|
671
670
|
Step = "Step9"
|
672
671
|
try:
|
673
|
-
print(f"{
|
672
|
+
print(f"[blue]{Step}:") if verbose < 50 else None
|
674
673
|
print(
|
675
674
|
f" Checking account {fChildAccountId} for any Role names containing the 'controltower' string"
|
676
675
|
) if verbose < 50 else None
|
677
676
|
RoleNames2 = []
|
678
|
-
logging.warning(f"Checking account {
|
677
|
+
logging.warning(f"Checking account [red]{fChildAccountId} for Role names")
|
679
678
|
RoleNames = Inventory_Modules.find_role_names2(
|
680
679
|
account_credentials, "us-east-1", ["controltower", "ControlTower"]
|
681
680
|
)
|
@@ -695,18 +694,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
695
694
|
ProcessStatus[Step]["Success"] = False
|
696
695
|
|
697
696
|
if ProcessStatus[Step]["Success"]:
|
698
|
-
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues
|
697
|
+
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues") if verbose < 50 else None
|
699
698
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
700
699
|
print(
|
701
|
-
f"{ERASE_LINE + Fore.GREEN}** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but we were able to remove the offending IAM roles
|
700
|
+
f"{ERASE_LINE + Fore.GREEN}** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but we were able to remove the offending IAM roles"
|
702
701
|
) if verbose < 50 else None
|
703
702
|
ProcessStatus[Step]["Success"] = True
|
704
703
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
705
704
|
print(
|
706
|
-
f"{ERASE_LINE + Fore.RED}** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blockers found that remain to be fixed
|
705
|
+
f"{ERASE_LINE + Fore.RED}** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blockers found that remain to be fixed"
|
707
706
|
) if verbose < 50 else None
|
708
707
|
else:
|
709
|
-
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found
|
708
|
+
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found") if verbose < 50 else None
|
710
709
|
print() if verbose < 50 else None
|
711
710
|
|
712
711
|
# Step 10
|
@@ -715,13 +714,13 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
715
714
|
|
716
715
|
Step = "Step10"
|
717
716
|
try:
|
718
|
-
print(f"{
|
717
|
+
print(f"[blue]{Step}:") if verbose < 50 else None
|
719
718
|
print(
|
720
719
|
f"Checking account {fChildAccountId} to make sure there are no duplicate CloudWatch Log Groups"
|
721
720
|
) if verbose < 50 else None
|
722
721
|
LogGroupNames2 = []
|
723
722
|
logging.warning(
|
724
|
-
f"Checking account {fChildAccountId} for
|
723
|
+
f"Checking account {fChildAccountId} for [red]duplicate CloudWatch Log Group names"
|
725
724
|
)
|
726
725
|
LogGroupNames = Inventory_Modules.find_cw_log_group_names2(
|
727
726
|
account_credentials, fRegion, ["controltower", "ControlTower"]
|
@@ -742,18 +741,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
742
741
|
ProcessStatus[Step]["Success"] = False
|
743
742
|
|
744
743
|
if ProcessStatus[Step]["Success"]:
|
745
|
-
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues
|
744
|
+
print(f"{ERASE_LINE + Fore.GREEN}** {Step} completed with no issues") if verbose < 50 else None
|
746
745
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
747
746
|
print(
|
748
|
-
f"{ERASE_LINE}
|
747
|
+
f"{ERASE_LINE}[green]** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but we were able to remove the offending CW Log groups"
|
749
748
|
) if verbose < 50 else None
|
750
749
|
ProcessStatus[Step]["Success"] = True
|
751
750
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
752
751
|
print(
|
753
|
-
f"{ERASE_LINE}
|
752
|
+
f"{ERASE_LINE}[red]** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blockers found that remain to be fixed "
|
754
753
|
) if verbose < 50 else None
|
755
754
|
else:
|
756
|
-
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found
|
755
|
+
print(f"{ERASE_LINE + Fore.RED}** {Step} completed with blockers found") if verbose < 50 else None
|
757
756
|
print() if verbose < 50 else None
|
758
757
|
|
759
758
|
""" Function Summary """
|
@@ -852,36 +851,43 @@ def DoThreadedAccountSteps(fChildAccountList, aws_account, fFixRun, fRegionList=
|
|
852
851
|
|
853
852
|
|
854
853
|
def display_results():
|
855
|
-
print()
|
856
|
-
x =
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
"
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
854
|
+
console.print()
|
855
|
+
x = create_table(
|
856
|
+
title="Account Readiness Summary",
|
857
|
+
columns=[
|
858
|
+
{"header": "Account", "justify": "left"},
|
859
|
+
{"header": "# of Regions", "justify": "center"},
|
860
|
+
{"header": "Issues Found", "justify": "center"},
|
861
|
+
{"header": "Issues Fixed", "justify": "center"},
|
862
|
+
{"header": "Ready?", "justify": "center"},
|
863
|
+
]
|
864
|
+
)
|
865
|
+
y = create_table(
|
866
|
+
title="Account Issue Details by Region",
|
867
|
+
columns=[
|
868
|
+
{"header": "Account", "justify": "left"},
|
869
|
+
{"header": "Region", "justify": "left"},
|
870
|
+
{"header": "Account Access", "justify": "center"},
|
871
|
+
{"header": "Org Config", "justify": "center"},
|
872
|
+
{"header": "Config", "justify": "center"},
|
873
|
+
{"header": "CloudTrail", "justify": "center"},
|
874
|
+
{"header": "GuardDuty", "justify": "center"},
|
875
|
+
{"header": "Org Member", "justify": "center"},
|
876
|
+
{"header": "CT OU", "justify": "center"},
|
877
|
+
{"header": "SNS Topics", "justify": "center"},
|
878
|
+
{"header": "Lambda", "justify": "center"},
|
879
|
+
{"header": "Roles", "justify": "center"},
|
880
|
+
{"header": "CW Log Groups", "justify": "center"},
|
881
|
+
{"header": "Ready?", "justify": "center"},
|
882
|
+
]
|
883
|
+
)
|
876
884
|
for i in SummarizedOrgResults:
|
877
885
|
x.add_row(
|
878
|
-
[
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
SummarizedOrgResults[i]["Ready"],
|
884
|
-
]
|
886
|
+
SummarizedOrgResults[i]["AccountId"],
|
887
|
+
str(len(SummarizedOrgResults[i]["Regions"])),
|
888
|
+
str(SummarizedOrgResults[i]["IssuesFound"]),
|
889
|
+
str(SummarizedOrgResults[i]["IssuesFixed"]),
|
890
|
+
str(SummarizedOrgResults[i]["Ready"]),
|
885
891
|
)
|
886
892
|
|
887
893
|
sorted_OrgResults = sorted(OrgResults, key=lambda k: (k["AccountId"], k["Region"]))
|
@@ -889,40 +895,37 @@ def display_results():
|
|
889
895
|
for i in sorted_OrgResults:
|
890
896
|
if pSkipAccounts is not None and i["AccountId"] in pSkipAccounts:
|
891
897
|
y.add_row(
|
892
|
-
[
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
]
|
898
|
+
i["AccountId"],
|
899
|
+
i["Region"],
|
900
|
+
"N/A",
|
901
|
+
"N/A",
|
902
|
+
"N/A",
|
903
|
+
"N/A",
|
904
|
+
"N/A",
|
905
|
+
"N/A",
|
906
|
+
"N/A",
|
907
|
+
"N/A",
|
908
|
+
"N/A",
|
909
|
+
"N/A",
|
910
|
+
"N/A",
|
911
|
+
"Skipped",
|
907
912
|
)
|
908
913
|
else:
|
909
|
-
# x.add_row([i['AccountId'], i['Region'], i['IssuesFound'], i['IssuesFixed'], i['Ready']])
|
910
914
|
y.add_row(
|
911
|
-
[
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
i["Step0"]["Success"]
|
915
|
+
i["AccountId"],
|
916
|
+
i["Region"],
|
917
|
+
str(i["Step0"]["IssuesFound"] - i["Step0"]["IssuesFixed"]),
|
918
|
+
str(i["Step1"]["IssuesFound"] - i["Step1"]["IssuesFixed"]),
|
919
|
+
str(i["Step2"]["IssuesFound"] - i["Step2"]["IssuesFixed"]),
|
920
|
+
str(i["Step3"]["IssuesFound"] - i["Step3"]["IssuesFixed"]),
|
921
|
+
str(i["Step4"]["IssuesFound"] - i["Step4"]["IssuesFixed"]),
|
922
|
+
str(i["Step5"]["IssuesFound"] - i["Step5"]["IssuesFixed"]),
|
923
|
+
str(i["Step6"]["IssuesFound"] - i["Step6"]["IssuesFixed"]),
|
924
|
+
str(i["Step7"]["IssuesFound"] - i["Step7"]["IssuesFixed"]),
|
925
|
+
str(i["Step8"]["IssuesFound"] - i["Step8"]["IssuesFixed"]),
|
926
|
+
str(i["Step9"]["IssuesFound"] - i["Step9"]["IssuesFixed"]),
|
927
|
+
str(i["Step10"]["IssuesFound"] - i["Step10"]["IssuesFixed"]),
|
928
|
+
str(i["Step0"]["Success"]
|
926
929
|
and i["Step1"]["Success"]
|
927
930
|
and i["Step2"]["Success"]
|
928
931
|
and i["Step3"]["Success"]
|
@@ -932,28 +935,27 @@ def display_results():
|
|
932
935
|
and i["Step7"]["Success"]
|
933
936
|
and i["Step8"]["Success"]
|
934
937
|
and i["Step9"]["Success"]
|
935
|
-
and i["Step10"]["Success"],
|
936
|
-
]
|
938
|
+
and i["Step10"]["Success"]),
|
937
939
|
)
|
938
|
-
print(
|
939
|
-
"The following table represents the accounts looked at, and whether they are ready to be incorporated into a Control Tower environment."
|
940
|
+
console.print(
|
941
|
+
"\n[bold cyan]The following table represents the accounts looked at, and whether they are ready to be incorporated into a Control Tower environment.[/bold cyan]"
|
940
942
|
)
|
941
|
-
print()
|
943
|
+
console.print()
|
942
944
|
if aws_acct.AccountType.lower() == "root" and (
|
943
945
|
pChildAccountList is None or aws_acct.acct_number in pChildAccountList
|
944
946
|
):
|
945
|
-
print(
|
946
|
-
f"Please note that for the Org Root account {aws_acct.acct_number}, the number of issues found for 'Org Config' will mistakenly show as 1 per region, since these issues are checked on a per-region basis."
|
947
|
+
console.print(
|
948
|
+
f"[yellow]Please note that for the Org Root account {aws_acct.acct_number}, the number of issues found for 'Org Config' will mistakenly show as 1 per region, since these issues are checked on a per-region basis.[/yellow]"
|
947
949
|
)
|
948
|
-
print(
|
949
|
-
f"Additionally, issues found with 'Roles', though global, will show as regional as well. This will be remedied in future versions of this script."
|
950
|
+
console.print(
|
951
|
+
f"[yellow]Additionally, issues found with 'Roles', though global, will show as regional as well. This will be remedied in future versions of this script.[/yellow]"
|
950
952
|
)
|
951
|
-
print(x)
|
952
|
-
print()
|
953
|
-
print(
|
954
|
-
"The following table represents the accounts looked at, and gives details under each type of issue as to what might prevent a successful migration of this account into a Control Tower environment."
|
953
|
+
console.print(x)
|
954
|
+
console.print()
|
955
|
+
console.print(
|
956
|
+
"[bold cyan]The following table represents the accounts looked at, and gives details under each type of issue as to what might prevent a successful migration of this account into a Control Tower environment.[/bold cyan]"
|
955
957
|
)
|
956
|
-
print(y)
|
958
|
+
console.print(y)
|
957
959
|
|
958
960
|
if verbose < 50:
|
959
961
|
for account in OrgResults:
|
@@ -961,21 +963,21 @@ def display_results():
|
|
961
963
|
FixesWorked = account["IssuesFound"] - account["IssuesFixed"] == 0
|
962
964
|
if account["Ready"] and account["IssuesFound"] == 0:
|
963
965
|
print(
|
964
|
-
f"
|
966
|
+
f"[green]**** We've found NO issues that would hinder the adoption of account {account['AccountId']} ****"
|
965
967
|
)
|
966
968
|
elif account["Ready"] and FixesWorked:
|
967
969
|
print(
|
968
|
-
f"
|
969
|
-
f"{account['IssuesFixed']}
|
970
|
-
f"
|
970
|
+
f"[green]We've found and fixed[red]",
|
971
|
+
f"{account['IssuesFixed']}",
|
972
|
+
f"[green]issues that would have otherwise blocked the adoption of account {account['AccountId']}",
|
971
973
|
)
|
972
974
|
else:
|
973
975
|
print(
|
974
|
-
f"
|
976
|
+
f"[red]Account # {account['AccountId']} has {account['IssuesFound'] - account['IssuesFixed']} issues that would hinder the adoption of this account"
|
975
977
|
)
|
976
978
|
for step in account:
|
977
979
|
if step[:4] == "Step" and len(account[step]["ProblemsFound"]) > 0:
|
978
|
-
print(f"{Fore.LIGHTRED_EX}Issues Found for {step} in account {account['AccountId']}:
|
980
|
+
print(f"{Fore.LIGHTRED_EX}Issues Found for {step} in account {account['AccountId']}:")
|
979
981
|
pprint(account[step]["ProblemsFound"])
|
980
982
|
|
981
983
|
|
@@ -983,7 +985,7 @@ def setup(fProfile, fRegions):
|
|
983
985
|
f_aws_acct = aws_acct_access(fProfile)
|
984
986
|
if not f_aws_acct.Success:
|
985
987
|
logging.error(f"Error: {f_aws_acct.ErrorType}")
|
986
|
-
print(f"
|
988
|
+
print(f"[red]\nThere was an error with profile {fProfile}. Unable to continue.\n")
|
987
989
|
sys.exit(9)
|
988
990
|
|
989
991
|
if Quick:
|
@@ -1092,7 +1094,6 @@ SNS topic created for AWS Config -- not yet implemented
|
|
1092
1094
|
|
1093
1095
|
"""
|
1094
1096
|
|
1095
|
-
ERASE_LINE = "\x1b[2K"
|
1096
1097
|
begin_time = time()
|
1097
1098
|
|
1098
1099
|
if __name__ == "__main__":
|
@@ -1102,6 +1103,6 @@ if __name__ == "__main__":
|
|
1102
1103
|
|
1103
1104
|
if pTiming:
|
1104
1105
|
print(ERASE_LINE)
|
1105
|
-
print(f"
|
1106
|
+
print(f"[green]This script took {time() - begin_time:.2f} seconds")
|
1106
1107
|
print("Thanks for using this script...")
|
1107
1108
|
print()
|