runbooks 0.2.3__py3-none-any.whl → 0.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- conftest.py +26 -0
- jupyter-agent/.env.template +2 -0
- jupyter-agent/.gitattributes +35 -0
- jupyter-agent/README.md +16 -0
- jupyter-agent/app.py +256 -0
- jupyter-agent/cloudops-agent.png +0 -0
- jupyter-agent/ds-system-prompt.txt +154 -0
- jupyter-agent/jupyter-agent.png +0 -0
- jupyter-agent/llama3_template.jinja +123 -0
- jupyter-agent/requirements.txt +9 -0
- jupyter-agent/utils.py +409 -0
- runbooks/__init__.py +71 -3
- runbooks/__main__.py +13 -0
- runbooks/aws/ec2_describe_instances.py +1 -1
- runbooks/aws/ec2_run_instances.py +8 -2
- runbooks/aws/ec2_start_stop_instances.py +17 -4
- runbooks/aws/ec2_unused_volumes.py +5 -1
- runbooks/aws/s3_create_bucket.py +4 -2
- runbooks/aws/s3_list_objects.py +6 -1
- runbooks/aws/tagging_lambda_handler.py +13 -2
- runbooks/aws/tags.json +12 -0
- runbooks/base.py +353 -0
- runbooks/cfat/README.md +49 -0
- runbooks/cfat/__init__.py +74 -0
- runbooks/cfat/app.ts +644 -0
- runbooks/cfat/assessment/__init__.py +40 -0
- runbooks/cfat/assessment/asana-import.csv +39 -0
- runbooks/cfat/assessment/cfat-checks.csv +31 -0
- runbooks/cfat/assessment/cfat.txt +520 -0
- runbooks/cfat/assessment/collectors.py +200 -0
- runbooks/cfat/assessment/jira-import.csv +39 -0
- runbooks/cfat/assessment/runner.py +387 -0
- runbooks/cfat/assessment/validators.py +290 -0
- runbooks/cfat/cli.py +103 -0
- runbooks/cfat/docs/asana-import.csv +24 -0
- runbooks/cfat/docs/cfat-checks.csv +31 -0
- runbooks/cfat/docs/cfat.txt +335 -0
- runbooks/cfat/docs/checks-output.png +0 -0
- runbooks/cfat/docs/cloudshell-console-run.png +0 -0
- runbooks/cfat/docs/cloudshell-download.png +0 -0
- runbooks/cfat/docs/cloudshell-output.png +0 -0
- runbooks/cfat/docs/downloadfile.png +0 -0
- runbooks/cfat/docs/jira-import.csv +24 -0
- runbooks/cfat/docs/open-cloudshell.png +0 -0
- runbooks/cfat/docs/report-header.png +0 -0
- runbooks/cfat/models.py +1026 -0
- runbooks/cfat/package-lock.json +5116 -0
- runbooks/cfat/package.json +38 -0
- runbooks/cfat/report.py +496 -0
- runbooks/cfat/reporting/__init__.py +46 -0
- runbooks/cfat/reporting/exporters.py +337 -0
- runbooks/cfat/reporting/formatters.py +496 -0
- runbooks/cfat/reporting/templates.py +135 -0
- runbooks/cfat/run-assessment.sh +23 -0
- runbooks/cfat/runner.py +69 -0
- runbooks/cfat/src/actions/check-cloudtrail-existence.ts +43 -0
- runbooks/cfat/src/actions/check-config-existence.ts +37 -0
- runbooks/cfat/src/actions/check-control-tower.ts +37 -0
- runbooks/cfat/src/actions/check-ec2-existence.ts +46 -0
- runbooks/cfat/src/actions/check-iam-users.ts +50 -0
- runbooks/cfat/src/actions/check-legacy-cur.ts +30 -0
- runbooks/cfat/src/actions/check-org-cloudformation.ts +30 -0
- runbooks/cfat/src/actions/check-vpc-existence.ts +43 -0
- runbooks/cfat/src/actions/create-asanaimport.ts +14 -0
- runbooks/cfat/src/actions/create-backlog.ts +372 -0
- runbooks/cfat/src/actions/create-jiraimport.ts +15 -0
- runbooks/cfat/src/actions/create-report.ts +616 -0
- runbooks/cfat/src/actions/define-account-type.ts +51 -0
- runbooks/cfat/src/actions/get-enabled-org-policy-types.ts +40 -0
- runbooks/cfat/src/actions/get-enabled-org-services.ts +26 -0
- runbooks/cfat/src/actions/get-idc-info.ts +34 -0
- runbooks/cfat/src/actions/get-org-da-accounts.ts +34 -0
- runbooks/cfat/src/actions/get-org-details.ts +35 -0
- runbooks/cfat/src/actions/get-org-member-accounts.ts +44 -0
- runbooks/cfat/src/actions/get-org-ous.ts +35 -0
- runbooks/cfat/src/actions/get-regions.ts +22 -0
- runbooks/cfat/src/actions/zip-assessment.ts +27 -0
- runbooks/cfat/src/types/index.d.ts +147 -0
- runbooks/cfat/tests/__init__.py +141 -0
- runbooks/cfat/tests/test_cli.py +340 -0
- runbooks/cfat/tests/test_integration.py +290 -0
- runbooks/cfat/tests/test_models.py +505 -0
- runbooks/cfat/tests/test_reporting.py +354 -0
- runbooks/cfat/tsconfig.json +16 -0
- runbooks/cfat/webpack.config.cjs +27 -0
- runbooks/config.py +260 -0
- runbooks/finops/__init__.py +88 -0
- runbooks/finops/aws_client.py +245 -0
- runbooks/finops/cli.py +151 -0
- runbooks/finops/cost_processor.py +410 -0
- runbooks/finops/dashboard_runner.py +448 -0
- runbooks/finops/helpers.py +355 -0
- runbooks/finops/main.py +14 -0
- runbooks/finops/profile_processor.py +174 -0
- runbooks/finops/types.py +66 -0
- runbooks/finops/visualisations.py +80 -0
- runbooks/inventory/.gitignore +354 -0
- runbooks/inventory/ArgumentsClass.py +261 -0
- runbooks/inventory/Inventory_Modules.py +6130 -0
- runbooks/inventory/LandingZone/delete_lz.py +1075 -0
- runbooks/inventory/README.md +1320 -0
- runbooks/inventory/__init__.py +62 -0
- runbooks/inventory/account_class.py +532 -0
- runbooks/inventory/all_my_instances_wrapper.py +123 -0
- runbooks/inventory/aws_decorators.py +201 -0
- runbooks/inventory/cfn_move_stack_instances.py +1526 -0
- runbooks/inventory/check_cloudtrail_compliance.py +614 -0
- runbooks/inventory/check_controltower_readiness.py +1107 -0
- runbooks/inventory/check_landingzone_readiness.py +711 -0
- runbooks/inventory/cloudtrail.md +727 -0
- runbooks/inventory/collectors/__init__.py +20 -0
- runbooks/inventory/collectors/aws_compute.py +518 -0
- runbooks/inventory/collectors/aws_networking.py +275 -0
- runbooks/inventory/collectors/base.py +222 -0
- runbooks/inventory/core/__init__.py +19 -0
- runbooks/inventory/core/collector.py +303 -0
- runbooks/inventory/core/formatter.py +296 -0
- runbooks/inventory/delete_s3_buckets_objects.py +169 -0
- runbooks/inventory/discovery.md +81 -0
- runbooks/inventory/draw_org_structure.py +748 -0
- runbooks/inventory/ec2_vpc_utils.py +341 -0
- runbooks/inventory/find_cfn_drift_detection.py +272 -0
- runbooks/inventory/find_cfn_orphaned_stacks.py +719 -0
- runbooks/inventory/find_cfn_stackset_drift.py +733 -0
- runbooks/inventory/find_ec2_security_groups.py +669 -0
- runbooks/inventory/find_landingzone_versions.py +201 -0
- runbooks/inventory/find_vpc_flow_logs.py +1221 -0
- runbooks/inventory/inventory.sh +659 -0
- runbooks/inventory/list_cfn_stacks.py +558 -0
- runbooks/inventory/list_cfn_stackset_operation_results.py +252 -0
- runbooks/inventory/list_cfn_stackset_operations.py +734 -0
- runbooks/inventory/list_cfn_stacksets.py +453 -0
- runbooks/inventory/list_config_recorders_delivery_channels.py +681 -0
- runbooks/inventory/list_ds_directories.py +354 -0
- runbooks/inventory/list_ec2_availability_zones.py +286 -0
- runbooks/inventory/list_ec2_ebs_volumes.py +244 -0
- runbooks/inventory/list_ec2_instances.py +425 -0
- runbooks/inventory/list_ecs_clusters_and_tasks.py +562 -0
- runbooks/inventory/list_elbs_load_balancers.py +411 -0
- runbooks/inventory/list_enis_network_interfaces.py +526 -0
- runbooks/inventory/list_guardduty_detectors.py +568 -0
- runbooks/inventory/list_iam_policies.py +404 -0
- runbooks/inventory/list_iam_roles.py +518 -0
- runbooks/inventory/list_iam_saml_providers.py +359 -0
- runbooks/inventory/list_lambda_functions.py +882 -0
- runbooks/inventory/list_org_accounts.py +446 -0
- runbooks/inventory/list_org_accounts_users.py +354 -0
- runbooks/inventory/list_rds_db_instances.py +406 -0
- runbooks/inventory/list_route53_hosted_zones.py +318 -0
- runbooks/inventory/list_servicecatalog_provisioned_products.py +575 -0
- runbooks/inventory/list_sns_topics.py +360 -0
- runbooks/inventory/list_ssm_parameters.py +402 -0
- runbooks/inventory/list_vpc_subnets.py +433 -0
- runbooks/inventory/list_vpcs.py +422 -0
- runbooks/inventory/lockdown_cfn_stackset_role.py +224 -0
- runbooks/inventory/models/__init__.py +24 -0
- runbooks/inventory/models/account.py +192 -0
- runbooks/inventory/models/inventory.py +309 -0
- runbooks/inventory/models/resource.py +247 -0
- runbooks/inventory/recover_cfn_stack_ids.py +205 -0
- runbooks/inventory/requirements.txt +12 -0
- runbooks/inventory/run_on_multi_accounts.py +211 -0
- runbooks/inventory/tests/common_test_data.py +3661 -0
- runbooks/inventory/tests/common_test_functions.py +204 -0
- runbooks/inventory/tests/script_test_data.py +0 -0
- runbooks/inventory/tests/setup.py +24 -0
- runbooks/inventory/tests/src.py +18 -0
- runbooks/inventory/tests/test_cfn_describe_stacks.py +208 -0
- runbooks/inventory/tests/test_ec2_describe_instances.py +162 -0
- runbooks/inventory/tests/test_inventory_modules.py +55 -0
- runbooks/inventory/tests/test_lambda_list_functions.py +86 -0
- runbooks/inventory/tests/test_moto_integration_example.py +273 -0
- runbooks/inventory/tests/test_org_list_accounts.py +49 -0
- runbooks/inventory/update_aws_actions.py +173 -0
- runbooks/inventory/update_cfn_stacksets.py +1215 -0
- runbooks/inventory/update_cloudwatch_logs_retention_policy.py +294 -0
- runbooks/inventory/update_iam_roles_cross_accounts.py +478 -0
- runbooks/inventory/update_s3_public_access_block.py +539 -0
- runbooks/inventory/utils/__init__.py +23 -0
- runbooks/inventory/utils/aws_helpers.py +510 -0
- runbooks/inventory/utils/threading_utils.py +493 -0
- runbooks/inventory/utils/validation.py +682 -0
- runbooks/inventory/verify_ec2_security_groups.py +1430 -0
- runbooks/main.py +785 -0
- runbooks/organizations/__init__.py +12 -0
- runbooks/organizations/manager.py +374 -0
- runbooks/security_baseline/README.md +324 -0
- runbooks/security_baseline/checklist/alternate_contacts.py +8 -1
- runbooks/security_baseline/checklist/bucket_public_access.py +4 -1
- runbooks/security_baseline/checklist/cloudwatch_alarm_configuration.py +9 -2
- runbooks/security_baseline/checklist/guardduty_enabled.py +9 -2
- runbooks/security_baseline/checklist/multi_region_instance_usage.py +5 -1
- runbooks/security_baseline/checklist/root_access_key.py +6 -1
- runbooks/security_baseline/config-origin.json +1 -1
- runbooks/security_baseline/config.json +1 -1
- runbooks/security_baseline/permission.json +1 -1
- runbooks/security_baseline/report_generator.py +10 -2
- runbooks/security_baseline/report_template_en.html +8 -8
- runbooks/security_baseline/report_template_jp.html +8 -8
- runbooks/security_baseline/report_template_kr.html +13 -13
- runbooks/security_baseline/report_template_vn.html +8 -8
- runbooks/security_baseline/requirements.txt +7 -0
- runbooks/security_baseline/run_script.py +8 -2
- runbooks/security_baseline/security_baseline_tester.py +10 -2
- runbooks/security_baseline/utils/common.py +5 -1
- runbooks/utils/__init__.py +204 -0
- runbooks-0.6.1.dist-info/METADATA +373 -0
- runbooks-0.6.1.dist-info/RECORD +237 -0
- {runbooks-0.2.3.dist-info → runbooks-0.6.1.dist-info}/WHEEL +1 -1
- runbooks-0.6.1.dist-info/entry_points.txt +7 -0
- runbooks-0.6.1.dist-info/licenses/LICENSE +201 -0
- runbooks-0.6.1.dist-info/top_level.txt +3 -0
- runbooks/python101/calculator.py +0 -34
- runbooks/python101/config.py +0 -1
- runbooks/python101/exceptions.py +0 -16
- runbooks/python101/file_manager.py +0 -218
- runbooks/python101/toolkit.py +0 -153
- runbooks-0.2.3.dist-info/METADATA +0 -435
- runbooks-0.2.3.dist-info/RECORD +0 -61
- runbooks-0.2.3.dist-info/entry_points.txt +0 -3
- runbooks-0.2.3.dist-info/top_level.txt +0 -1
@@ -0,0 +1,558 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
AWS CloudFormation Stack Inventory Collection
|
4
|
+
|
5
|
+
A comprehensive CloudFormation stack discovery and management tool for multi-account
|
6
|
+
AWS Organizations. Provides detailed stack inventory with advanced filtering capabilities
|
7
|
+
and optional stack deletion functionality.
|
8
|
+
|
9
|
+
**AWS API Mapping**: `cloudformation.describe_stacks()`, `cloudformation.list_stacks()`
|
10
|
+
|
11
|
+
Features:
|
12
|
+
- Multi-account CloudFormation stack discovery
|
13
|
+
- Advanced status filtering (CREATE_COMPLETE, UPDATE_FAILED, etc.)
|
14
|
+
- Stack fragment matching for partial name searches
|
15
|
+
- Stack ID display and metadata collection
|
16
|
+
- Conditional stack deletion with safety controls
|
17
|
+
- Cross-region stack inventory aggregation
|
18
|
+
- Specialized GuardDuty stack handling
|
19
|
+
|
20
|
+
Compatibility:
|
21
|
+
- AWS Organizations with cross-account roles
|
22
|
+
- AWS Control Tower managed accounts
|
23
|
+
- Standalone AWS accounts
|
24
|
+
- All AWS regions including opt-in regions
|
25
|
+
|
26
|
+
Example:
|
27
|
+
Discover all active stacks:
|
28
|
+
```bash
|
29
|
+
python cfn_describe_stacks.py --profile my-org-profile
|
30
|
+
```
|
31
|
+
|
32
|
+
Find stacks by name fragment:
|
33
|
+
```bash
|
34
|
+
python cfn_describe_stacks.py --profile my-profile \\
|
35
|
+
--fragment my-app --status CREATE_COMPLETE
|
36
|
+
```
|
37
|
+
|
38
|
+
Display stack IDs for troubleshooting:
|
39
|
+
```bash
|
40
|
+
python cfn_describe_stacks.py --profile my-profile \\
|
41
|
+
--stackid --fragment problematic-stack
|
42
|
+
```
|
43
|
+
|
44
|
+
Warning:
|
45
|
+
The `+delete` parameter will **DELETE** matching stacks WITHOUT additional
|
46
|
+
confirmation. Use with extreme caution in production environments.
|
47
|
+
|
48
|
+
Requirements:
|
49
|
+
- IAM permissions: `cloudformation:DescribeStacks`, `cloudformation:DeleteStack`
|
50
|
+
- AWS Organizations access (for multi-account scanning)
|
51
|
+
- Python 3.8+ with required dependencies
|
52
|
+
|
53
|
+
Author:
|
54
|
+
AWS Cloud Foundations Team
|
55
|
+
|
56
|
+
Version:
|
57
|
+
2024.05.31
|
58
|
+
"""
|
59
|
+
|
60
|
+
import logging
|
61
|
+
import sys
|
62
|
+
from os.path import split
|
63
|
+
from pprint import pprint
|
64
|
+
from time import time
|
65
|
+
|
66
|
+
import Inventory_Modules
|
67
|
+
from account_class import aws_acct_access
|
68
|
+
from ArgumentsClass import CommonArguments
|
69
|
+
from botocore.exceptions import ClientError
|
70
|
+
from colorama import Fore, init
|
71
|
+
from Inventory_Modules import display_results, get_all_credentials
|
72
|
+
|
73
|
+
init()
|
74
|
+
|
75
|
+
__version__ = "2024.05.31"
|
76
|
+
|
77
|
+
###########################
|
78
|
+
|
79
|
+
|
80
|
+
def parse_args(f_arguments):
|
81
|
+
"""
|
82
|
+
Parse and validate command-line arguments for CloudFormation stack inventory.
|
83
|
+
|
84
|
+
Configures the argument parser with CloudFormation-specific options including
|
85
|
+
status filtering, stack ID display, and optional deletion capabilities.
|
86
|
+
Uses the standardized CommonArguments framework for consistency.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
f_arguments (list): Command-line arguments to parse (typically sys.argv[1:])
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
argparse.Namespace: Parsed arguments containing:
|
93
|
+
- Profile: AWS profile for authentication
|
94
|
+
- Regions: Target AWS regions for stack discovery
|
95
|
+
- Fragments: Stack name fragments for filtering
|
96
|
+
- status: CloudFormation stack status filters
|
97
|
+
- stackid: Flag to display full stack ARNs
|
98
|
+
- DeletionRun: DANGEROUS flag to delete matched stacks
|
99
|
+
- Other standard framework arguments
|
100
|
+
|
101
|
+
Note:
|
102
|
+
The DeletionRun parameter enables destructive operations.
|
103
|
+
Use with extreme caution in production environments.
|
104
|
+
"""
|
105
|
+
script_path, script_name = split(sys.argv[0])
|
106
|
+
parser = CommonArguments()
|
107
|
+
parser.singleprofile() # Allows for a single profile to be specified
|
108
|
+
parser.multiregion() # Allows for multiple regions to be specified at the command line
|
109
|
+
parser.extendedargs() # Allows for extended arguments like which accounts to skip, and whether Force is enabled.
|
110
|
+
parser.rootOnly()
|
111
|
+
# TODO: Add parameter for access_roles
|
112
|
+
# parser.rolestouse() # Allows for the roles to be specified at the command line.
|
113
|
+
parser.fragment()
|
114
|
+
parser.timing()
|
115
|
+
parser.verbosity() # Allows for the verbosity to be handled.
|
116
|
+
parser.version(__version__)
|
117
|
+
local = parser.my_parser.add_argument_group(script_name, "Parameters specific to this script")
|
118
|
+
local.add_argument(
|
119
|
+
"-s",
|
120
|
+
"--status",
|
121
|
+
dest="status",
|
122
|
+
nargs="*",
|
123
|
+
choices=[
|
124
|
+
"CREATE_IN_PROGRESS",
|
125
|
+
"CREATE_FAILED",
|
126
|
+
"CREATE_COMPLETE",
|
127
|
+
"ROLLBACK_IN_PROGRESS",
|
128
|
+
"ROLLBACK_FAILED",
|
129
|
+
"ROLLBACK_COMPLETE",
|
130
|
+
"DELETE_IN_PROGRESS",
|
131
|
+
"DELETE_FAILED",
|
132
|
+
"DELETE_COMPLETE",
|
133
|
+
"UPDATE_IN_PROGRESS",
|
134
|
+
"UPDATE_COMPLETE_CLEANUP_IN_PROGRESS",
|
135
|
+
"UPDATE_COMPLETE",
|
136
|
+
"UPDATE_FAILED",
|
137
|
+
"UPDATE_ROLLBACK_IN_PROGRESS",
|
138
|
+
"UPDATE_ROLLBACK_FAILED",
|
139
|
+
"UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS",
|
140
|
+
"UPDATE_ROLLBACK_COMPLETE",
|
141
|
+
"REVIEW_IN_PROGRESS",
|
142
|
+
"IMPORT_IN_PROGRESS",
|
143
|
+
"IMPORT_COMPLETE",
|
144
|
+
"IMPORT_ROLLBACK_IN_PROGRESS",
|
145
|
+
"IMPORT_ROLLBACK_FAILED",
|
146
|
+
"IMPORT_ROLLBACK_COMPLETE",
|
147
|
+
"all",
|
148
|
+
"All",
|
149
|
+
"ALL",
|
150
|
+
],
|
151
|
+
metavar="CloudFormation status",
|
152
|
+
default=None,
|
153
|
+
help="List of statuses that determines which statuses we see. Default is all ACTIVE statuses. 'All' will capture all statuses",
|
154
|
+
)
|
155
|
+
local.add_argument(
|
156
|
+
"--stackid",
|
157
|
+
dest="stackid",
|
158
|
+
action="store_true",
|
159
|
+
help="Flag that determines whether we display the Stack IDs as well",
|
160
|
+
)
|
161
|
+
local.add_argument(
|
162
|
+
"+delete",
|
163
|
+
"+forreal",
|
164
|
+
dest="DeletionRun",
|
165
|
+
action="store_true",
|
166
|
+
help="This will delete the stacks found - without any opportunity to confirm. Be careful!!",
|
167
|
+
)
|
168
|
+
return parser.my_parser.parse_args(f_arguments)
|
169
|
+
|
170
|
+
|
171
|
+
def setup_auth_accounts_and_regions(
|
172
|
+
fProfile: str,
|
173
|
+
fRegionList: list = None,
|
174
|
+
fAccountList: list = None,
|
175
|
+
fSkipAccounts: list = None,
|
176
|
+
fStackFrag: list = None,
|
177
|
+
fExact: bool = False,
|
178
|
+
fDeletionRun: bool = False,
|
179
|
+
) -> (aws_acct_access, list, list):
|
180
|
+
"""
|
181
|
+
Initialize AWS account access and determine target accounts/regions for stack discovery.
|
182
|
+
|
183
|
+
This function establishes the AWS Organizations context, resolves account access
|
184
|
+
permissions, and prepares the execution scope for CloudFormation stack operations.
|
185
|
+
Includes safety warnings and user confirmation for destructive operations.
|
186
|
+
|
187
|
+
Args:
|
188
|
+
fProfile (str): AWS profile name for authentication and organization access
|
189
|
+
fRegionList (list, optional): Target regions for stack discovery.
|
190
|
+
Defaults to all accessible regions if None.
|
191
|
+
fAccountList (list, optional): Specific account IDs to include.
|
192
|
+
Defaults to all organization accounts if None.
|
193
|
+
fSkipAccounts (list, optional): Account IDs to exclude from discovery
|
194
|
+
fStackFrag (list, optional): Stack name fragments for filtering operations
|
195
|
+
fExact (bool, optional): Use exact matching for stack fragments.
|
196
|
+
Defaults to False (partial matching).
|
197
|
+
fDeletionRun (bool, optional): Flag indicating destructive operations.
|
198
|
+
Triggers additional warnings and confirmations.
|
199
|
+
|
200
|
+
Returns:
|
201
|
+
tuple: Three-element tuple containing:
|
202
|
+
- aws_acct_access: Authenticated AWS account access object
|
203
|
+
- list: Resolved list of target account IDs
|
204
|
+
- list: Resolved list of target AWS regions
|
205
|
+
|
206
|
+
Raises:
|
207
|
+
ConnectionError: When AWS profile authentication fails
|
208
|
+
|
209
|
+
Side Effects:
|
210
|
+
- Displays execution parameters and warnings to stdout
|
211
|
+
- Shows colored output for operation confirmation
|
212
|
+
- Exits process (sys.exit(8)) on authentication failure
|
213
|
+
|
214
|
+
Security Note:
|
215
|
+
When fDeletionRun=True, displays prominent warnings about
|
216
|
+
destructive operations to prevent accidental stack deletion.
|
217
|
+
"""
|
218
|
+
try:
|
219
|
+
aws_acct = aws_acct_access(fProfile)
|
220
|
+
except ConnectionError as my_Error:
|
221
|
+
logging.error(f"Exiting due to error: {my_Error}")
|
222
|
+
sys.exit(8)
|
223
|
+
|
224
|
+
ChildAccounts = aws_acct.ChildAccounts
|
225
|
+
RegionList = Inventory_Modules.get_regions3(aws_acct, fRegionList)
|
226
|
+
|
227
|
+
ChildAccounts = Inventory_Modules.RemoveCoreAccounts(ChildAccounts, fSkipAccounts)
|
228
|
+
if fAccountList is None:
|
229
|
+
AccountList = [account["AccountId"] for account in ChildAccounts]
|
230
|
+
else:
|
231
|
+
AccountList = [account["AccountId"] for account in ChildAccounts if account["AccountId"] in fAccountList]
|
232
|
+
|
233
|
+
print(f"You asked to find stacks with this fragment {Fore.RED}'{fStackFrag}'{Fore.RESET}")
|
234
|
+
print(f"in these accounts:\n{Fore.RED}{AccountList}{Fore.RESET}")
|
235
|
+
print(f"in these regions:\n{Fore.RED}{RegionList}{Fore.RESET}")
|
236
|
+
print(f"While skipping these accounts:\n{Fore.RED}{fSkipAccounts}{Fore.RESET}") if fSkipAccounts is not None else ""
|
237
|
+
if fDeletionRun:
|
238
|
+
print()
|
239
|
+
print("And delete the stacks that are found...")
|
240
|
+
|
241
|
+
if fExact:
|
242
|
+
print(f"\t\tFor stacks that {Fore.RED}exactly match{Fore.RESET} these fragments: {fStackFrag}")
|
243
|
+
else:
|
244
|
+
print(f"\t\tFor stacks that contains these fragments: {fStackFrag}")
|
245
|
+
|
246
|
+
return aws_acct, AccountList, RegionList
|
247
|
+
|
248
|
+
|
249
|
+
def collect_cfnstacks(fCredentialList: list) -> list:
|
250
|
+
"""
|
251
|
+
Execute multi-threaded CloudFormation stack discovery across AWS accounts and regions.
|
252
|
+
|
253
|
+
This is the core discovery engine that performs concurrent CloudFormation API calls
|
254
|
+
across all provided credentials. Uses a worker thread pool pattern for optimal
|
255
|
+
performance while managing API rate limits and error handling.
|
256
|
+
|
257
|
+
Args:
|
258
|
+
fCredentialList (list): List of credential dictionaries containing:
|
259
|
+
- AccountId: AWS account identifier
|
260
|
+
- Region: AWS region name
|
261
|
+
- AccessKeyId, SecretAccessKey, SessionToken: AWS credentials
|
262
|
+
- Success: Boolean flag indicating credential validation status
|
263
|
+
|
264
|
+
Returns:
|
265
|
+
list: Sorted list of CloudFormation stack dictionaries with metadata:
|
266
|
+
- Account: AWS account ID where stack exists
|
267
|
+
- Region: AWS region of the stack
|
268
|
+
- StackName: CloudFormation stack name
|
269
|
+
- StackStatus: Current stack status (CREATE_COMPLETE, etc.)
|
270
|
+
- StackCreate: Stack creation date (formatted YYYY-MM-DD)
|
271
|
+
- StackArn: Full stack ARN (if stackid flag enabled)
|
272
|
+
- Credential fields for potential stack operations
|
273
|
+
|
274
|
+
Threading Architecture:
|
275
|
+
- Uses Queue for thread-safe work distribution
|
276
|
+
- 4 concurrent worker threads for balanced throughput
|
277
|
+
- Real-time progress display with colored output
|
278
|
+
- Graceful handling of authentication failures
|
279
|
+
|
280
|
+
Error Handling:
|
281
|
+
- AuthFailure: Logs and continues with other accounts
|
282
|
+
- ClientError: Handles AWS API throttling and service errors
|
283
|
+
- Credential validation via Success flag checking
|
284
|
+
|
285
|
+
Performance:
|
286
|
+
Results are automatically sorted by Account -> Region -> StackName
|
287
|
+
for consistent output presentation across executions.
|
288
|
+
"""
|
289
|
+
from queue import Queue
|
290
|
+
from threading import Thread
|
291
|
+
|
292
|
+
StacksFound = []
|
293
|
+
|
294
|
+
def worker(q, pStackfrag, pstatus):
|
295
|
+
"""
|
296
|
+
Worker thread function for concurrent CloudFormation stack discovery.
|
297
|
+
|
298
|
+
Each worker processes credentials from the shared queue, calls the
|
299
|
+
CloudFormation API to discover stacks, and aggregates results.
|
300
|
+
Implements comprehensive error handling and real-time progress display.
|
301
|
+
|
302
|
+
Args:
|
303
|
+
q (Queue): Thread-safe queue containing credential work items
|
304
|
+
pStackfrag (list): Stack name fragments for filtering
|
305
|
+
pstatus (list): Stack status filters to apply
|
306
|
+
|
307
|
+
Side Effects:
|
308
|
+
- Updates shared StacksFound list with discoveries
|
309
|
+
- Displays real-time progress to stdout
|
310
|
+
- Logs detailed account/region processing information
|
311
|
+
"""
|
312
|
+
while True:
|
313
|
+
# Get work item from thread-safe queue
|
314
|
+
credential = q.get()
|
315
|
+
if credential is None:
|
316
|
+
break # Shutdown signal received
|
317
|
+
|
318
|
+
Stacks = False
|
319
|
+
|
320
|
+
# Only process credentials that passed validation
|
321
|
+
if credential["Success"]:
|
322
|
+
try:
|
323
|
+
# Call CloudFormation API to discover stacks
|
324
|
+
Stacks = Inventory_Modules.find_stacks2(credential, credential["Region"], pStackfrag, pstatus)
|
325
|
+
|
326
|
+
# Log discovery results for debugging
|
327
|
+
logging.warning(
|
328
|
+
f"Account: {credential['AccountId']} | Region: {credential['Region']} | Found {len(Stacks)} Stacks"
|
329
|
+
)
|
330
|
+
|
331
|
+
# Display real-time progress with colored output
|
332
|
+
print(
|
333
|
+
f"{ERASE_LINE}{Fore.RED}Account: {credential['AccountId']} Region: {credential['Region']} Found {len(Stacks)} Stacks{Fore.RESET}",
|
334
|
+
end="\r",
|
335
|
+
)
|
336
|
+
|
337
|
+
except ClientError as my_Error:
|
338
|
+
# Handle AWS authentication and authorization errors
|
339
|
+
if "AuthFailure" in str(my_Error):
|
340
|
+
print(f"{credential['AccountId']}: Authorization Failure")
|
341
|
+
else:
|
342
|
+
# Skip credentials that failed validation
|
343
|
+
continue
|
344
|
+
|
345
|
+
# Process discovered stacks and aggregate results
|
346
|
+
if Stacks and len(Stacks) > 0:
|
347
|
+
for y in range(len(Stacks)):
|
348
|
+
# Extract stack metadata from AWS API response
|
349
|
+
StackName = Stacks[y]["StackName"]
|
350
|
+
StackStatus = Stacks[y]["StackStatus"]
|
351
|
+
StackID = Stacks[y]["StackId"]
|
352
|
+
StackCreate = Stacks[y]["CreationTime"]
|
353
|
+
|
354
|
+
# Create standardized stack record for aggregation
|
355
|
+
# Includes both metadata and credentials for potential operations
|
356
|
+
StacksFound.append(
|
357
|
+
{
|
358
|
+
# Identity and location information
|
359
|
+
"Account": credential["AccountId"],
|
360
|
+
"Region": credential["Region"],
|
361
|
+
# AWS credentials for stack operations (deletion, etc.)
|
362
|
+
"AccessKeyId": credential["AccessKeyId"],
|
363
|
+
"SecretAccessKey": credential["SecretAccessKey"],
|
364
|
+
"SessionToken": credential["SessionToken"],
|
365
|
+
"AccountNumber": credential["AccountNumber"],
|
366
|
+
# CloudFormation stack metadata
|
367
|
+
"StackName": StackName,
|
368
|
+
"StackCreate": StackCreate.strftime("%Y-%m-%d"),
|
369
|
+
"StackStatus": StackStatus,
|
370
|
+
# Conditional stack ARN display (based on CLI flag)
|
371
|
+
"StackArn": StackID if pStackIdFlag else "None",
|
372
|
+
}
|
373
|
+
)
|
374
|
+
|
375
|
+
# Mark work item as complete for queue management
|
376
|
+
q.task_done()
|
377
|
+
|
378
|
+
stacks_queue = Queue()
|
379
|
+
for credential in fCredentialList:
|
380
|
+
stacks_queue.put(credential)
|
381
|
+
|
382
|
+
num_threads = 4 # Number of worker threads
|
383
|
+
for i in range(num_threads):
|
384
|
+
t = Thread(target=worker, args=(stacks_queue, pStackfrag, pstatus))
|
385
|
+
t.start()
|
386
|
+
|
387
|
+
stacks_queue.join()
|
388
|
+
|
389
|
+
for i in range(num_threads):
|
390
|
+
stacks_queue.put(None)
|
391
|
+
|
392
|
+
sortedStacksFound = sorted(StacksFound, key=lambda x: (x["Account"], x["Region"], x["StackName"]))
|
393
|
+
return sortedStacksFound
|
394
|
+
|
395
|
+
|
396
|
+
def display_stacks(fAllStacks: list):
|
397
|
+
display_dict = {
|
398
|
+
"Account": {"DisplayOrder": 1, "Heading": "Account"},
|
399
|
+
"Region": {"DisplayOrder": 2, "Heading": "Region"},
|
400
|
+
"StackStatus": {"DisplayOrder": 3, "Heading": "Stack Status"},
|
401
|
+
"StackCreate": {"DisplayOrder": 4, "Heading": "Create Date"},
|
402
|
+
"StackName": {"DisplayOrder": 5, "Heading": "Stack Name"},
|
403
|
+
"StackArn": {"DisplayOrder": 6, "Heading": "Stack ID"},
|
404
|
+
}
|
405
|
+
|
406
|
+
display_results(
|
407
|
+
fAllStacks,
|
408
|
+
display_dict,
|
409
|
+
None,
|
410
|
+
)
|
411
|
+
print(ERASE_LINE)
|
412
|
+
print(
|
413
|
+
f"{Fore.RED}Found {len(fAllStacks)} stacks across {len(AccountList)} accounts across {len(RegionList)} regions{Fore.RESET}"
|
414
|
+
)
|
415
|
+
print()
|
416
|
+
if args.loglevel < 21: # INFO level
|
417
|
+
lAccounts = lRegions = lAccountsAndRegions = []
|
418
|
+
for i in range(len(fAllStacks)):
|
419
|
+
lAccounts.append(fAllStacks[i]["Account"])
|
420
|
+
lRegions.append(fAllStacks[i]["Region"])
|
421
|
+
lAccountsAndRegions.append((fAllStacks[i]["Account"], fAllStacks[i]["Region"]))
|
422
|
+
print("The list of accounts and regions:")
|
423
|
+
pprint(list(sorted(set(lAccountsAndRegions))))
|
424
|
+
|
425
|
+
|
426
|
+
def modify_stacks(fStacksFound: list):
|
427
|
+
"""
|
428
|
+
Execute CloudFormation stack deletion operations with specialized error handling.
|
429
|
+
|
430
|
+
This function implements the destructive stack deletion workflow with multiple
|
431
|
+
safety confirmations and specialized handling for problematic stack types like
|
432
|
+
GuardDuty stacks that may have resource retention requirements.
|
433
|
+
|
434
|
+
Args:
|
435
|
+
fStacksFound (list): List of stack dictionaries to delete, each containing:
|
436
|
+
- StackName: CloudFormation stack identifier
|
437
|
+
- Account: AWS account ID
|
438
|
+
- Region: AWS region
|
439
|
+
- StackStatus: Current stack status
|
440
|
+
- Credential information for API calls
|
441
|
+
|
442
|
+
Returns:
|
443
|
+
list: Collection of AWS API responses from deletion operations.
|
444
|
+
May contain success confirmations or error details.
|
445
|
+
|
446
|
+
Safety Features:
|
447
|
+
- Interactive confirmation prompt before execution
|
448
|
+
- Special handling for DELETE_FAILED stacks
|
449
|
+
- GuardDuty-specific resource retention logic
|
450
|
+
- Progress display during batch deletions
|
451
|
+
|
452
|
+
Special Cases:
|
453
|
+
- GuardDuty stacks: Uses RetainResources=['MasterDetector'] due to
|
454
|
+
common deletion failures with GuardDuty master detectors
|
455
|
+
- DELETE_FAILED stacks: Automatically retains problematic resources
|
456
|
+
that may be blocking normal deletion
|
457
|
+
|
458
|
+
Warning:
|
459
|
+
This function performs IRREVERSIBLE CloudFormation stack deletions.
|
460
|
+
Ensure proper backups and confirmations before execution.
|
461
|
+
|
462
|
+
Example:
|
463
|
+
>>> stacks_to_delete = [{'StackName': 'test-stack', 'Account': '123456789'}]
|
464
|
+
>>> responses = modify_stacks(stacks_to_delete)
|
465
|
+
>>> print(f"Deleted {len(responses)} stacks")
|
466
|
+
"""
|
467
|
+
ReallyDelete = (
|
468
|
+
(input(f"Deletion of stacks has been requested, are you still sure? (y/n): ") in ["y", "Y"])
|
469
|
+
if DeletionRun
|
470
|
+
else False
|
471
|
+
)
|
472
|
+
response2 = []
|
473
|
+
if DeletionRun and ReallyDelete and ("GuardDuty" in pStackfrag or "guardduty" in pStackfrag):
|
474
|
+
logging.warning(f"Deleting {len(fStacksFound)} stacks")
|
475
|
+
for stack_found in fStacksFound:
|
476
|
+
print(
|
477
|
+
f"Deleting stack {stack_found['StackName']} from Account {stack_found['Account']} in region {stack_found['Region']}"
|
478
|
+
)
|
479
|
+
if stack_found["StackStatus"] == "DELETE_FAILED":
|
480
|
+
# This deletion generally fails because the Master Detector doesn't properly delete (and it's usually already deleted due to some other script) - so we just need to delete the stack anyway - and ignore the actual resource.
|
481
|
+
response = Inventory_Modules.delete_stack2(
|
482
|
+
stack_found,
|
483
|
+
stack_found["Region"],
|
484
|
+
stack_found["StackName"],
|
485
|
+
RetainResources=True,
|
486
|
+
ResourcesToRetain=["MasterDetector"],
|
487
|
+
)
|
488
|
+
else:
|
489
|
+
response = Inventory_Modules.delete_stack2(stack_found, stack_found["Region"], stack_found["StackName"])
|
490
|
+
response2.append(response)
|
491
|
+
elif DeletionRun and ReallyDelete:
|
492
|
+
logging.warning(f"Deleting {len(fStacksFound)} stacks")
|
493
|
+
for stack_found in fStacksFound:
|
494
|
+
print(
|
495
|
+
f"Deleting stack {stack_found['StackName']} from account {stack_found['Account']} in region {stack_found['Region']} with status: {stack_found['StackStatus']}"
|
496
|
+
)
|
497
|
+
# print(f"Finished {y + 1} of {len(fStacksFound)}")
|
498
|
+
response = Inventory_Modules.delete_stack2(stack_found, stack_found["Region"], stack_found["StackName"])
|
499
|
+
response2.append(response)
|
500
|
+
# pprint(response)
|
501
|
+
return response2
|
502
|
+
|
503
|
+
|
504
|
+
###########################
|
505
|
+
|
506
|
+
if __name__ == "__main__":
|
507
|
+
args = parse_args(sys.argv[1:])
|
508
|
+
|
509
|
+
pProfile = args.Profile
|
510
|
+
pRegionList = args.Regions
|
511
|
+
pStackfrag = args.Fragments
|
512
|
+
pExact = args.Exact
|
513
|
+
pTiming = args.Time
|
514
|
+
verbose = args.loglevel
|
515
|
+
pSkipProfiles = args.SkipProfiles
|
516
|
+
pSkipAccounts = args.SkipAccounts
|
517
|
+
pRootOnly = args.RootOnly
|
518
|
+
pAccountList = args.Accounts
|
519
|
+
pstatus = args.status
|
520
|
+
pStackIdFlag = args.stackid
|
521
|
+
DeletionRun = args.DeletionRun
|
522
|
+
# Setup logging levels
|
523
|
+
logging.basicConfig(level=verbose, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
|
524
|
+
logging.getLogger("boto3").setLevel(logging.CRITICAL)
|
525
|
+
logging.getLogger("botocore").setLevel(logging.CRITICAL)
|
526
|
+
logging.getLogger("s3transfer").setLevel(logging.CRITICAL)
|
527
|
+
logging.getLogger("urllib3").setLevel(logging.CRITICAL)
|
528
|
+
logging.getLogger("botocore").setLevel(logging.CRITICAL)
|
529
|
+
|
530
|
+
begin_time = time()
|
531
|
+
|
532
|
+
##########################
|
533
|
+
ERASE_LINE = "\x1b[2K"
|
534
|
+
|
535
|
+
print()
|
536
|
+
# Setup the aws_acct object
|
537
|
+
aws_acct, AccountList, RegionList = setup_auth_accounts_and_regions(
|
538
|
+
pProfile, pRegionList, pAccountList, pSkipAccounts, pStackfrag, pExact, DeletionRun
|
539
|
+
)
|
540
|
+
# Get credentials for all Child Accounts
|
541
|
+
CredentialList = get_all_credentials(
|
542
|
+
pProfile, pTiming, pSkipProfiles, pSkipAccounts, pRootOnly, AccountList, RegionList
|
543
|
+
)
|
544
|
+
# Collect the stacksets, AccountList and RegionList involved
|
545
|
+
AllStacks = collect_cfnstacks(CredentialList)
|
546
|
+
# Display the information we've found this far
|
547
|
+
display_stacks(AllStacks)
|
548
|
+
# Modify stacks, if requested
|
549
|
+
if DeletionRun:
|
550
|
+
modify_result = modify_stacks(AllStacks)
|
551
|
+
|
552
|
+
if pTiming:
|
553
|
+
print(ERASE_LINE)
|
554
|
+
print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
|
555
|
+
|
556
|
+
print()
|
557
|
+
print("Thanks for using this script...")
|
558
|
+
print()
|