runbooks 1.1.7__py3-none-any.whl → 1.1.10__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/__init___optimized.py +2 -1
- runbooks/_platform/__init__.py +1 -1
- runbooks/cfat/cli.py +4 -3
- runbooks/cfat/cloud_foundations_assessment.py +1 -2
- runbooks/cfat/tests/test_cli.py +4 -1
- runbooks/cli/commands/finops.py +68 -19
- runbooks/cli/commands/inventory.py +838 -14
- runbooks/cli/commands/operate.py +65 -4
- runbooks/cli/commands/vpc.py +1 -1
- runbooks/cloudops/cost_optimizer.py +1 -3
- runbooks/common/cli_decorators.py +6 -4
- runbooks/common/config_loader.py +787 -0
- runbooks/common/config_schema.py +280 -0
- runbooks/common/dry_run_framework.py +14 -2
- runbooks/common/mcp_integration.py +238 -0
- runbooks/finops/ebs_cost_optimizer.py +7 -4
- runbooks/finops/elastic_ip_optimizer.py +7 -4
- runbooks/finops/infrastructure/__init__.py +3 -2
- runbooks/finops/infrastructure/commands.py +7 -4
- runbooks/finops/infrastructure/load_balancer_optimizer.py +7 -4
- runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +7 -4
- runbooks/finops/nat_gateway_optimizer.py +7 -4
- runbooks/finops/tests/run_tests.py +1 -1
- runbooks/inventory/ArgumentsClass.py +2 -1
- runbooks/inventory/CLAUDE.md +41 -0
- runbooks/inventory/README.md +210 -2
- runbooks/inventory/Tests/test_Inventory_Modules.py +27 -10
- runbooks/inventory/Tests/test_cfn_describe_stacks.py +18 -7
- runbooks/inventory/Tests/test_ec2_describe_instances.py +30 -15
- runbooks/inventory/Tests/test_lambda_list_functions.py +17 -3
- runbooks/inventory/Tests/test_org_list_accounts.py +17 -4
- runbooks/inventory/account_class.py +0 -1
- runbooks/inventory/all_my_instances_wrapper.py +4 -8
- runbooks/inventory/aws_organization.png +0 -0
- runbooks/inventory/check_cloudtrail_compliance.py +4 -4
- runbooks/inventory/check_controltower_readiness.py +50 -47
- runbooks/inventory/check_landingzone_readiness.py +35 -31
- runbooks/inventory/cloud_foundations_integration.py +8 -3
- runbooks/inventory/collectors/aws_compute.py +59 -11
- runbooks/inventory/collectors/aws_management.py +39 -5
- runbooks/inventory/core/collector.py +1655 -159
- runbooks/inventory/core/concurrent_paginator.py +511 -0
- runbooks/inventory/discovery.md +15 -6
- runbooks/inventory/{draw_org_structure.py → draw_org.py} +55 -9
- runbooks/inventory/drift_detection_cli.py +8 -68
- runbooks/inventory/find_cfn_drift_detection.py +14 -4
- runbooks/inventory/find_cfn_orphaned_stacks.py +7 -5
- runbooks/inventory/find_cfn_stackset_drift.py +5 -5
- runbooks/inventory/find_ec2_security_groups.py +6 -3
- runbooks/inventory/find_landingzone_versions.py +5 -5
- runbooks/inventory/find_vpc_flow_logs.py +5 -5
- runbooks/inventory/inventory.sh +20 -7
- runbooks/inventory/inventory_mcp_cli.py +4 -0
- runbooks/inventory/inventory_modules.py +9 -7
- runbooks/inventory/list_cfn_stacks.py +18 -8
- runbooks/inventory/list_cfn_stackset_operation_results.py +2 -2
- runbooks/inventory/list_cfn_stackset_operations.py +32 -20
- runbooks/inventory/list_cfn_stacksets.py +7 -4
- runbooks/inventory/list_config_recorders_delivery_channels.py +4 -4
- runbooks/inventory/list_ds_directories.py +3 -3
- runbooks/inventory/list_ec2_availability_zones.py +7 -3
- runbooks/inventory/list_ec2_ebs_volumes.py +3 -3
- runbooks/inventory/list_ec2_instances.py +1 -1
- runbooks/inventory/list_ecs_clusters_and_tasks.py +8 -4
- runbooks/inventory/list_elbs_load_balancers.py +7 -3
- runbooks/inventory/list_enis_network_interfaces.py +3 -3
- runbooks/inventory/list_guardduty_detectors.py +9 -5
- runbooks/inventory/list_iam_policies.py +7 -3
- runbooks/inventory/list_iam_roles.py +3 -3
- runbooks/inventory/list_iam_saml_providers.py +8 -4
- runbooks/inventory/list_lambda_functions.py +8 -4
- runbooks/inventory/list_org_accounts.py +306 -276
- runbooks/inventory/list_org_accounts_users.py +45 -9
- runbooks/inventory/list_rds_db_instances.py +4 -4
- runbooks/inventory/list_route53_hosted_zones.py +3 -3
- runbooks/inventory/list_servicecatalog_provisioned_products.py +5 -5
- runbooks/inventory/list_sns_topics.py +4 -4
- runbooks/inventory/list_ssm_parameters.py +6 -3
- runbooks/inventory/list_vpc_subnets.py +8 -4
- runbooks/inventory/list_vpcs.py +15 -4
- runbooks/inventory/mcp_inventory_validator.py +771 -134
- runbooks/inventory/mcp_vpc_validator.py +6 -0
- runbooks/inventory/organizations_discovery.py +17 -3
- runbooks/inventory/organizations_utils.py +553 -0
- runbooks/inventory/output_formatters.py +422 -0
- runbooks/inventory/recover_cfn_stack_ids.py +5 -5
- runbooks/inventory/run_on_multi_accounts.py +3 -3
- runbooks/inventory/tag_coverage.py +481 -0
- runbooks/inventory/validation_utils.py +358 -0
- runbooks/inventory/verify_ec2_security_groups.py +18 -5
- runbooks/inventory/vpc_architecture_validator.py +7 -1
- runbooks/inventory/vpc_dependency_analyzer.py +6 -0
- runbooks/main_final.py +2 -2
- runbooks/main_ultra_minimal.py +2 -2
- runbooks/mcp/integration.py +6 -4
- runbooks/remediation/acm_remediation.py +2 -2
- runbooks/remediation/cloudtrail_remediation.py +2 -2
- runbooks/remediation/cognito_remediation.py +2 -2
- runbooks/remediation/dynamodb_remediation.py +2 -2
- runbooks/remediation/ec2_remediation.py +2 -2
- runbooks/remediation/kms_remediation.py +2 -2
- runbooks/remediation/lambda_remediation.py +2 -2
- runbooks/remediation/rds_remediation.py +2 -2
- runbooks/remediation/s3_remediation.py +1 -1
- runbooks/vpc/cloudtrail_audit_integration.py +1 -1
- {runbooks-1.1.7.dist-info → runbooks-1.1.10.dist-info}/METADATA +74 -4
- {runbooks-1.1.7.dist-info → runbooks-1.1.10.dist-info}/RECORD +112 -105
- runbooks/__init__.py.backup +0 -134
- {runbooks-1.1.7.dist-info → runbooks-1.1.10.dist-info}/WHEEL +0 -0
- {runbooks-1.1.7.dist-info → runbooks-1.1.10.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.7.dist-info → runbooks-1.1.10.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.7.dist-info → runbooks-1.1.10.dist-info}/top_level.txt +0 -0
@@ -8,14 +8,17 @@ from queue import Queue
|
|
8
8
|
from threading import Thread
|
9
9
|
from time import time
|
10
10
|
|
11
|
-
import Inventory_Modules
|
12
|
-
from account_class import aws_acct_access
|
13
|
-
from ArgumentsClass import CommonArguments
|
11
|
+
from runbooks.inventory import inventory_modules as Inventory_Modules
|
12
|
+
from runbooks.inventory.account_class import aws_acct_access
|
13
|
+
from runbooks.inventory.ArgumentsClass import CommonArguments
|
14
14
|
from botocore.exceptions import ClientError
|
15
|
-
from runbooks.common.rich_utils import console, print_success, print_error, print_info
|
16
|
-
from runbooks
|
15
|
+
from runbooks.common.rich_utils import console, print_success, print_error, print_info, create_table
|
16
|
+
from runbooks import __version__
|
17
17
|
|
18
|
-
|
18
|
+
|
19
|
+
|
20
|
+
# Terminal control constants
|
21
|
+
ERASE_LINE = '\x1b[2K'
|
19
22
|
|
20
23
|
script_path, script_name = os.path.split(sys.argv[0])
|
21
24
|
parser = CommonArguments()
|
@@ -210,7 +213,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
210
213
|
print(
|
211
214
|
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}"
|
212
215
|
)
|
213
|
-
print(
|
216
|
+
console.print(f"[red]{CTRoles}[/red]")
|
214
217
|
logging.debug(account_credentials)
|
215
218
|
ProcessStatus[Step]["Success"] = False
|
216
219
|
sys.exit("Exiting due to cross-account access failure")
|
@@ -277,18 +280,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
277
280
|
logging.error(DelConfigService["ErrorMessage"])
|
278
281
|
|
279
282
|
if ProcessStatus[Step]["Success"]:
|
280
|
-
print(f"{ERASE_LINE
|
283
|
+
print(f"{ERASE_LINE}** {Step} completed with no issues") if verbose < 50 else None
|
281
284
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
282
285
|
print(
|
283
|
-
f"{ERASE_LINE
|
286
|
+
f"{ERASE_LINE}** {Step} found {ProcessStatus[Step]['IssuesFound']} issue, but we were able to fix it"
|
284
287
|
) if verbose < 50 else None
|
285
288
|
ProcessStatus[Step]["Success"] = True
|
286
289
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
287
290
|
print(
|
288
|
-
f"{ERASE_LINE
|
291
|
+
f"{ERASE_LINE}** {Step} completed, but there was {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blocker found that wasn't fixed"
|
289
292
|
) if verbose < 50 else None
|
290
293
|
else:
|
291
|
-
print(f"{ERASE_LINE
|
294
|
+
print(f"{ERASE_LINE}** {Step} completed with blockers found") if verbose < 50 else None
|
292
295
|
print() if verbose < 50 else None
|
293
296
|
|
294
297
|
# Step 2
|
@@ -341,7 +344,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
341
344
|
logging.warning(
|
342
345
|
f"[red]Found a config recorder for account %s in region %s",
|
343
346
|
ConfigList[_]["AccountID"],
|
344
|
-
ConfigList[_]["Region"]
|
347
|
+
ConfigList[_]["Region"],
|
345
348
|
)
|
346
349
|
ProcessStatus[Step]["Success"] = False
|
347
350
|
ProcessStatus[Step]["IssuesFound"] += 1
|
@@ -379,18 +382,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
379
382
|
ProcessStatus[Step]["IssuesFixed"] += 1
|
380
383
|
|
381
384
|
if ProcessStatus[Step]["Success"]:
|
382
|
-
print(f"{ERASE_LINE
|
385
|
+
print(f"{ERASE_LINE}** Step 2 completed with no issues") if verbose < 50 else None
|
383
386
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
384
387
|
print(
|
385
|
-
f"{ERASE_LINE
|
388
|
+
f"{ERASE_LINE}** Step 2 found {ProcessStatus[Step]['IssuesFound']} issues, but they were fixed by deleting the existing Config Recorders and Delivery Channels"
|
386
389
|
) if verbose < 50 else None
|
387
390
|
ProcessStatus[Step]["Success"] = True
|
388
391
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
389
392
|
print(
|
390
|
-
f"{ERASE_LINE
|
393
|
+
f"{ERASE_LINE}** Step 2 completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} items found that weren't deleted"
|
391
394
|
) if verbose < 50 else None
|
392
395
|
else:
|
393
|
-
print(f"{ERASE_LINE
|
396
|
+
print(f"{ERASE_LINE}** Step 2 completed with blockers found") if verbose < 50 else None
|
394
397
|
print() if verbose < 50 else None
|
395
398
|
|
396
399
|
# Step 3
|
@@ -434,18 +437,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
434
437
|
print(my_Error)
|
435
438
|
|
436
439
|
if ProcessStatus[Step]["Success"]:
|
437
|
-
print(f"{ERASE_LINE
|
440
|
+
print(f"{ERASE_LINE}** {Step} completed with no issues") if verbose < 50 else None
|
438
441
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
439
442
|
print(
|
440
|
-
f"{ERASE_LINE
|
443
|
+
f"{ERASE_LINE}** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but they were fixed by deleting the existing CloudTrail trail names"
|
441
444
|
) if verbose < 50 else None
|
442
445
|
ProcessStatus[Step]["Success"] = True
|
443
446
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
444
447
|
print(
|
445
|
-
f"{ERASE_LINE
|
448
|
+
f"{ERASE_LINE}** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} trail names found that wasn't deleted"
|
446
449
|
) if verbose < 50 else None
|
447
450
|
else:
|
448
|
-
print(f"{ERASE_LINE
|
451
|
+
print(f"{ERASE_LINE}** {Step} completed with blockers found") if verbose < 50 else None
|
449
452
|
print() if verbose < 50 else None
|
450
453
|
|
451
454
|
""" Step 4
|
@@ -453,11 +456,11 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
453
456
|
# 4. This section checks for a pending guard duty invite. You can also check from the Guard Duty Console
|
454
457
|
Step='Step4'
|
455
458
|
try:
|
456
|
-
print(
|
459
|
+
print("{}:".format(Step))
|
457
460
|
print(" Checking account {} for any GuardDuty invites".format(fChildAccountId))
|
458
461
|
GDinvites2=[]
|
459
462
|
for region in fRegionList:
|
460
|
-
logging.warning("Checking account %s in region %s for", fChildAccountId, region+
|
463
|
+
logging.warning("Checking account %s in region %s for", fChildAccountId, region+" GuardDuty"+" invitations")
|
461
464
|
logging.warning("Checking account %s in region %s for GuardDuty invites", fChildAccountId, region)
|
462
465
|
GDinvites=Inventory_Modules.find_gd_invites(account_credentials, region)
|
463
466
|
if len(GDinvites) > 0:
|
@@ -475,7 +478,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
475
478
|
ProcessStatus[Step]['Success']=False
|
476
479
|
|
477
480
|
for i in range(len(GDinvites2)):
|
478
|
-
logging.warning(
|
481
|
+
logging.warning("I found a GuardDuty invitation for account %s in region %s from account %s ", fChildAccountId, GDinvites2[i]['Region'], GDinvites2[i]['AccountId'])
|
479
482
|
ProcessStatus[Step]['IssuesFound']+=1
|
480
483
|
ProcessStatus[Step]['Success']=False
|
481
484
|
if fFixRun:
|
@@ -489,14 +492,14 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
489
492
|
print(my_Error)
|
490
493
|
|
491
494
|
if ProcessStatus[Step]['Success']:
|
492
|
-
print(ERASE_LINE+
|
495
|
+
print(ERASE_LINE+"** {} completed with no issues".format(Step))
|
493
496
|
elif ProcessStatus[Step]['IssuesFound']-ProcessStatus[Step]['IssuesFixed']==0:
|
494
|
-
print(ERASE_LINE+
|
497
|
+
print(ERASE_LINE+"** {} found {} guardduty invites, but they were deleted".format(Step,ProcessStatus[Step]['IssuesFound']))
|
495
498
|
ProcessStatus[Step]['Success']=True
|
496
499
|
elif (ProcessStatus[Step]['IssuesFound']>ProcessStatus[Step]['IssuesFixed']):
|
497
|
-
print(ERASE_LINE+
|
500
|
+
print(ERASE_LINE+"** {} completed, but there were {} guardduty invites found that couldn't be deleted".format(Step,ProcessStatus[Step]['IssuesFound']-ProcessStatus[Step]['IssuesFixed']))
|
498
501
|
else:
|
499
|
-
print(ERASE_LINE+
|
502
|
+
print(ERASE_LINE+"** {} completed with blockers found".format(Step))
|
500
503
|
print()
|
501
504
|
"""
|
502
505
|
|
@@ -528,18 +531,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
528
531
|
print("Which it is - so ...") if verbose < 50 else None
|
529
532
|
|
530
533
|
if ProcessStatus[Step]["Success"]:
|
531
|
-
print(f"{ERASE_LINE
|
534
|
+
print(f"{ERASE_LINE}** {Step} completed with no issues") if verbose < 50 else None
|
532
535
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
533
536
|
print(
|
534
|
-
f"{ERASE_LINE
|
537
|
+
f"{ERASE_LINE}** {Step} found {ProcessStatus[Step]['IssuesFound']} issue, but we were able to fix it"
|
535
538
|
) if verbose < 50 else None
|
536
539
|
ProcessStatus[Step]["Success"] = True
|
537
540
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
538
541
|
print(
|
539
|
-
f"{ERASE_LINE
|
542
|
+
f"{ERASE_LINE}** {Step} completed, but there was {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blocker found that wasn't fixed"
|
540
543
|
) if verbose < 50 else None
|
541
544
|
else:
|
542
|
-
print(f"{ERASE_LINE
|
545
|
+
print(f"{ERASE_LINE}** {Step} completed with blockers found") if verbose < 50 else None
|
543
546
|
print() if verbose < 50 else None
|
544
547
|
|
545
548
|
# Step 6
|
@@ -572,7 +575,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
572
575
|
) if verbose < 50 else None
|
573
576
|
SNSTopics2 = []
|
574
577
|
logging.warning(
|
575
|
-
"Checking account %s in region %s for", fChildAccountId,
|
578
|
+
"Checking account %s in region %s for SNS Topics", fChildAccountId, fRegion
|
576
579
|
)
|
577
580
|
print(
|
578
581
|
ERASE_LINE, f"Checking account {fChildAccountId} in region {fRegion} for SNS Topics", end="\r"
|
@@ -595,18 +598,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
595
598
|
ProcessStatus[Step]["Success"] = False
|
596
599
|
|
597
600
|
if ProcessStatus[Step]["Success"]:
|
598
|
-
print(f"{ERASE_LINE
|
601
|
+
print(f"{ERASE_LINE}** {Step} completed with no issues") if verbose < 50 else None
|
599
602
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
600
603
|
print(
|
601
|
-
f"{ERASE_LINE
|
604
|
+
f"{ERASE_LINE}** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but we were able to remove the offending SNS Topics"
|
602
605
|
) if verbose < 50 else None
|
603
606
|
ProcessStatus[Step]["Success"] = True
|
604
607
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
605
608
|
print(
|
606
|
-
f"{ERASE_LINE
|
609
|
+
f"{ERASE_LINE}** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blockers found that wasn't fixed"
|
607
610
|
) if verbose < 50 else None
|
608
611
|
else:
|
609
|
-
print(f"{ERASE_LINE
|
612
|
+
print(f"{ERASE_LINE}** {Step} completed with blockers found") if verbose < 50 else None
|
610
613
|
print() if verbose < 50 else None
|
611
614
|
|
612
615
|
# Step 8
|
@@ -650,18 +653,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
650
653
|
ProcessStatus[Step]["Success"] = False
|
651
654
|
|
652
655
|
if ProcessStatus[Step]["Success"]:
|
653
|
-
print(f"{ERASE_LINE
|
656
|
+
print(f"{ERASE_LINE}** {Step} completed with no issues") if verbose < 50 else None
|
654
657
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
655
658
|
print(
|
656
|
-
f"{ERASE_LINE
|
659
|
+
f"{ERASE_LINE}** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but we were able to remove the offending Lambda Functions"
|
657
660
|
) if verbose < 50 else None
|
658
661
|
ProcessStatus[Step]["Success"] = True
|
659
662
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
660
663
|
print(
|
661
|
-
f"{ERASE_LINE
|
664
|
+
f"{ERASE_LINE}** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blockers found that wasn't fixed"
|
662
665
|
) if verbose < 50 else None
|
663
666
|
else:
|
664
|
-
print(f"{ERASE_LINE
|
667
|
+
print(f"{ERASE_LINE}** {Step} completed with blockers found") if verbose < 50 else None
|
665
668
|
print() if verbose < 50 else None
|
666
669
|
|
667
670
|
# Step 9
|
@@ -694,18 +697,18 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
694
697
|
ProcessStatus[Step]["Success"] = False
|
695
698
|
|
696
699
|
if ProcessStatus[Step]["Success"]:
|
697
|
-
print(f"{ERASE_LINE
|
700
|
+
print(f"{ERASE_LINE}** {Step} completed with no issues") if verbose < 50 else None
|
698
701
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
699
702
|
print(
|
700
|
-
f"{ERASE_LINE
|
703
|
+
f"{ERASE_LINE}** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but we were able to remove the offending IAM roles"
|
701
704
|
) if verbose < 50 else None
|
702
705
|
ProcessStatus[Step]["Success"] = True
|
703
706
|
elif ProcessStatus[Step]["IssuesFound"] > ProcessStatus[Step]["IssuesFixed"]:
|
704
707
|
print(
|
705
|
-
f"{ERASE_LINE
|
708
|
+
f"{ERASE_LINE}** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blockers found that remain to be fixed"
|
706
709
|
) if verbose < 50 else None
|
707
710
|
else:
|
708
|
-
print(f"{ERASE_LINE
|
711
|
+
print(f"{ERASE_LINE}** {Step} completed with blockers found") if verbose < 50 else None
|
709
712
|
print() if verbose < 50 else None
|
710
713
|
|
711
714
|
# Step 10
|
@@ -741,7 +744,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
741
744
|
ProcessStatus[Step]["Success"] = False
|
742
745
|
|
743
746
|
if ProcessStatus[Step]["Success"]:
|
744
|
-
print(f"{ERASE_LINE
|
747
|
+
print(f"{ERASE_LINE}** {Step} completed with no issues") if verbose < 50 else None
|
745
748
|
elif ProcessStatus[Step]["IssuesFound"] - ProcessStatus[Step]["IssuesFixed"] == 0:
|
746
749
|
print(
|
747
750
|
f"{ERASE_LINE}[green]** {Step} found {ProcessStatus[Step]['IssuesFound']} issues, but we were able to remove the offending CW Log groups"
|
@@ -752,7 +755,7 @@ def DoAccountSteps(fChildAccountId, aws_account, fFixRun, fRegion):
|
|
752
755
|
f"{ERASE_LINE}[red]** {Step} completed, but there were {ProcessStatus[Step]['IssuesFound'] - ProcessStatus[Step]['IssuesFixed']} blockers found that remain to be fixed "
|
753
756
|
) if verbose < 50 else None
|
754
757
|
else:
|
755
|
-
print(f"{ERASE_LINE
|
758
|
+
print(f"{ERASE_LINE}** {Step} completed with blockers found") if verbose < 50 else None
|
756
759
|
print() if verbose < 50 else None
|
757
760
|
|
758
761
|
""" Function Summary """
|
@@ -977,7 +980,7 @@ def display_results():
|
|
977
980
|
)
|
978
981
|
for step in account:
|
979
982
|
if step[:4] == "Step" and len(account[step]["ProblemsFound"]) > 0:
|
980
|
-
print(f"
|
983
|
+
print(f"[red]Issues Found for {step} in account {account['AccountId']}:[/red]")
|
981
984
|
pprint(account[step]["ProblemsFound"])
|
982
985
|
|
983
986
|
|
@@ -3,15 +3,19 @@
|
|
3
3
|
import logging
|
4
4
|
import sys
|
5
5
|
|
6
|
-
import ec2_vpc_utils as vpc_modules
|
7
|
-
import Inventory_Modules
|
8
|
-
from account_class import aws_acct_access
|
9
|
-
from ArgumentsClass import CommonArguments
|
6
|
+
from runbooks.inventory import ec2_vpc_utils as vpc_modules
|
7
|
+
from runbooks.inventory import inventory_modules as Inventory_Modules
|
8
|
+
from runbooks.inventory.account_class import aws_acct_access
|
9
|
+
from runbooks.inventory.ArgumentsClass import CommonArguments
|
10
10
|
from botocore.exceptions import ClientError
|
11
11
|
from runbooks.common.rich_utils import console, print_success, print_error, print_info
|
12
12
|
from runbooks.common.rich_utils import console, create_table
|
13
|
+
from runbooks import __version__
|
13
14
|
|
14
|
-
|
15
|
+
|
16
|
+
|
17
|
+
# Terminal control constants
|
18
|
+
ERASE_LINE = '\x1b[2K'
|
15
19
|
|
16
20
|
parser = CommonArguments()
|
17
21
|
parser.singleprofile()
|
@@ -222,11 +226,11 @@ if aws_account.AccountType.lower() == "root" and pChildAccountId is None:
|
|
222
226
|
)
|
223
227
|
elif pChildAccountId is None:
|
224
228
|
sys.exit(
|
225
|
-
f"Account {aws_account.
|
229
|
+
f"Account {aws_account.acct_num} is a {aws_account.AccountType} account. This script should be run with Management Account credentials."
|
226
230
|
)
|
227
231
|
else:
|
228
232
|
print(
|
229
|
-
f"Account {aws_account.
|
233
|
+
f"Account {aws_account.acct_num} is a {aws_account.AccountType} account."
|
230
234
|
f"We're checking to validate that account {pChildAccountId} can be adopted into the Landing Zone"
|
231
235
|
)
|
232
236
|
ChildAccountList = [pChildAccountId]
|
@@ -285,7 +289,7 @@ for childaccount in ChildAccountList:
|
|
285
289
|
continue
|
286
290
|
else:
|
287
291
|
logging.error(
|
288
|
-
f"Was able to successfully connect to account {childaccount} using credentials from account {aws_account.
|
292
|
+
f"Was able to successfully connect to account {childaccount} using credentials from account {aws_account.acct_num}... "
|
289
293
|
)
|
290
294
|
print()
|
291
295
|
print(f"[green]** Step 0 completed without issues")
|
@@ -362,18 +366,18 @@ for childaccount in ChildAccountList:
|
|
362
366
|
|
363
367
|
print()
|
364
368
|
if ProcessStatus[childaccount]["Step1"]["Success"]:
|
365
|
-
print(f"{ERASE_LINE
|
369
|
+
print(f"{ERASE_LINE}** Step 1 completed with no issues")
|
366
370
|
elif ProcessStatus[childaccount]["Step1"]["IssuesFound"] - ProcessStatus[childaccount]["Step1"]["IssuesFixed"] == 0:
|
367
371
|
print(
|
368
|
-
f"{ERASE_LINE
|
372
|
+
f"{ERASE_LINE}** Step 1 found {ProcessStatus[childaccount]['Step1']['IssuesFound']} issues, but they were fixed by deleting the default vpcs"
|
369
373
|
)
|
370
374
|
ProcessStatus[childaccount]["Step1"]["Success"] = True
|
371
375
|
elif ProcessStatus[childaccount]["Step1"]["IssuesFound"] > ProcessStatus[childaccount]["Step1"]["IssuesFixed"]:
|
372
376
|
print(
|
373
|
-
f"{ERASE_LINE
|
377
|
+
f"{ERASE_LINE}** Step 1 completed, but there were {ProcessStatus[childaccount]['Step1']['IssuesFound'] - ProcessStatus[childaccount]['Step1']['IssuesFixed']} vpcs that couldn't be fixed"
|
374
378
|
)
|
375
379
|
else:
|
376
|
-
print(f"{ERASE_LINE
|
380
|
+
print(f"{ERASE_LINE}** Step 1 completed with blockers found")
|
377
381
|
|
378
382
|
# Step 2
|
379
383
|
# 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.
|
@@ -422,7 +426,7 @@ for childaccount in ChildAccountList:
|
|
422
426
|
logging.error(
|
423
427
|
f"[red]Found a config recorder for account %s in region %s",
|
424
428
|
ConfigList[i]["AccountID"],
|
425
|
-
ConfigList[i]["Region"]
|
429
|
+
ConfigList[i]["Region"],
|
426
430
|
)
|
427
431
|
ProcessStatus[childaccount]["Step2"]["IssuesFound"] += 1
|
428
432
|
config_deliv_channels_found = True
|
@@ -442,7 +446,7 @@ for childaccount in ChildAccountList:
|
|
442
446
|
logging.error(
|
443
447
|
f"[red]I found a delivery channel for account %s in region %s",
|
444
448
|
DeliveryChanList[i]["AccountID"],
|
445
|
-
DeliveryChanList[i]["Region"]
|
449
|
+
DeliveryChanList[i]["Region"],
|
446
450
|
)
|
447
451
|
ProcessStatus[childaccount]["Step2"]["IssuesFound"] += 1
|
448
452
|
config_deliv_channels_found = True
|
@@ -464,18 +468,18 @@ for childaccount in ChildAccountList:
|
|
464
468
|
ProcessStatus[childaccount]["Step2"]["Success"] = True
|
465
469
|
|
466
470
|
if ProcessStatus[childaccount]["Step2"]["Success"]:
|
467
|
-
print(f"{ERASE_LINE
|
471
|
+
print(f"{ERASE_LINE}** Step 2 completed with no issues")
|
468
472
|
elif ProcessStatus[childaccount]["Step2"]["IssuesFound"] - ProcessStatus[childaccount]["Step2"]["IssuesFixed"] == 0:
|
469
473
|
print(
|
470
|
-
f"{ERASE_LINE
|
474
|
+
f"{ERASE_LINE}** Step 2 found {ProcessStatus[childaccount]['Step2']['IssuesFound']} issues, but they were fixed by deleting the existing Config Recorders and Delivery Channels"
|
471
475
|
)
|
472
476
|
ProcessStatus[childaccount]["Step2"]["Success"] = True
|
473
477
|
elif ProcessStatus[childaccount]["Step2"]["IssuesFound"] > ProcessStatus[childaccount]["Step2"]["IssuesFixed"]:
|
474
478
|
print(
|
475
|
-
f"{ERASE_LINE
|
479
|
+
f"{ERASE_LINE}** Step 2 completed, but there were {ProcessStatus[childaccount]['Step2']['IssuesFound'] - ProcessStatus[childaccount]['Step2']['IssuesFixed']} items found that couldn't be deleted"
|
476
480
|
)
|
477
481
|
else:
|
478
|
-
print(f"{ERASE_LINE
|
482
|
+
print(f"{ERASE_LINE}** Step 2 completed with blockers found")
|
479
483
|
print()
|
480
484
|
|
481
485
|
# Step 3
|
@@ -521,18 +525,18 @@ for childaccount in ChildAccountList:
|
|
521
525
|
ProcessStatus[childaccount]["Step3"]["Success"] = True
|
522
526
|
|
523
527
|
if ProcessStatus[childaccount]["Step3"]["Success"]:
|
524
|
-
print(f"{ERASE_LINE
|
528
|
+
print(f"{ERASE_LINE}** Step 3 completed with no issues")
|
525
529
|
elif ProcessStatus[childaccount]["Step3"]["IssuesFound"] - ProcessStatus[childaccount]["Step3"]["IssuesFixed"] == 0:
|
526
530
|
print(
|
527
|
-
f"{ERASE_LINE
|
531
|
+
f"{ERASE_LINE}** Step 3 found {ProcessStatus[childaccount]['Step3']['IssuesFound']} issues, but they were fixed by deleting the existing CloudTrail trail names"
|
528
532
|
)
|
529
533
|
ProcessStatus[childaccount]["Step3"]["Success"] = True
|
530
534
|
elif ProcessStatus[childaccount]["Step3"]["IssuesFound"] > ProcessStatus[childaccount]["Step3"]["IssuesFixed"]:
|
531
535
|
print(
|
532
|
-
f"{ERASE_LINE
|
536
|
+
f"{ERASE_LINE}** Step 3 completed, but there were {ProcessStatus[childaccount]['Step3']['IssuesFound'] - ProcessStatus[childaccount]['Step3']['IssuesFixed']} trail names found that couldn't be deleted"
|
533
537
|
)
|
534
538
|
else:
|
535
|
-
print(f"{ERASE_LINE
|
539
|
+
print(f"{ERASE_LINE}** Step 3 completed with blockers found")
|
536
540
|
print()
|
537
541
|
|
538
542
|
# Step 4
|
@@ -571,7 +575,7 @@ for childaccount in ChildAccountList:
|
|
571
575
|
f"[red]I found a GuardDuty invitation for account %s in region %s from account %s ",
|
572
576
|
childaccount,
|
573
577
|
GDinvites2[i]["Region"],
|
574
|
-
GDinvites2[i]["AccountId"]
|
578
|
+
GDinvites2[i]["AccountId"],
|
575
579
|
)
|
576
580
|
ProcessStatus[childaccount]["Step4"]["IssuesFound"] += 1
|
577
581
|
if FixRun:
|
@@ -591,18 +595,18 @@ for childaccount in ChildAccountList:
|
|
591
595
|
ProcessStatus[childaccount]["Step4"]["Success"] = True
|
592
596
|
|
593
597
|
if ProcessStatus[childaccount]["Step4"]["Success"]:
|
594
|
-
print(f"{ERASE_LINE
|
598
|
+
print(f"{ERASE_LINE}** Step 4 completed with no issues")
|
595
599
|
elif ProcessStatus[childaccount]["Step4"]["IssuesFound"] - ProcessStatus[childaccount]["Step4"]["IssuesFixed"] == 0:
|
596
600
|
print(
|
597
|
-
f"{ERASE_LINE
|
601
|
+
f"{ERASE_LINE}** Step 4 found {ProcessStatus[childaccount]['Step4']['IssuesFound']} guardduty invites, but they were deleted"
|
598
602
|
)
|
599
603
|
ProcessStatus[childaccount]["Step4"]["Success"] = True
|
600
604
|
elif ProcessStatus[childaccount]["Step4"]["IssuesFound"] > ProcessStatus[childaccount]["Step4"]["IssuesFixed"]:
|
601
605
|
print(
|
602
|
-
f"{ERASE_LINE
|
606
|
+
f"{ERASE_LINE}** Step 4 completed, but there were {ProcessStatus[childaccount]['Step4']['IssuesFound'] - ProcessStatus[childaccount]['Step4']['IssuesFixed']} guardduty invites found that couldn't be deleted"
|
603
607
|
)
|
604
608
|
else:
|
605
|
-
print(f"{ERASE_LINE
|
609
|
+
print(f"{ERASE_LINE}** Step 4 completed with blockers found")
|
606
610
|
print()
|
607
611
|
|
608
612
|
"""
|
@@ -629,18 +633,18 @@ for childaccount in ChildAccountList:
|
|
629
633
|
ProcessStatus[childaccount]["Step5"]["IssuesFound"] += 1
|
630
634
|
|
631
635
|
if ProcessStatus[childaccount]["Step5"]["Success"]:
|
632
|
-
print(f"{ERASE_LINE
|
636
|
+
print(f"{ERASE_LINE}** Step 5 completed with no issues")
|
633
637
|
elif ProcessStatus[childaccount]["Step5"]["IssuesFound"] - ProcessStatus[childaccount]["Step5"]["IssuesFixed"] == 0:
|
634
638
|
print(
|
635
|
-
f"{ERASE_LINE
|
639
|
+
f"{ERASE_LINE}** Step 5 found {ProcessStatus[childaccount]['Step5']['IssuesFound']} issues, but we were able to move the account into the they were able to be fixed"
|
636
640
|
)
|
637
641
|
ProcessStatus[childaccount]["Step5"]["Success"] = True
|
638
642
|
elif ProcessStatus[childaccount]["Step5"]["IssuesFound"] > ProcessStatus[childaccount]["Step5"]["IssuesFixed"]:
|
639
643
|
print(
|
640
|
-
f"{ERASE_LINE
|
644
|
+
f"{ERASE_LINE}** Step 5 completed, but there were {ProcessStatus[childaccount]['Step5']['IssuesFound'] - ProcessStatus[childaccount]['Step5']['IssuesFixed']} blockers found that couldn't be fixed"
|
641
645
|
)
|
642
646
|
else:
|
643
|
-
print(f"{ERASE_LINE
|
647
|
+
print(f"{ERASE_LINE}** Step 5 completed with blockers found")
|
644
648
|
print()
|
645
649
|
|
646
650
|
print(f"[cyan]Account {childaccount} is complete. {accountsleft} more to go!!")
|
@@ -28,6 +28,8 @@ from botocore.exceptions import ClientError, ProfileNotFound
|
|
28
28
|
|
29
29
|
# Import runbooks enterprise standards
|
30
30
|
from runbooks.common.rich_utils import (
|
31
|
+
|
32
|
+
# Terminal control constants
|
31
33
|
console,
|
32
34
|
print_header,
|
33
35
|
print_success,
|
@@ -37,9 +39,11 @@ from runbooks.common.rich_utils import (
|
|
37
39
|
create_progress_bar,
|
38
40
|
create_panel,
|
39
41
|
)
|
40
|
-
from runbooks.common.profile_utils import get_profile_for_operation
|
41
42
|
|
42
|
-
|
43
|
+
# Terminal control constants
|
44
|
+
ERASE_LINE = '\x1b[2K'
|
45
|
+
from runbooks.common.profile_utils import get_profile_for_operation
|
46
|
+
from runbooks import __version__
|
43
47
|
|
44
48
|
|
45
49
|
@dataclass
|
@@ -368,7 +372,8 @@ Organizational Units: {len(structured_accounts)}
|
|
368
372
|
Session Cache: {len(self.session_cache)} active sessions
|
369
373
|
"""
|
370
374
|
|
371
|
-
|
375
|
+
from rich.panel import Panel
|
376
|
+
summary_panel = Panel(summary_text.strip(), title="[bold green]Organization Summary[/bold green]", border_style="green")
|
372
377
|
console.print(summary_panel)
|
373
378
|
|
374
379
|
|
@@ -114,24 +114,72 @@ class ComputeResourceCollector(BaseResourceCollector):
|
|
114
114
|
def _collect_ec2_instances(
|
115
115
|
self, ec2_client: boto3.client, context: CollectionContext, filters: Dict[str, Any]
|
116
116
|
) -> List[AWSResource]:
|
117
|
-
"""
|
117
|
+
"""
|
118
|
+
Collect EC2 instances with optional state filtering.
|
119
|
+
|
120
|
+
Args:
|
121
|
+
ec2_client: boto3 EC2 client
|
122
|
+
context: Collection context
|
123
|
+
filters: Resource filters including optional 'status' for instance state
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
List of EC2 instance resources
|
127
|
+
"""
|
118
128
|
resources = []
|
119
129
|
|
120
130
|
try:
|
121
|
-
|
131
|
+
# Build boto3 Filters parameter for AWS API call
|
132
|
+
api_filters = []
|
133
|
+
if filters.get("status"):
|
134
|
+
api_filters.append({
|
135
|
+
"Name": "instance-state-name",
|
136
|
+
"Values": [filters["status"]] # "running" or "stopped"
|
137
|
+
})
|
138
|
+
logger.info(f"EC2 filtering: instance-state-name={filters['status']}")
|
122
139
|
|
123
|
-
|
124
|
-
for reservation in page["Reservations"]:
|
125
|
-
for instance in reservation["Instances"]:
|
126
|
-
resource = self._create_ec2_instance_resource(instance, context)
|
127
|
-
if resource:
|
128
|
-
resources.append(resource)
|
140
|
+
paginator = ec2_client.get_paginator("describe_instances")
|
129
141
|
|
130
|
-
|
142
|
+
# Apply filters to AWS API call if present
|
143
|
+
if api_filters:
|
144
|
+
logger.debug(f"Applying boto3 Filters to describe_instances: {api_filters}")
|
145
|
+
for page in paginator.paginate(Filters=api_filters):
|
146
|
+
for reservation in page["Reservations"]:
|
147
|
+
for instance in reservation["Instances"]:
|
148
|
+
resource = self._create_ec2_instance_resource(instance, context)
|
149
|
+
if resource:
|
150
|
+
resources.append(resource)
|
151
|
+
else:
|
152
|
+
# Backward compatibility: No filters provided
|
153
|
+
for page in paginator.paginate():
|
154
|
+
for reservation in page["Reservations"]:
|
155
|
+
for instance in reservation["Instances"]:
|
156
|
+
resource = self._create_ec2_instance_resource(instance, context)
|
157
|
+
if resource:
|
158
|
+
resources.append(resource)
|
159
|
+
|
160
|
+
logger.debug(f"Collected {len(resources)} EC2 instances (filtered: {bool(api_filters)})")
|
131
161
|
|
132
162
|
except ClientError as e:
|
133
|
-
|
134
|
-
|
163
|
+
error_code = e.response.get('Error', {}).get('Code', 'Unknown')
|
164
|
+
|
165
|
+
# Graceful degradation for permission errors
|
166
|
+
if error_code in ['UnauthorizedOperation', 'AccessDenied']:
|
167
|
+
logger.warning(f"Insufficient permissions for EC2 filtering, collecting all instances: {error_code}")
|
168
|
+
# Fallback to unfiltered collection
|
169
|
+
try:
|
170
|
+
for page in paginator.paginate():
|
171
|
+
for reservation in page["Reservations"]:
|
172
|
+
for instance in reservation["Instances"]:
|
173
|
+
resource = self._create_ec2_instance_resource(instance, context)
|
174
|
+
if resource:
|
175
|
+
resources.append(resource)
|
176
|
+
logger.debug(f"Collected {len(resources)} EC2 instances (fallback mode)")
|
177
|
+
except Exception as fallback_error:
|
178
|
+
logger.error(f"Fallback collection also failed: {fallback_error}")
|
179
|
+
raise
|
180
|
+
else:
|
181
|
+
logger.error(f"Failed to collect EC2 instances: {e}")
|
182
|
+
raise
|
135
183
|
|
136
184
|
return resources
|
137
185
|
|