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,1075 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
import sys
|
3
|
+
|
4
|
+
# from botocore.errorfactory import StackSetNotFoundException
|
5
|
+
import time
|
6
|
+
|
7
|
+
import boto3
|
8
|
+
from botocore.exceptions import ClientError
|
9
|
+
|
10
|
+
"""
|
11
|
+
Script for cleanup of deployed AWS Landing Zone, as per flow from https://w.amazon.com/bin/view/AWS/Teams/SA/AWS_Solutions_Builder/Working_Backwards/AWS_Solutions-Foundations-Landing-Zone/deletion/
|
12
|
+
To use it:
|
13
|
+
1. Create a user within your Master account with Administrator Access and key pair or a session token
|
14
|
+
2. run the script
|
15
|
+
Usage: python delete_lz.py REGION AWS_ACCESS_KEY AWS_SECRET_ACCESS_KEY [AWS_SESSION_TOKEN] [debug:true]
|
16
|
+
"""
|
17
|
+
|
18
|
+
__author__ = "xOps"
|
19
|
+
__email__ = "nnthanh101@gmail.com"
|
20
|
+
__status__ = "Use at your own risk"
|
21
|
+
|
22
|
+
# make sure we are running with python 3
|
23
|
+
if sys.version_info < (3, 0):
|
24
|
+
print("Sorry, this script requires Python 3 to run")
|
25
|
+
sys.exit(1)
|
26
|
+
|
27
|
+
|
28
|
+
def wait_with_progress_bar(Message="", Seconds=30):
|
29
|
+
"""
|
30
|
+
Wait for given number of seconds, displaying provided message and moving progress bar in the meantime
|
31
|
+
"""
|
32
|
+
iteration = 0
|
33
|
+
for _ in range(Seconds):
|
34
|
+
iteration += 1
|
35
|
+
progress_string = "." * (iteration % 10)
|
36
|
+
print(f"{Message}, please wait {progress_string}", end=" ")
|
37
|
+
time.sleep(1)
|
38
|
+
|
39
|
+
|
40
|
+
def print_debug(message):
|
41
|
+
"""Print message if debug is turned on"""
|
42
|
+
if DEBUG:
|
43
|
+
print(message)
|
44
|
+
|
45
|
+
|
46
|
+
if __name__ == "__main__":
|
47
|
+
# This handles when they don't specify enough parameters - or too many.
|
48
|
+
if len(sys.argv) < 4 or (len(sys.argv) == 6 and sys.argv[5] != "debug:true"):
|
49
|
+
print("Usage: python delete_lz REGION AWS_ACCESS_KEY AWS_SECRET_ACCESS_KEY [AWS_SESSION_TOKEN] [debug:true]")
|
50
|
+
exit()
|
51
|
+
|
52
|
+
# credentials for the root org account, note that this better have the super awesome role
|
53
|
+
# if you are using Isengard or SSO, enter the access key, secret key and session token
|
54
|
+
# if you are using hardcoded IAM credentials, then the session token should be left out (not recommended)
|
55
|
+
AWS_REGION = sys.argv[1]
|
56
|
+
AWS_ACCESS_KEY = sys.argv[2]
|
57
|
+
AWS_SECRET_ACCESS_KEY = sys.argv[3]
|
58
|
+
AWS_SESSION_TOKEN = ""
|
59
|
+
AWS_SESSION_TOKEN_PASSED = False
|
60
|
+
|
61
|
+
DEBUG = False
|
62
|
+
if len(sys.argv) == 5 and sys.argv[4] == "debug:true":
|
63
|
+
DEBUG = True
|
64
|
+
print("Debugging enabled and No Session Token passed")
|
65
|
+
|
66
|
+
if len(sys.argv) == 5 and sys.argv[4] != "debug:true":
|
67
|
+
print("Debugging is NOT enabled and Session Token passed")
|
68
|
+
AWS_SESSION_TOKEN = sys.argv[4]
|
69
|
+
AWS_SESSION_TOKEN_PASSED = True
|
70
|
+
|
71
|
+
if len(sys.argv) == 6 and sys.argv[5] == "debug:true":
|
72
|
+
DEBUG = True
|
73
|
+
AWS_SESSION_TOKEN = sys.argv[4]
|
74
|
+
AWS_SESSION_TOKEN_PASSED = True
|
75
|
+
print("Debugging enabled and Session Token passed")
|
76
|
+
|
77
|
+
start_time = time.time()
|
78
|
+
SECURITY_ACCOUNT_NAME = "security"
|
79
|
+
LOGGING_ACCOUNT_NAME = "log-archive"
|
80
|
+
SHARED_SERVICES_ACCOUNT_NAME = "shared-services"
|
81
|
+
if AWS_SESSION_TOKEN_PASSED:
|
82
|
+
client = boto3.client(
|
83
|
+
"organizations",
|
84
|
+
region_name=AWS_REGION,
|
85
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
86
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
87
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
88
|
+
)
|
89
|
+
else:
|
90
|
+
client = boto3.client(
|
91
|
+
"organizations",
|
92
|
+
region_name=AWS_REGION,
|
93
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
94
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
95
|
+
)
|
96
|
+
accounts = client.list_accounts()
|
97
|
+
print("List of accounts in this organization:")
|
98
|
+
for account in accounts["Accounts"]:
|
99
|
+
print(f"Account Name: {account['Name']} Email: {account['Email']}")
|
100
|
+
if account["Name"].lower().find("logging") >= 0:
|
101
|
+
LOGGING_ACCOUNT_NAME = account["Name"]
|
102
|
+
if account["Name"].lower().find("shared") >= 0:
|
103
|
+
SHARED_SERVICES_ACCOUNT_NAME = account["Name"]
|
104
|
+
if account["Name"].lower().find("security") >= 0:
|
105
|
+
SECURITY_ACCOUNT_NAME = account["Name"]
|
106
|
+
# Step 1 - disconnect directory from SSO - MUST be done manually
|
107
|
+
user_input = input(
|
108
|
+
"\nStep 1 (disconnect directory from SSO) isn't automated, you MUST do it manually BEFORE moving forward, do you want to proceed? [y/n]:"
|
109
|
+
)
|
110
|
+
if user_input == "n":
|
111
|
+
exit()
|
112
|
+
|
113
|
+
# Step 1a - Get the create event from the LandingZoneLaunchAVMStateMachine and change it to a delete event, then run the LandingZoneLaunchAVMStateMachine
|
114
|
+
user_input = input(
|
115
|
+
"\nStep 1a - Do you want to try using the LandingZoneLaunchAVMStateMachine Delete event method? [y/n]:"
|
116
|
+
)
|
117
|
+
if user_input == "y":
|
118
|
+
# iterate through current step functions to find the ARN for the LandingZoneLaunchAVMStateMachine
|
119
|
+
if AWS_SESSION_TOKEN_PASSED:
|
120
|
+
client = boto3.client(
|
121
|
+
"stepfunctions",
|
122
|
+
region_name=AWS_REGION,
|
123
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
124
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
125
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
126
|
+
)
|
127
|
+
else:
|
128
|
+
client = boto3.client(
|
129
|
+
"stepfunctions",
|
130
|
+
region_name=AWS_REGION,
|
131
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
132
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
133
|
+
)
|
134
|
+
state_machines = client.list_state_machines()
|
135
|
+
for state_machine in state_machines["stateMachines"]:
|
136
|
+
if state_machine["name"] == "LandingZoneLaunchAVMStateMachine":
|
137
|
+
arn = state_machine["stateMachineArn"]
|
138
|
+
|
139
|
+
# find the latest execution which should have all of the accounts reflected in the create event
|
140
|
+
executions = client.list_executions(stateMachineArn=arn, statusFilter="SUCCEEDED")
|
141
|
+
execution_arn = executions["executions"][0]["executionArn"]
|
142
|
+
execution = client.describe_execution(executionArn=execution_arn)
|
143
|
+
|
144
|
+
# change the event from CREATE to DELETE and execute the DELETE event
|
145
|
+
data_input = execution["input"].replace("Create", "Delete")
|
146
|
+
execution_arn = client.start_execution(stateMachineArn=arn, input=data_input)
|
147
|
+
execution_status = client.describe_execution(executionArn=execution_arn["executionArn"])
|
148
|
+
|
149
|
+
# wait for a while - the LandingZoneLaunchAVMStateMachine will reverse the SC-0045 launches and terminate all of the stack instances from the baseline in each account
|
150
|
+
while execution_status["status"] == "RUNNING":
|
151
|
+
wait_with_progress_bar(
|
152
|
+
Message="Waiting for LandingZoneLaunchAVMStateMachine Delete event to complete", Seconds=30
|
153
|
+
)
|
154
|
+
execution_status = client.describe_execution(executionArn=execution_arn["executionArn"])
|
155
|
+
|
156
|
+
# exit()
|
157
|
+
|
158
|
+
# Step 2 - Remove products provisioned through Service Catalog
|
159
|
+
print("\nStep 2 - Remove products provisioned through Service Catalog (it may take couple of minutes)")
|
160
|
+
if AWS_SESSION_TOKEN_PASSED:
|
161
|
+
client = boto3.client(
|
162
|
+
"servicecatalog",
|
163
|
+
region_name=AWS_REGION,
|
164
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
165
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
166
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
167
|
+
)
|
168
|
+
else:
|
169
|
+
client = boto3.client(
|
170
|
+
"servicecatalog",
|
171
|
+
region_name=AWS_REGION,
|
172
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
173
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
174
|
+
)
|
175
|
+
provisioned_products = client.search_provisioned_products()["ProvisionedProducts"]
|
176
|
+
|
177
|
+
# if product wasnt created by StateMachineLambdaRole - delete it
|
178
|
+
list_of_termination_records = []
|
179
|
+
for provisioned_product in provisioned_products:
|
180
|
+
if "StateMachineLambdaRole" not in provisioned_product["UserArn"]:
|
181
|
+
print(f"Terminating provisioned product {provisioned_product['Name']}", end=" ")
|
182
|
+
response = client.terminate_provisioned_product(
|
183
|
+
ProvisionedProductId=provisioned_product["Id"],
|
184
|
+
IgnoreErrors=True,
|
185
|
+
TerminateToken=provisioned_product["Id"],
|
186
|
+
)
|
187
|
+
list_of_termination_records.append(response["RecordDetail"]["RecordId"])
|
188
|
+
print("[DONE]")
|
189
|
+
|
190
|
+
if len(list_of_termination_records) > 0:
|
191
|
+
if DEBUG:
|
192
|
+
print(list_of_termination_records)
|
193
|
+
|
194
|
+
while len(list_of_termination_records) > 0:
|
195
|
+
for termination_record in list(list_of_termination_records):
|
196
|
+
response = client.describe_record(Id=termination_record)
|
197
|
+
if response["RecordDetail"]["Status"] == "SUCCEEDED":
|
198
|
+
list_of_termination_records.remove(termination_record)
|
199
|
+
elif response["RecordDetail"]["Status"] == "FAILED":
|
200
|
+
list_of_termination_records.remove(termination_record)
|
201
|
+
print(
|
202
|
+
"Failed deletion of provisioned product {} ".format(
|
203
|
+
response["RecordDetail"]["ProvisionedProductName"]
|
204
|
+
)
|
205
|
+
)
|
206
|
+
input("Please perform manual cleanup and press ENTER when done")
|
207
|
+
|
208
|
+
# we're doing delay in a while loop to display a nice progress bar :D
|
209
|
+
if len(list_of_termination_records) > 0:
|
210
|
+
wait_with_progress_bar(Message="Service Catalog products termination in progress", Seconds=30)
|
211
|
+
else:
|
212
|
+
print("No Service Catalog provisioned products to terminate")
|
213
|
+
|
214
|
+
# Step 3 - content of portfolio from Service Catalog
|
215
|
+
print("\nStep 3 - delete content of portfolio from Service Catalog ")
|
216
|
+
portfolios = client.list_portfolios()["PortfolioDetails"]
|
217
|
+
if len(portfolios) > 0:
|
218
|
+
for portfolio in portfolios:
|
219
|
+
# delete constrains from portfolio
|
220
|
+
constraints = client.list_constraints_for_portfolio(PortfolioId=portfolio["Id"])["ConstraintDetails"]
|
221
|
+
for constraint in constraints:
|
222
|
+
client.delete_constraint(Id=constraint["ConstraintId"])
|
223
|
+
# delete users and groups from portfolio
|
224
|
+
principals = client.list_principals_for_portfolio(PortfolioId=portfolio["Id"])["Principals"]
|
225
|
+
for principal in principals:
|
226
|
+
client.disassociate_principal_from_portfolio(
|
227
|
+
PortfolioId=portfolio["Id"], PrincipalARN=principal["PrincipalARN"]
|
228
|
+
)
|
229
|
+
|
230
|
+
# to delete products from portfolios we need to go through products
|
231
|
+
products = client.search_products_as_admin()["ProductViewDetails"]
|
232
|
+
for product in products:
|
233
|
+
portfolios_for_product = client.list_portfolios_for_product(
|
234
|
+
ProductId=product["ProductViewSummary"]["ProductId"]
|
235
|
+
)
|
236
|
+
for portfolio in portfolios_for_product["PortfolioDetails"]:
|
237
|
+
client.disassociate_product_from_portfolio(
|
238
|
+
ProductId=product["ProductViewSummary"]["ProductId"], PortfolioId=portfolio["Id"]
|
239
|
+
)
|
240
|
+
|
241
|
+
# delete the portfolio (finally)
|
242
|
+
for portfolio in portfolios:
|
243
|
+
print(f"Deleting portfolio {portfolio['Id']}", end=" ")
|
244
|
+
client.delete_portfolio(Id=portfolio["Id"])
|
245
|
+
print("[DONE]")
|
246
|
+
else:
|
247
|
+
print("No portfolios to delete")
|
248
|
+
|
249
|
+
# Step 4 - delete Service Catalog products
|
250
|
+
print("\nStep 4 - delete Service Catalog products")
|
251
|
+
products = client.search_products_as_admin()["ProductViewDetails"]
|
252
|
+
if len(products) > 0:
|
253
|
+
for product in products:
|
254
|
+
print(f"Deleting product {product['ProductViewSummary']['ProductId']}", end=" ")
|
255
|
+
client.delete_product(Id=product["ProductViewSummary"]["ProductId"])
|
256
|
+
print("[DONE]")
|
257
|
+
else:
|
258
|
+
print("No products to delete")
|
259
|
+
|
260
|
+
# Step 5 - delete cloud formation baseline stacks
|
261
|
+
print("\nStep 5 - delete cloud formation baseline stacks")
|
262
|
+
if AWS_SESSION_TOKEN_PASSED:
|
263
|
+
client = boto3.client(
|
264
|
+
"cloudformation",
|
265
|
+
region_name=AWS_REGION,
|
266
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
267
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
268
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
269
|
+
)
|
270
|
+
else:
|
271
|
+
client = boto3.client(
|
272
|
+
"cloudformation",
|
273
|
+
region_name=AWS_REGION,
|
274
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
275
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
276
|
+
)
|
277
|
+
stacks = client.list_stacks(
|
278
|
+
StackStatusFilter=[
|
279
|
+
"CREATE_COMPLETE",
|
280
|
+
"ROLLBACK_FAILED",
|
281
|
+
"ROLLBACK_COMPLETE",
|
282
|
+
"DELETE_FAILED",
|
283
|
+
"UPDATE_COMPLETE",
|
284
|
+
]
|
285
|
+
)
|
286
|
+
deleted_something = False
|
287
|
+
for stack in stacks["StackSummaries"]:
|
288
|
+
desc = stack.get("TemplateDescription", "")
|
289
|
+
if "(SO0045)" in desc and stack["StackStatus"] != "DELETE_COMPLETE":
|
290
|
+
print(f"Deleting stack {stack['StackName']} - {stack['TemplateDescription']}", end=" ")
|
291
|
+
client.delete_stack(StackName=stack["StackName"])
|
292
|
+
print("[DONE]")
|
293
|
+
deleted_something = True
|
294
|
+
|
295
|
+
if not deleted_something:
|
296
|
+
print("No stacks with SO0045 in name to delete")
|
297
|
+
|
298
|
+
# wait for stacks to be deleted
|
299
|
+
delete_in_progress = True
|
300
|
+
while delete_in_progress:
|
301
|
+
delete_in_progress = False
|
302
|
+
stacks = client.list_stacks(
|
303
|
+
StackStatusFilter=[
|
304
|
+
"CREATE_COMPLETE",
|
305
|
+
"ROLLBACK_FAILED",
|
306
|
+
"ROLLBACK_COMPLETE",
|
307
|
+
"DELETE_FAILED",
|
308
|
+
"UPDATE_COMPLETE",
|
309
|
+
]
|
310
|
+
)
|
311
|
+
for stack in stacks["StackSummaries"]:
|
312
|
+
desc = stack.get("TemplateDescription", "")
|
313
|
+
if "(SO0045)" in desc and stack["StackStatus"] != "DELETE_COMPLETE":
|
314
|
+
delete_in_progress = True
|
315
|
+
if delete_in_progress:
|
316
|
+
wait_with_progress_bar(Message="Stacks are still being deleted", Seconds=30)
|
317
|
+
|
318
|
+
# Step 6 - Delete the Security Baseline for each account via StackSets in the Master Account
|
319
|
+
print("\n\nStep 6 - Delete the Security Baseline for each account via StackSets in the Master Account")
|
320
|
+
stack_sets_to_delete = []
|
321
|
+
|
322
|
+
stack_sets = client.list_stack_sets(Status="ACTIVE")
|
323
|
+
deleted_stack_sets = False
|
324
|
+
for stack_set in stack_sets["Summaries"]:
|
325
|
+
deleted_stack_sets = False
|
326
|
+
print(f"Checking whether {stack_set['StackSetName']} is a stackset we need to delete")
|
327
|
+
print("Verbose Output:")
|
328
|
+
print(f" Stack Set Name: {stack_set['StackSetName']}")
|
329
|
+
print(f" Stack Set Status: {stack_set['Status']}")
|
330
|
+
print(f" Delete In Progress: {delete_in_progress}")
|
331
|
+
|
332
|
+
if (
|
333
|
+
stack_set["StackSetName"] in stack_sets_to_delete
|
334
|
+
and stack_set["Status"] == "ACTIVE"
|
335
|
+
and not delete_in_progress
|
336
|
+
):
|
337
|
+
print(f"Deleting stack set {stack_set['StackSetName']}", end="")
|
338
|
+
client.delete_stack_set(StackSetName=stack_set["StackSetName"])
|
339
|
+
deleted_stack_sets = True
|
340
|
+
delete_in_progress = True
|
341
|
+
elif (
|
342
|
+
stack_set["StackSetName"] in stack_sets_to_delete
|
343
|
+
and stack_set["Status"] == "FAILED"
|
344
|
+
and not delete_in_progress
|
345
|
+
):
|
346
|
+
print(
|
347
|
+
f"Even though the stack set {stack_set['StackSetName']} is in a FAILED state, we're going to delete it anyway..."
|
348
|
+
)
|
349
|
+
print(f"Deleting stack set {stack_set['StackSetName']}", end="")
|
350
|
+
client.delete_stack_set(StackSetName=stack_set["StackSetName"])
|
351
|
+
print("[DONE]")
|
352
|
+
deleted_stack_sets = True
|
353
|
+
delete_in_progress = True
|
354
|
+
elif not stack_set["StackSetName"] in stack_sets_to_delete:
|
355
|
+
print(f"It appears that {stack_set['StackSetName']} isn't a stackset we need to delete", end="")
|
356
|
+
|
357
|
+
# wait for those stacks sets to be deleted
|
358
|
+
while deleted_stack_sets and delete_in_progress:
|
359
|
+
delete_in_progress = False
|
360
|
+
stack_set_status = {"Status": "None"}
|
361
|
+
try:
|
362
|
+
stack_set_status = client.describe_stack_set(StackSetName=stack_set["StackSetName"])["StackSet"][
|
363
|
+
"Status"
|
364
|
+
]
|
365
|
+
except:
|
366
|
+
# This means the deletion beat us.
|
367
|
+
pass
|
368
|
+
print(f"\nError deleting stack set {stack_set['StackSetName']}. The deletion probably beat us")
|
369
|
+
# for stack_set in stack_sets['Summaries']:
|
370
|
+
if stack_set_status["Status"] == "ACTIVE":
|
371
|
+
delete_in_progress = True
|
372
|
+
print(f"Status of {stack_set['StackSetName']} stackset is currently {stack_set_status['Status']}")
|
373
|
+
wait_with_progress_bar(Message="\nSecurity Baseline stack sets delete in progress", Seconds=30)
|
374
|
+
print("[DONE]")
|
375
|
+
# TODO: If the Stackset is in status "Failed" - this will go into a race condition, and never complete.
|
376
|
+
if not deleted_stack_sets:
|
377
|
+
print("No more stack sets to delete")
|
378
|
+
|
379
|
+
# Step 7 - For the remaining StackSets which will still have stack instances, you will need to Manage Stacksets, enter the account numbers and regions,
|
380
|
+
# and delete all stack instances. Once the stack instances have been deleted, delete the StackSets.
|
381
|
+
# Note - I have added 3 stack sets, since they are not mentioned in the official doc - AWS-Landing-Zone-Baseline-PrimaryVPC, AWS-Landing-Zone-Baseline-SecurityRoles,
|
382
|
+
# AWS-Landing-Zone-Centralized-Logging-Primary
|
383
|
+
# added two more stacks based on contributed customizations - AWS-Landing-Zone-Baseline-ConfigAggregator and AWS-Landing-Zone-Baseline-ConfigAggregator
|
384
|
+
# added the centralized logging primary and spoke stacks - AWS-Landing-Zone-Baseline-CentralizedLoggingSpoke and AWS-Landing-Zone-Centralized-Logging-Primary
|
385
|
+
|
386
|
+
print("\nStep 7 - getting rid of multiple stack sets")
|
387
|
+
|
388
|
+
stack_set_names = [
|
389
|
+
"AWS-Landing-Zone-Baseline-EnableCloudTrail",
|
390
|
+
"AWS-Landing-Zone-Baseline-EnableConfig",
|
391
|
+
"AWS-Landing-Zone-Baseline-EnableConfigRules",
|
392
|
+
"AWS-Landing-Zone-Baseline-EnableNotifications",
|
393
|
+
"AWS-Landing-Zone-Baseline-IamPasswordPolicy",
|
394
|
+
"AWS-Landing-Zone-SharedTopic",
|
395
|
+
"AWS-Landing-Zone-SharedBucket",
|
396
|
+
"AWS-Landing-Zone-SecurityRoles",
|
397
|
+
"AWS-Landing-Zone-Baseline-SecurityRoles",
|
398
|
+
"AWS-Landing-Zone-Baseline-CentralizedLoggingSpoke",
|
399
|
+
"AWS-Landing-Zone-Centralized-Logging-Primary",
|
400
|
+
"AWS-Landing-Zone-Baseline-ConfigAggregator",
|
401
|
+
"AWS-Landing-Zone-ConfigAggregatorService",
|
402
|
+
"AWS-Landing-Zone-PrimaryADConnector",
|
403
|
+
"AWS-Landing-Zone-PrimaryAccountVPC",
|
404
|
+
"AWS-Landing-Zone-SharedServicesRDGW",
|
405
|
+
"AWS-Landing-Zone-SharedServicesActiveDirectory",
|
406
|
+
"AWS-Landing-Zone-SharedServicesAccountVPC",
|
407
|
+
"AWS-Landing-Zone-Baseline-PrimaryVPC",
|
408
|
+
"AWS-Landing-Zone-GuardDutyMaster",
|
409
|
+
"AWS-Landing-Zone-Baseline-ConfigRole",
|
410
|
+
"AWS-Landing-Zone-Baseline-EnableConfigRulesGlobal",
|
411
|
+
]
|
412
|
+
|
413
|
+
for stack_set_name in stack_set_names:
|
414
|
+
# first check if stack set exists at all
|
415
|
+
stack_set_exists = False
|
416
|
+
active_stack_sets = client.list_stack_sets(Status="ACTIVE")
|
417
|
+
for active_stack_set in active_stack_sets["Summaries"]:
|
418
|
+
if active_stack_set["StackSetName"] == stack_set_name:
|
419
|
+
stack_set_exists = True
|
420
|
+
print(f"StackSet {active_stack_set['StackSetName']} exists, is ACTIVE and needs to be deleted.")
|
421
|
+
|
422
|
+
if stack_set_exists:
|
423
|
+
instances = client.list_stack_instances(StackSetName=stack_set_name)
|
424
|
+
deleted_instances = False
|
425
|
+
for instance in instances["Summaries"]:
|
426
|
+
if (
|
427
|
+
instance["Status"] == "CURRENT"
|
428
|
+
or instance["Status"] == "OUTDATED"
|
429
|
+
or instance["Status"] == "INOPERABLE"
|
430
|
+
):
|
431
|
+
print(
|
432
|
+
"Deleting stack instance in account {}, region {} from {}".format(
|
433
|
+
instance["Account"], instance["Region"], stack_set_name
|
434
|
+
),
|
435
|
+
end=" ",
|
436
|
+
)
|
437
|
+
response = client.delete_stack_instances(
|
438
|
+
StackSetName=stack_set_name,
|
439
|
+
Accounts=[instance["Account"]],
|
440
|
+
Regions=[instance["Region"]],
|
441
|
+
RetainStacks=False,
|
442
|
+
)
|
443
|
+
print_debug(response)
|
444
|
+
delete_in_progress = True
|
445
|
+
while delete_in_progress:
|
446
|
+
operation_status = client.describe_stack_set_operation(
|
447
|
+
StackSetName=stack_set_name, OperationId=response["OperationId"]
|
448
|
+
)
|
449
|
+
if operation_status["StackSetOperation"]["Status"] == "SUCCEEDED":
|
450
|
+
delete_in_progress = False
|
451
|
+
if operation_status["StackSetOperation"]["Status"] == "FAILED":
|
452
|
+
print("Stack Instance delete failed - fix the problem and try again")
|
453
|
+
exit()
|
454
|
+
print(".", end="")
|
455
|
+
time.sleep(5)
|
456
|
+
print_debug(
|
457
|
+
f"Still deleting stackset {stack_set_name} in region {instance['Region']} in account {instance['Account']}"
|
458
|
+
)
|
459
|
+
deleted_instances = True
|
460
|
+
|
461
|
+
if not deleted_instances:
|
462
|
+
print(f"No instances to delete for {stack_set_name}")
|
463
|
+
|
464
|
+
# wait for delete operations on this stack set to finish
|
465
|
+
# operations = client.list_stack_set_operations(StackSetName=stack_set_name)
|
466
|
+
delete_in_progress = True
|
467
|
+
while delete_in_progress:
|
468
|
+
delete_in_progress = False
|
469
|
+
operations = client.list_stack_set_operations(StackSetName=stack_set_name)
|
470
|
+
for operation in operations["Summaries"]:
|
471
|
+
if operation["Action"] == "DELETE" and (
|
472
|
+
operation["Status"] != "SUCCEEDED" and operation["Status"] != "FAILED"
|
473
|
+
): # In other words - the status is "Running"
|
474
|
+
delete_in_progress = True
|
475
|
+
wait_with_progress_bar(Message="Instance deletion in progress", Seconds=10)
|
476
|
+
print("[DONE]")
|
477
|
+
|
478
|
+
# delete the stack set
|
479
|
+
print(f"Deleting stack set {stack_set_name}", end=" ")
|
480
|
+
try:
|
481
|
+
client.delete_stack_set(StackSetName=stack_set_name)
|
482
|
+
deleted_stack_set = True
|
483
|
+
print("[DONE]")
|
484
|
+
except ClientError as e:
|
485
|
+
print(f"\nError deleting stack set {stack_set_name} - {e}")
|
486
|
+
input("Please investigate it, delete all relevant resources and press ENTER to continue")
|
487
|
+
else:
|
488
|
+
print(f"Stack set {stack_set_name} not found, skipping it")
|
489
|
+
|
490
|
+
# Step 8 - Unlock the member accounts (Skip this step: if the flag 'lock_down_stack_sets_role' is already set to 'No')
|
491
|
+
print(
|
492
|
+
"\nStep 8 - skipping it, assuming lock_down_stacks_set_role is set to 'No'. Following steps will fail otherwise!"
|
493
|
+
)
|
494
|
+
|
495
|
+
# Step 9 - Delete the Logging Bucket in the Logging Account. Note - I delete all buckets that include 'aws-landing-zone' string in name
|
496
|
+
print(
|
497
|
+
"\nStep 9 - cleaning up logging account, since we are emptying S3 buckets one-by-one, this step will take some time. "
|
498
|
+
)
|
499
|
+
provided_account_name = input(f"Please provide logging account name or press ENTER if it's {LOGGING_ACCOUNT_NAME} ")
|
500
|
+
if provided_account_name == "":
|
501
|
+
provided_account_name = LOGGING_ACCOUNT_NAME
|
502
|
+
|
503
|
+
deleted_logging_bucket = False
|
504
|
+
# get the ID of the logging account
|
505
|
+
account_found = False
|
506
|
+
if AWS_SESSION_TOKEN_PASSED:
|
507
|
+
client = boto3.client(
|
508
|
+
"organizations",
|
509
|
+
region_name=AWS_REGION,
|
510
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
511
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
512
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
513
|
+
)
|
514
|
+
else:
|
515
|
+
client = boto3.client(
|
516
|
+
"organizations",
|
517
|
+
region_name=AWS_REGION,
|
518
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
519
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
520
|
+
)
|
521
|
+
accounts = client.list_accounts()
|
522
|
+
for account in accounts["Accounts"]:
|
523
|
+
if (account["Name"] == provided_account_name) and not (account["Id"] == "614019996801"):
|
524
|
+
account_found = True
|
525
|
+
if AWS_SESSION_TOKEN_PASSED:
|
526
|
+
sts_client = boto3.client(
|
527
|
+
"sts",
|
528
|
+
region_name=AWS_REGION,
|
529
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
530
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
531
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
532
|
+
)
|
533
|
+
else:
|
534
|
+
sts_client = boto3.client(
|
535
|
+
"sts",
|
536
|
+
region_name=AWS_REGION,
|
537
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
538
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
539
|
+
)
|
540
|
+
# generate temporary session for the log-archive account
|
541
|
+
role_arn = f"arn:aws:iam::{account['Id']}:role/AWSCloudFormationStackSetExecutionRole"
|
542
|
+
print(f"Account ID for the log-archive account: {account['Id']}")
|
543
|
+
account_credentials = sts_client.assume_role(RoleArn=role_arn, RoleSessionName="ALZAddIsengardUserScript")[
|
544
|
+
"Credentials"
|
545
|
+
]
|
546
|
+
|
547
|
+
# create client with temporary credentials
|
548
|
+
s3_client = boto3.client(
|
549
|
+
"s3",
|
550
|
+
region_name=AWS_REGION,
|
551
|
+
aws_access_key_id=account_credentials["AccessKeyId"],
|
552
|
+
aws_secret_access_key=account_credentials["SecretAccessKey"],
|
553
|
+
aws_session_token=account_credentials["SessionToken"],
|
554
|
+
)
|
555
|
+
|
556
|
+
# delete bucket
|
557
|
+
buckets = s3_client.list_buckets()["Buckets"]
|
558
|
+
for bucket in buckets:
|
559
|
+
if "aws-landing-zone" in bucket["Name"]:
|
560
|
+
# empty the bucket and delete it
|
561
|
+
s3 = boto3.resource(
|
562
|
+
"s3",
|
563
|
+
region_name=AWS_REGION,
|
564
|
+
aws_access_key_id=account_credentials["AccessKeyId"],
|
565
|
+
aws_secret_access_key=account_credentials["SecretAccessKey"],
|
566
|
+
aws_session_token=account_credentials["SessionToken"],
|
567
|
+
)
|
568
|
+
s3_bucket = s3.Bucket(bucket["Name"])
|
569
|
+
if s3_bucket in s3.buckets.all():
|
570
|
+
print(
|
571
|
+
f"Emptying bucket (this may take a while, restart script if it crashes here) {bucket['Name']}",
|
572
|
+
end=" ",
|
573
|
+
)
|
574
|
+
try:
|
575
|
+
s3_bucket.object_versions.all().delete()
|
576
|
+
print("[DONE]")
|
577
|
+
print(f"Deleting bucket {bucket['Name']}", end=" ")
|
578
|
+
s3_bucket.delete()
|
579
|
+
print("[DONE]")
|
580
|
+
deleted_logging_bucket = True
|
581
|
+
except ClientError as e:
|
582
|
+
print(
|
583
|
+
"Error while trying to delete S3 bucket {}, it should be empty by now so if you see BucketNotEmpty check the bucket in AWS console and delete it manually"
|
584
|
+
)
|
585
|
+
input("Press ENTER, to continue, when you have deleted the S3 bucket")
|
586
|
+
deleted_logging_bucket = True
|
587
|
+
if not deleted_logging_bucket:
|
588
|
+
print("No S3 buckets to delete")
|
589
|
+
|
590
|
+
if not account_found:
|
591
|
+
input_response = input(
|
592
|
+
"Logging account WAS NOT FOUND. Unless you are certain this is ok, you should STOP and DEBUG this. Do you want to proceed with deletion? [y/n]"
|
593
|
+
)
|
594
|
+
if input_response == "n":
|
595
|
+
exit()
|
596
|
+
|
597
|
+
# Step 10 - Delete the auto-generated EC2 key-pairs in the Shared Services Account
|
598
|
+
print("\nStep 10 - deleting auto-generated EC2 key-pairs")
|
599
|
+
provided_account_name = input(
|
600
|
+
f"Please provide shared-services account name or press ENTER if it's {SHARED_SERVICES_ACCOUNT_NAME}"
|
601
|
+
)
|
602
|
+
if provided_account_name == "":
|
603
|
+
provided_account_name = SHARED_SERVICES_ACCOUNT_NAME
|
604
|
+
|
605
|
+
deleted_key_pair = False
|
606
|
+
if AWS_SESSION_TOKEN_PASSED:
|
607
|
+
client = boto3.client(
|
608
|
+
"organizations",
|
609
|
+
region_name=AWS_REGION,
|
610
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
611
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
612
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
613
|
+
)
|
614
|
+
else:
|
615
|
+
client = boto3.client(
|
616
|
+
"organizations",
|
617
|
+
region_name=AWS_REGION,
|
618
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
619
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
620
|
+
)
|
621
|
+
accounts = client.list_accounts()
|
622
|
+
account_found = False
|
623
|
+
for account in accounts["Accounts"]:
|
624
|
+
if account["Name"] == provided_account_name:
|
625
|
+
account_found = True
|
626
|
+
if AWS_SESSION_TOKEN_PASSED:
|
627
|
+
sts_client = boto3.client(
|
628
|
+
"sts",
|
629
|
+
region_name=AWS_REGION,
|
630
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
631
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
632
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
633
|
+
)
|
634
|
+
else:
|
635
|
+
sts_client = boto3.client(
|
636
|
+
"sts",
|
637
|
+
region_name=AWS_REGION,
|
638
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
639
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
640
|
+
)
|
641
|
+
# generate temporary session for the logging account
|
642
|
+
role_arn = f"arn:aws:iam::{account['Id']}:role/AWSCloudFormationStackSetExecutionRole"
|
643
|
+
print(f"Account ID for the shared services account: {account['Id']}")
|
644
|
+
account_credentials = sts_client.assume_role(RoleArn=role_arn, RoleSessionName="ALZAddIsengardUserScript")[
|
645
|
+
"Credentials"
|
646
|
+
]
|
647
|
+
|
648
|
+
# create client with temporary credentials
|
649
|
+
ec2_client = boto3.client(
|
650
|
+
"ec2",
|
651
|
+
region_name=AWS_REGION,
|
652
|
+
aws_access_key_id=account_credentials["AccessKeyId"],
|
653
|
+
aws_secret_access_key=account_credentials["SecretAccessKey"],
|
654
|
+
aws_session_token=account_credentials["SessionToken"],
|
655
|
+
)
|
656
|
+
key_pairs = ec2_client.describe_key_pairs()["KeyPairs"]
|
657
|
+
for key_pair in key_pairs:
|
658
|
+
if key_pair["KeyName"].startswith("lz"):
|
659
|
+
print(f"Deleting key pair {key_pair['KeyName']}", end=" ")
|
660
|
+
ec2_client.delete_key_pair(KeyName=key_pair["KeyName"])
|
661
|
+
print("[DONE]")
|
662
|
+
deleted_key_pair = True
|
663
|
+
|
664
|
+
if not deleted_key_pair:
|
665
|
+
print("Key pair not found for deletion")
|
666
|
+
|
667
|
+
if not account_found:
|
668
|
+
input_response = input(
|
669
|
+
"Shared services account WAS NOT FOUND. Unless you are certain this is ok, you should STOP and DEBUG this. Do you want to proceed with deletion? [y/n]"
|
670
|
+
)
|
671
|
+
if input_response == "n":
|
672
|
+
exit()
|
673
|
+
|
674
|
+
# Step 11 - Delete the following S3 buckets in the Master Account
|
675
|
+
print("\nStep 11 - delete S3 buckets for config and pipeline artifacts in the master account")
|
676
|
+
if AWS_SESSION_TOKEN_PASSED:
|
677
|
+
s3_client = boto3.client(
|
678
|
+
"s3",
|
679
|
+
region_name=AWS_REGION,
|
680
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
681
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
682
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
683
|
+
)
|
684
|
+
else:
|
685
|
+
s3_client = boto3.client(
|
686
|
+
"s3", region_name=AWS_REGION, aws_access_key_id=AWS_ACCESS_KEY, aws_secret_access_key=AWS_SECRET_ACCESS_KEY
|
687
|
+
)
|
688
|
+
# delete buckets
|
689
|
+
deleted_a_bucket = False
|
690
|
+
buckets = s3_client.list_buckets()["Buckets"]
|
691
|
+
for bucket in buckets:
|
692
|
+
if ("aws-landing-zone-configuration" in bucket["Name"]) or ("landingzonepipelineartifacts" in bucket["Name"]):
|
693
|
+
# empty the bucket and delete it
|
694
|
+
if AWS_SESSION_TOKEN_PASSED:
|
695
|
+
s3 = boto3.resource(
|
696
|
+
"s3",
|
697
|
+
region_name=AWS_REGION,
|
698
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
699
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
700
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
701
|
+
)
|
702
|
+
else:
|
703
|
+
s3 = boto3.resource(
|
704
|
+
"s3",
|
705
|
+
region_name=AWS_REGION,
|
706
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
707
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
708
|
+
)
|
709
|
+
bucket_to_delete = s3.Bucket(bucket["Name"])
|
710
|
+
print_debug(f"Bucket to delete: {bucket_to_delete}")
|
711
|
+
try:
|
712
|
+
print(f"Emptying bucket (this may take a while) {bucket['Name']}", end=" ")
|
713
|
+
bucket_to_delete.object_versions.all().delete()
|
714
|
+
print("[DONE]")
|
715
|
+
except ClientError as e:
|
716
|
+
print(f"An error occured - {e}")
|
717
|
+
input(
|
718
|
+
"This _may_ mean that not all objects were deleted from the bucket, please investigate, empty bucket manually and press ENTER to continue"
|
719
|
+
)
|
720
|
+
|
721
|
+
try:
|
722
|
+
print(f"Deleting bucket {bucket['Name']}", end=" ")
|
723
|
+
bucket_to_delete.delete()
|
724
|
+
print("[DONE]")
|
725
|
+
deleted_a_bucket = True
|
726
|
+
except ClientError as e:
|
727
|
+
print(f"An error occured - {e}")
|
728
|
+
input("Please delete the bucket manually and press ENTER to continue")
|
729
|
+
|
730
|
+
if not deleted_a_bucket:
|
731
|
+
print("Buckets with matching names not found")
|
732
|
+
|
733
|
+
# Step 12 - Delete the config recorder and delivery channel from any account you wish to reuse
|
734
|
+
"""
|
735
|
+
print("\nStep 12 - Delete the config recorder and delivery channel from any account you wish to reuse")
|
736
|
+
if AWS_SESSION_TOKEN_PASSED:
|
737
|
+
config = boto3.client(
|
738
|
+
'config',
|
739
|
+
region_name=AWS_REGION,
|
740
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
741
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
742
|
+
aws_session_token=AWS_SESSION_TOKEN
|
743
|
+
)
|
744
|
+
else:
|
745
|
+
config = boto3.client(
|
746
|
+
'config',
|
747
|
+
region_name=AWS_REGION,
|
748
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
749
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
750
|
+
)
|
751
|
+
|
752
|
+
DeliveryChannelName = config.describe_delivery_channels()
|
753
|
+
DeliveryChannelDeletion = config.delete_delivery_channel(
|
754
|
+
DeliveryChannelName=DeliveryChannelName
|
755
|
+
)
|
756
|
+
ConfigRecorderName = config.describe_configuration_recorders()
|
757
|
+
ConfigRecorderNameDeletion = config.delete_configuration_recorder(
|
758
|
+
ConfigurationRecorderName=ConfigRecorderName
|
759
|
+
)
|
760
|
+
"""
|
761
|
+
"""
|
762
|
+
print("\nStep 12 - Delete the config recorder and delivery channel from any account you wish to reuse")
|
763
|
+
user_input = input("This step is MANUAL (if you need it) and must be done with CLI. Do you want to continue? [y/n]")
|
764
|
+
if user_input == 'n':
|
765
|
+
exit()
|
766
|
+
"""
|
767
|
+
|
768
|
+
# Step 12.5 - delete stacks that are not mentioned in the deletion manual, but are hanging
|
769
|
+
print("\nStep 12.5 - Delete anything that is left with 'AWS-Landing-Zone' in name")
|
770
|
+
if AWS_SESSION_TOKEN_PASSED:
|
771
|
+
client = boto3.client(
|
772
|
+
"cloudformation",
|
773
|
+
region_name=AWS_REGION,
|
774
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
775
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
776
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
777
|
+
)
|
778
|
+
else:
|
779
|
+
client = boto3.client(
|
780
|
+
"cloudformation",
|
781
|
+
region_name=AWS_REGION,
|
782
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
783
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
784
|
+
)
|
785
|
+
stacks = client.list_stacks()
|
786
|
+
deleted_something = False
|
787
|
+
for stack in stacks["StackSummaries"]:
|
788
|
+
if "AWS-Landing-Zone" in stack["StackName"] and stack["StackStatus"] != "DELETE_COMPLETE":
|
789
|
+
print(f"Deleting stack {stack['StackName']} - {stack['TemplateDescription']}", end=" ")
|
790
|
+
client.delete_stack(StackName=stack["StackName"])
|
791
|
+
print("[DONE]")
|
792
|
+
deleted_something = True
|
793
|
+
|
794
|
+
if not deleted_something:
|
795
|
+
print("No stacks with AWS-Landing-Zone in name to delete")
|
796
|
+
|
797
|
+
# we are not waiting for those deletion to finish
|
798
|
+
|
799
|
+
# Step 12.6 - remove GuardDuty detectors
|
800
|
+
user_input = input("\nStep 12.6 - Do you want to remove GuardDuty detectors? [y/n]:")
|
801
|
+
if user_input == "y":
|
802
|
+
print("Deleting detectors in master account")
|
803
|
+
if AWS_SESSION_TOKEN_PASSED:
|
804
|
+
session = boto3.session.Session(
|
805
|
+
region_name=AWS_REGION,
|
806
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
807
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
808
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
809
|
+
)
|
810
|
+
else:
|
811
|
+
session = boto3.session.Session(
|
812
|
+
region_name=AWS_REGION, aws_access_key_id=AWS_ACCESS_KEY, aws_secret_access_key=AWS_SECRET_ACCESS_KEY
|
813
|
+
)
|
814
|
+
# iterate through current step functions to find the ARN for the LandingZoneLaunchAVMStateMachine
|
815
|
+
guardduty_regions = session.get_available_regions("guardduty")
|
816
|
+
guardduty_regions.remove("ap-east-1") # Removes HongKong
|
817
|
+
guardduty_regions.remove("me-south-1") # Removes Bahrain
|
818
|
+
for region in guardduty_regions:
|
819
|
+
if AWS_SESSION_TOKEN_PASSED:
|
820
|
+
client = boto3.client(
|
821
|
+
"guardduty",
|
822
|
+
region_name=region,
|
823
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
824
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
825
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
826
|
+
)
|
827
|
+
else:
|
828
|
+
client = boto3.client(
|
829
|
+
"guardduty",
|
830
|
+
region_name=region,
|
831
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
832
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
833
|
+
)
|
834
|
+
detectors = client.list_detectors()
|
835
|
+
for detector in detectors["DetectorIds"]:
|
836
|
+
response = client.delete_detector(DetectorId=detector)
|
837
|
+
print(f"Deleted GuardDuty detector {detector} in region {region}")
|
838
|
+
print_debug(response)
|
839
|
+
print("\nAssuming security account is called 'security', edit the script otherwise")
|
840
|
+
provided_account_name = input(
|
841
|
+
f"Please provide security account name or press ENTER if it's {SECURITY_ACCOUNT_NAME}"
|
842
|
+
)
|
843
|
+
if provided_account_name == "":
|
844
|
+
provided_account_name = SECURITY_ACCOUNT_NAME
|
845
|
+
|
846
|
+
deleted_key_pair = False
|
847
|
+
if AWS_SESSION_TOKEN_PASSED:
|
848
|
+
client = boto3.client(
|
849
|
+
"organizations",
|
850
|
+
region_name=AWS_REGION,
|
851
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
852
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
853
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
854
|
+
)
|
855
|
+
else:
|
856
|
+
client = boto3.client(
|
857
|
+
"organizations",
|
858
|
+
region_name=AWS_REGION,
|
859
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
860
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
861
|
+
)
|
862
|
+
accounts = client.list_accounts()
|
863
|
+
account_found = False
|
864
|
+
for account in accounts["Accounts"]:
|
865
|
+
if account["Name"] == provided_account_name:
|
866
|
+
account_found = True
|
867
|
+
if AWS_SESSION_TOKEN_PASSED:
|
868
|
+
sts_client = boto3.client(
|
869
|
+
"sts",
|
870
|
+
region_name=AWS_REGION,
|
871
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
872
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
873
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
874
|
+
)
|
875
|
+
else:
|
876
|
+
sts_client = boto3.client(
|
877
|
+
"sts",
|
878
|
+
region_name=AWS_REGION,
|
879
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
880
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
881
|
+
)
|
882
|
+
# generate temporary session for the security account
|
883
|
+
role_arn = f"arn:aws:iam::{account['Id']}:role/AWSCloudFormationStackSetExecutionRole"
|
884
|
+
print(f"Account ID for the security account: {account['Id']}")
|
885
|
+
account_credentials = sts_client.assume_role(
|
886
|
+
RoleArn=role_arn, RoleSessionName="ALZAddIsengardUserScript"
|
887
|
+
)["Credentials"]
|
888
|
+
|
889
|
+
# create client with temporary credentials
|
890
|
+
session = boto3.session.Session(
|
891
|
+
region_name=AWS_REGION,
|
892
|
+
aws_access_key_id=account_credentials["AccessKeyId"],
|
893
|
+
aws_secret_access_key=account_credentials["SecretAccessKey"],
|
894
|
+
aws_session_token=account_credentials["SessionToken"],
|
895
|
+
)
|
896
|
+
|
897
|
+
# iterate through current step functions to find the ARN for the LandingZoneLaunchAVMStateMachine
|
898
|
+
# guardduty_regions = session.get_available_regions('guardduty')
|
899
|
+
for region in guardduty_regions:
|
900
|
+
client = boto3.client(
|
901
|
+
"guardduty",
|
902
|
+
region_name=region,
|
903
|
+
aws_access_key_id=account_credentials["AccessKeyId"],
|
904
|
+
aws_secret_access_key=account_credentials["SecretAccessKey"],
|
905
|
+
aws_session_token=account_credentials["SessionToken"],
|
906
|
+
)
|
907
|
+
detectors = client.list_detectors()
|
908
|
+
for detector in detectors["DetectorIds"]:
|
909
|
+
response = client.delete_detector(DetectorId=detector)
|
910
|
+
print(f"Deleted GuardDuty detector {detector} in region {region}")
|
911
|
+
print_debug(response)
|
912
|
+
|
913
|
+
# Step 13
|
914
|
+
print("\nStep 13 - Delete the Landing Zone initiation template")
|
915
|
+
user_input = input("\nStep 13 - Do you want to remove the landing zone initiation template? [y/n]:")
|
916
|
+
if user_input == "y":
|
917
|
+
if AWS_SESSION_TOKEN_PASSED:
|
918
|
+
client = boto3.client(
|
919
|
+
"cloudformation",
|
920
|
+
region_name=AWS_REGION,
|
921
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
922
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
923
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
924
|
+
)
|
925
|
+
else:
|
926
|
+
client = boto3.client(
|
927
|
+
"cloudformation",
|
928
|
+
region_name=AWS_REGION,
|
929
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
930
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
931
|
+
)
|
932
|
+
stacks = client.list_stacks()
|
933
|
+
deleted_something = False
|
934
|
+
for stack in stacks["StackSummaries"]:
|
935
|
+
desc = stack.get("TemplateDescription", "")
|
936
|
+
if "(SO0044)" in desc and stack["StackStatus"] != "DELETE_COMPLETE":
|
937
|
+
print(f"Updating termination protection in case it's set on stack {stack['StackName']}")
|
938
|
+
client.update_termination_protection(EnableTerminationProtection=False, StackName=stack["StackName"])
|
939
|
+
print(f"Triggering delete of stack {stack['StackName']} - {stack['TemplateDescription']}", end=" ")
|
940
|
+
client.delete_stack(StackName=stack["StackName"])
|
941
|
+
print("[DONE]")
|
942
|
+
deleted_something = True
|
943
|
+
|
944
|
+
if not deleted_something:
|
945
|
+
print("No stacks with SO0044 in name to delete")
|
946
|
+
|
947
|
+
# wait for stacks to be deleted
|
948
|
+
delete_in_progress = True
|
949
|
+
while delete_in_progress:
|
950
|
+
delete_in_progress = False
|
951
|
+
stacks = client.list_stacks()
|
952
|
+
for stack in stacks["StackSummaries"]:
|
953
|
+
desc = stack.get("TemplateDescription", "")
|
954
|
+
if "(SO0044)" in desc and stack["StackStatus"] != "DELETE_COMPLETE":
|
955
|
+
# print(stack)
|
956
|
+
delete_in_progress = True
|
957
|
+
if delete_in_progress:
|
958
|
+
wait_with_progress_bar(
|
959
|
+
Message="Stack(s) are still being deleted (if it takes more than 1 hour, restart the process)",
|
960
|
+
Seconds=30,
|
961
|
+
)
|
962
|
+
|
963
|
+
# Step 14 - Clean up Organizations
|
964
|
+
print("\n\nStep 14 - Clean up Organizations.")
|
965
|
+
print(
|
966
|
+
"If you've got any errors above, this is the best time to manually review ALZ accounts and remove all resources that are left behind"
|
967
|
+
)
|
968
|
+
input_response = input("Do you want to continue? [y/n]")
|
969
|
+
if input_response == "n":
|
970
|
+
exit()
|
971
|
+
|
972
|
+
deleted_something = False
|
973
|
+
if AWS_SESSION_TOKEN_PASSED:
|
974
|
+
client = boto3.client(
|
975
|
+
"organizations",
|
976
|
+
region_name=AWS_REGION,
|
977
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
978
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
979
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
980
|
+
)
|
981
|
+
else:
|
982
|
+
client = boto3.client(
|
983
|
+
"organizations",
|
984
|
+
region_name=AWS_REGION,
|
985
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
986
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
987
|
+
)
|
988
|
+
# move accounts out of the org
|
989
|
+
roots = client.list_roots()["Roots"]
|
990
|
+
for root in roots:
|
991
|
+
ous = client.list_organizational_units_for_parent(ParentId=root["Id"])["OrganizationalUnits"]
|
992
|
+
for ou in ous:
|
993
|
+
accounts = client.list_accounts_for_parent(ParentId=ou["Id"])["Accounts"]
|
994
|
+
for account in accounts:
|
995
|
+
print(f"Moving account {account['Id']} to root", end=" ")
|
996
|
+
client.move_account(AccountId=account["Id"], SourceParentId=ou["Id"], DestinationParentId=root["Id"])
|
997
|
+
print("[DONE]")
|
998
|
+
|
999
|
+
# delete ous
|
1000
|
+
roots = client.list_roots()["Roots"]
|
1001
|
+
for root in roots:
|
1002
|
+
ous = client.list_organizational_units_for_parent(ParentId=root["Id"])["OrganizationalUnits"]
|
1003
|
+
for ou in ous:
|
1004
|
+
print(f"Deleting OU {ou['Name']}", end=" ")
|
1005
|
+
client.delete_organizational_unit(OrganizationalUnitId=ou["Id"])
|
1006
|
+
print("[DONE]")
|
1007
|
+
deleted_something = True
|
1008
|
+
|
1009
|
+
if not deleted_something:
|
1010
|
+
print("Found nothing to delete")
|
1011
|
+
|
1012
|
+
# Step 15 - Delete all the Landing Zone SSM Parameters
|
1013
|
+
print("\nStep 15 - Delete all the Landing Zone SSM Parameters")
|
1014
|
+
if AWS_SESSION_TOKEN_PASSED:
|
1015
|
+
client = boto3.client(
|
1016
|
+
"ssm",
|
1017
|
+
region_name=AWS_REGION,
|
1018
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
1019
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
1020
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
1021
|
+
)
|
1022
|
+
else:
|
1023
|
+
client = boto3.client(
|
1024
|
+
"ssm", region_name=AWS_REGION, aws_access_key_id=AWS_ACCESS_KEY, aws_secret_access_key=AWS_SECRET_ACCESS_KEY
|
1025
|
+
)
|
1026
|
+
deleted_any_ssm_params = False
|
1027
|
+
# because the API for delete is able to accept max 10 params in one shot, we need to run it multiple times
|
1028
|
+
while True:
|
1029
|
+
ssm_parameters = client.describe_parameters(MaxResults=10)
|
1030
|
+
print_debug(ssm_parameters)
|
1031
|
+
|
1032
|
+
list_of_params = []
|
1033
|
+
for ssm_parameter in ssm_parameters["Parameters"]:
|
1034
|
+
list_of_params.append(ssm_parameter["Name"])
|
1035
|
+
|
1036
|
+
if len(list_of_params) > 0:
|
1037
|
+
print("Deleting SSM Parameters", end=" ")
|
1038
|
+
client.delete_parameters(Names=list_of_params)
|
1039
|
+
deleted_any_ssm_params = True
|
1040
|
+
print("[DONE]")
|
1041
|
+
else:
|
1042
|
+
break
|
1043
|
+
|
1044
|
+
if not deleted_any_ssm_params:
|
1045
|
+
print("No SSM Parameters found to delete")
|
1046
|
+
|
1047
|
+
# Step 16 - Ensure the Landing Zone KMS Keys have been deleted
|
1048
|
+
print("\nStep 16 - Ensure the Landing Zone KMS Keys have been deleted")
|
1049
|
+
if AWS_SESSION_TOKEN_PASSED:
|
1050
|
+
client = boto3.client(
|
1051
|
+
"kms",
|
1052
|
+
region_name=AWS_REGION,
|
1053
|
+
aws_access_key_id=AWS_ACCESS_KEY,
|
1054
|
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
1055
|
+
aws_session_token=AWS_SESSION_TOKEN,
|
1056
|
+
)
|
1057
|
+
else:
|
1058
|
+
client = boto3.client(
|
1059
|
+
"kms", region_name=AWS_REGION, aws_access_key_id=AWS_ACCESS_KEY, aws_secret_access_key=AWS_SECRET_ACCESS_KEY
|
1060
|
+
)
|
1061
|
+
|
1062
|
+
deleted_alias = False
|
1063
|
+
ALIAS_NAME = "alias/AwsLandingZoneKMSKey"
|
1064
|
+
for alias in client.list_aliases()["Aliases"]:
|
1065
|
+
if alias["AliasName"] == ALIAS_NAME:
|
1066
|
+
print(f"Deleting alias {alias['AliasName']}", end=" ")
|
1067
|
+
client.delete_alias(AliasName=ALIAS_NAME)
|
1068
|
+
deleted_alias = True
|
1069
|
+
print("[DONE]")
|
1070
|
+
|
1071
|
+
if not deleted_alias:
|
1072
|
+
print(f"Alias {ALIAS_NAME} not found")
|
1073
|
+
|
1074
|
+
print("\nWe're done! All resources from your ALZ accounts are removed!")
|
1075
|
+
print(f"Total removal time {time.time() - start_time} seconds")
|