runbooks 0.2.5__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.
Files changed (221) hide show
  1. conftest.py +26 -0
  2. jupyter-agent/.env.template +2 -0
  3. jupyter-agent/.gitattributes +35 -0
  4. jupyter-agent/README.md +16 -0
  5. jupyter-agent/app.py +256 -0
  6. jupyter-agent/cloudops-agent.png +0 -0
  7. jupyter-agent/ds-system-prompt.txt +154 -0
  8. jupyter-agent/jupyter-agent.png +0 -0
  9. jupyter-agent/llama3_template.jinja +123 -0
  10. jupyter-agent/requirements.txt +9 -0
  11. jupyter-agent/utils.py +409 -0
  12. runbooks/__init__.py +71 -3
  13. runbooks/__main__.py +13 -0
  14. runbooks/aws/ec2_describe_instances.py +1 -1
  15. runbooks/aws/ec2_run_instances.py +8 -2
  16. runbooks/aws/ec2_start_stop_instances.py +17 -4
  17. runbooks/aws/ec2_unused_volumes.py +5 -1
  18. runbooks/aws/s3_create_bucket.py +4 -2
  19. runbooks/aws/s3_list_objects.py +6 -1
  20. runbooks/aws/tagging_lambda_handler.py +13 -2
  21. runbooks/aws/tags.json +12 -0
  22. runbooks/base.py +353 -0
  23. runbooks/cfat/README.md +49 -0
  24. runbooks/cfat/__init__.py +74 -0
  25. runbooks/cfat/app.ts +644 -0
  26. runbooks/cfat/assessment/__init__.py +40 -0
  27. runbooks/cfat/assessment/asana-import.csv +39 -0
  28. runbooks/cfat/assessment/cfat-checks.csv +31 -0
  29. runbooks/cfat/assessment/cfat.txt +520 -0
  30. runbooks/cfat/assessment/collectors.py +200 -0
  31. runbooks/cfat/assessment/jira-import.csv +39 -0
  32. runbooks/cfat/assessment/runner.py +387 -0
  33. runbooks/cfat/assessment/validators.py +290 -0
  34. runbooks/cfat/cli.py +103 -0
  35. runbooks/cfat/docs/asana-import.csv +24 -0
  36. runbooks/cfat/docs/cfat-checks.csv +31 -0
  37. runbooks/cfat/docs/cfat.txt +335 -0
  38. runbooks/cfat/docs/checks-output.png +0 -0
  39. runbooks/cfat/docs/cloudshell-console-run.png +0 -0
  40. runbooks/cfat/docs/cloudshell-download.png +0 -0
  41. runbooks/cfat/docs/cloudshell-output.png +0 -0
  42. runbooks/cfat/docs/downloadfile.png +0 -0
  43. runbooks/cfat/docs/jira-import.csv +24 -0
  44. runbooks/cfat/docs/open-cloudshell.png +0 -0
  45. runbooks/cfat/docs/report-header.png +0 -0
  46. runbooks/cfat/models.py +1026 -0
  47. runbooks/cfat/package-lock.json +5116 -0
  48. runbooks/cfat/package.json +38 -0
  49. runbooks/cfat/report.py +496 -0
  50. runbooks/cfat/reporting/__init__.py +46 -0
  51. runbooks/cfat/reporting/exporters.py +337 -0
  52. runbooks/cfat/reporting/formatters.py +496 -0
  53. runbooks/cfat/reporting/templates.py +135 -0
  54. runbooks/cfat/run-assessment.sh +23 -0
  55. runbooks/cfat/runner.py +69 -0
  56. runbooks/cfat/src/actions/check-cloudtrail-existence.ts +43 -0
  57. runbooks/cfat/src/actions/check-config-existence.ts +37 -0
  58. runbooks/cfat/src/actions/check-control-tower.ts +37 -0
  59. runbooks/cfat/src/actions/check-ec2-existence.ts +46 -0
  60. runbooks/cfat/src/actions/check-iam-users.ts +50 -0
  61. runbooks/cfat/src/actions/check-legacy-cur.ts +30 -0
  62. runbooks/cfat/src/actions/check-org-cloudformation.ts +30 -0
  63. runbooks/cfat/src/actions/check-vpc-existence.ts +43 -0
  64. runbooks/cfat/src/actions/create-asanaimport.ts +14 -0
  65. runbooks/cfat/src/actions/create-backlog.ts +372 -0
  66. runbooks/cfat/src/actions/create-jiraimport.ts +15 -0
  67. runbooks/cfat/src/actions/create-report.ts +616 -0
  68. runbooks/cfat/src/actions/define-account-type.ts +51 -0
  69. runbooks/cfat/src/actions/get-enabled-org-policy-types.ts +40 -0
  70. runbooks/cfat/src/actions/get-enabled-org-services.ts +26 -0
  71. runbooks/cfat/src/actions/get-idc-info.ts +34 -0
  72. runbooks/cfat/src/actions/get-org-da-accounts.ts +34 -0
  73. runbooks/cfat/src/actions/get-org-details.ts +35 -0
  74. runbooks/cfat/src/actions/get-org-member-accounts.ts +44 -0
  75. runbooks/cfat/src/actions/get-org-ous.ts +35 -0
  76. runbooks/cfat/src/actions/get-regions.ts +22 -0
  77. runbooks/cfat/src/actions/zip-assessment.ts +27 -0
  78. runbooks/cfat/src/types/index.d.ts +147 -0
  79. runbooks/cfat/tests/__init__.py +141 -0
  80. runbooks/cfat/tests/test_cli.py +340 -0
  81. runbooks/cfat/tests/test_integration.py +290 -0
  82. runbooks/cfat/tests/test_models.py +505 -0
  83. runbooks/cfat/tests/test_reporting.py +354 -0
  84. runbooks/cfat/tsconfig.json +16 -0
  85. runbooks/cfat/webpack.config.cjs +27 -0
  86. runbooks/config.py +260 -0
  87. runbooks/finops/__init__.py +88 -0
  88. runbooks/finops/aws_client.py +245 -0
  89. runbooks/finops/cli.py +151 -0
  90. runbooks/finops/cost_processor.py +410 -0
  91. runbooks/finops/dashboard_runner.py +448 -0
  92. runbooks/finops/helpers.py +355 -0
  93. runbooks/finops/main.py +14 -0
  94. runbooks/finops/profile_processor.py +174 -0
  95. runbooks/finops/types.py +66 -0
  96. runbooks/finops/visualisations.py +80 -0
  97. runbooks/inventory/.gitignore +354 -0
  98. runbooks/inventory/ArgumentsClass.py +261 -0
  99. runbooks/inventory/Inventory_Modules.py +6130 -0
  100. runbooks/inventory/LandingZone/delete_lz.py +1075 -0
  101. runbooks/inventory/README.md +1320 -0
  102. runbooks/inventory/__init__.py +62 -0
  103. runbooks/inventory/account_class.py +532 -0
  104. runbooks/inventory/all_my_instances_wrapper.py +123 -0
  105. runbooks/inventory/aws_decorators.py +201 -0
  106. runbooks/inventory/cfn_move_stack_instances.py +1526 -0
  107. runbooks/inventory/check_cloudtrail_compliance.py +614 -0
  108. runbooks/inventory/check_controltower_readiness.py +1107 -0
  109. runbooks/inventory/check_landingzone_readiness.py +711 -0
  110. runbooks/inventory/cloudtrail.md +727 -0
  111. runbooks/inventory/collectors/__init__.py +20 -0
  112. runbooks/inventory/collectors/aws_compute.py +518 -0
  113. runbooks/inventory/collectors/aws_networking.py +275 -0
  114. runbooks/inventory/collectors/base.py +222 -0
  115. runbooks/inventory/core/__init__.py +19 -0
  116. runbooks/inventory/core/collector.py +303 -0
  117. runbooks/inventory/core/formatter.py +296 -0
  118. runbooks/inventory/delete_s3_buckets_objects.py +169 -0
  119. runbooks/inventory/discovery.md +81 -0
  120. runbooks/inventory/draw_org_structure.py +748 -0
  121. runbooks/inventory/ec2_vpc_utils.py +341 -0
  122. runbooks/inventory/find_cfn_drift_detection.py +272 -0
  123. runbooks/inventory/find_cfn_orphaned_stacks.py +719 -0
  124. runbooks/inventory/find_cfn_stackset_drift.py +733 -0
  125. runbooks/inventory/find_ec2_security_groups.py +669 -0
  126. runbooks/inventory/find_landingzone_versions.py +201 -0
  127. runbooks/inventory/find_vpc_flow_logs.py +1221 -0
  128. runbooks/inventory/inventory.sh +659 -0
  129. runbooks/inventory/list_cfn_stacks.py +558 -0
  130. runbooks/inventory/list_cfn_stackset_operation_results.py +252 -0
  131. runbooks/inventory/list_cfn_stackset_operations.py +734 -0
  132. runbooks/inventory/list_cfn_stacksets.py +453 -0
  133. runbooks/inventory/list_config_recorders_delivery_channels.py +681 -0
  134. runbooks/inventory/list_ds_directories.py +354 -0
  135. runbooks/inventory/list_ec2_availability_zones.py +286 -0
  136. runbooks/inventory/list_ec2_ebs_volumes.py +244 -0
  137. runbooks/inventory/list_ec2_instances.py +425 -0
  138. runbooks/inventory/list_ecs_clusters_and_tasks.py +562 -0
  139. runbooks/inventory/list_elbs_load_balancers.py +411 -0
  140. runbooks/inventory/list_enis_network_interfaces.py +526 -0
  141. runbooks/inventory/list_guardduty_detectors.py +568 -0
  142. runbooks/inventory/list_iam_policies.py +404 -0
  143. runbooks/inventory/list_iam_roles.py +518 -0
  144. runbooks/inventory/list_iam_saml_providers.py +359 -0
  145. runbooks/inventory/list_lambda_functions.py +882 -0
  146. runbooks/inventory/list_org_accounts.py +446 -0
  147. runbooks/inventory/list_org_accounts_users.py +354 -0
  148. runbooks/inventory/list_rds_db_instances.py +406 -0
  149. runbooks/inventory/list_route53_hosted_zones.py +318 -0
  150. runbooks/inventory/list_servicecatalog_provisioned_products.py +575 -0
  151. runbooks/inventory/list_sns_topics.py +360 -0
  152. runbooks/inventory/list_ssm_parameters.py +402 -0
  153. runbooks/inventory/list_vpc_subnets.py +433 -0
  154. runbooks/inventory/list_vpcs.py +422 -0
  155. runbooks/inventory/lockdown_cfn_stackset_role.py +224 -0
  156. runbooks/inventory/models/__init__.py +24 -0
  157. runbooks/inventory/models/account.py +192 -0
  158. runbooks/inventory/models/inventory.py +309 -0
  159. runbooks/inventory/models/resource.py +247 -0
  160. runbooks/inventory/recover_cfn_stack_ids.py +205 -0
  161. runbooks/inventory/requirements.txt +12 -0
  162. runbooks/inventory/run_on_multi_accounts.py +211 -0
  163. runbooks/inventory/tests/common_test_data.py +3661 -0
  164. runbooks/inventory/tests/common_test_functions.py +204 -0
  165. runbooks/inventory/tests/script_test_data.py +0 -0
  166. runbooks/inventory/tests/setup.py +24 -0
  167. runbooks/inventory/tests/src.py +18 -0
  168. runbooks/inventory/tests/test_cfn_describe_stacks.py +208 -0
  169. runbooks/inventory/tests/test_ec2_describe_instances.py +162 -0
  170. runbooks/inventory/tests/test_inventory_modules.py +55 -0
  171. runbooks/inventory/tests/test_lambda_list_functions.py +86 -0
  172. runbooks/inventory/tests/test_moto_integration_example.py +273 -0
  173. runbooks/inventory/tests/test_org_list_accounts.py +49 -0
  174. runbooks/inventory/update_aws_actions.py +173 -0
  175. runbooks/inventory/update_cfn_stacksets.py +1215 -0
  176. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +294 -0
  177. runbooks/inventory/update_iam_roles_cross_accounts.py +478 -0
  178. runbooks/inventory/update_s3_public_access_block.py +539 -0
  179. runbooks/inventory/utils/__init__.py +23 -0
  180. runbooks/inventory/utils/aws_helpers.py +510 -0
  181. runbooks/inventory/utils/threading_utils.py +493 -0
  182. runbooks/inventory/utils/validation.py +682 -0
  183. runbooks/inventory/verify_ec2_security_groups.py +1430 -0
  184. runbooks/main.py +785 -0
  185. runbooks/organizations/__init__.py +12 -0
  186. runbooks/organizations/manager.py +374 -0
  187. runbooks/security_baseline/README.md +324 -0
  188. runbooks/security_baseline/checklist/alternate_contacts.py +8 -1
  189. runbooks/security_baseline/checklist/bucket_public_access.py +4 -1
  190. runbooks/security_baseline/checklist/cloudwatch_alarm_configuration.py +9 -2
  191. runbooks/security_baseline/checklist/guardduty_enabled.py +9 -2
  192. runbooks/security_baseline/checklist/multi_region_instance_usage.py +5 -1
  193. runbooks/security_baseline/checklist/root_access_key.py +6 -1
  194. runbooks/security_baseline/config-origin.json +1 -1
  195. runbooks/security_baseline/config.json +1 -1
  196. runbooks/security_baseline/permission.json +1 -1
  197. runbooks/security_baseline/report_generator.py +10 -2
  198. runbooks/security_baseline/report_template_en.html +7 -7
  199. runbooks/security_baseline/report_template_jp.html +7 -7
  200. runbooks/security_baseline/report_template_kr.html +12 -12
  201. runbooks/security_baseline/report_template_vn.html +7 -7
  202. runbooks/security_baseline/requirements.txt +7 -0
  203. runbooks/security_baseline/run_script.py +8 -2
  204. runbooks/security_baseline/security_baseline_tester.py +10 -2
  205. runbooks/security_baseline/utils/common.py +5 -1
  206. runbooks/utils/__init__.py +204 -0
  207. runbooks-0.6.1.dist-info/METADATA +373 -0
  208. runbooks-0.6.1.dist-info/RECORD +237 -0
  209. {runbooks-0.2.5.dist-info → runbooks-0.6.1.dist-info}/WHEEL +1 -1
  210. runbooks-0.6.1.dist-info/entry_points.txt +7 -0
  211. runbooks-0.6.1.dist-info/licenses/LICENSE +201 -0
  212. runbooks-0.6.1.dist-info/top_level.txt +3 -0
  213. runbooks/python101/calculator.py +0 -34
  214. runbooks/python101/config.py +0 -1
  215. runbooks/python101/exceptions.py +0 -16
  216. runbooks/python101/file_manager.py +0 -218
  217. runbooks/python101/toolkit.py +0 -153
  218. runbooks-0.2.5.dist-info/METADATA +0 -439
  219. runbooks-0.2.5.dist-info/RECORD +0 -61
  220. runbooks-0.2.5.dist-info/entry_points.txt +0 -3
  221. runbooks-0.2.5.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")