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,341 @@
|
|
1
|
+
def del_vpc(ocredentials, fVPCId, fRegion):
|
2
|
+
"""
|
3
|
+
ocredentials looks like this:
|
4
|
+
session_vpc = boto3.Session(
|
5
|
+
aws_access_key_id = ocredentials['AccessKeyId'],
|
6
|
+
aws_secret_access_key = ocredentials['SecretAccessKey'],
|
7
|
+
aws_session_token = ocredentials['SessionToken'],
|
8
|
+
region_name=fRegion)
|
9
|
+
fVPCId is a string with the VPC ID in it
|
10
|
+
fRegion is the string that represents the AWS region name
|
11
|
+
"""
|
12
|
+
|
13
|
+
import logging
|
14
|
+
|
15
|
+
import boto3
|
16
|
+
from botocore.exceptions import ClientError
|
17
|
+
from colorama import Fore, init
|
18
|
+
|
19
|
+
# ERASE_LINE = '\x1b[2K'
|
20
|
+
|
21
|
+
def find_and_delete_vpc_endpoints(fVPC_client, fVpcId, fRegion):
|
22
|
+
import logging
|
23
|
+
|
24
|
+
vpc_endpoints = fVPC_client.describe_vpc_endpoints(Filters=[{"Name": "vpc-id", "Values": [fVpcId]}])
|
25
|
+
vpc_endpoints_to_delete = []
|
26
|
+
logging.info("Found %s vpc endpoints", len(vpc_endpoints))
|
27
|
+
for x in range(len(vpc_endpoints["VpcEndpoints"])):
|
28
|
+
vpc_endpoints_to_delete.append(vpc_endpoints["VpcEndpoints"][x]["VpcEndpointId"])
|
29
|
+
|
30
|
+
logging.warning("Found %s endpoints in vpc", len(vpc_endpoints_to_delete))
|
31
|
+
if len(vpc_endpoints_to_delete) > 0:
|
32
|
+
try:
|
33
|
+
response = fVPC_client.delete_vpc_endpoints(VpcEndpointIds=vpc_endpoints_to_delete)
|
34
|
+
return 0
|
35
|
+
except ClientError as my_error:
|
36
|
+
print(my_error)
|
37
|
+
return 1
|
38
|
+
else:
|
39
|
+
logging.warning("No Endpoints found to delete")
|
40
|
+
return 0
|
41
|
+
|
42
|
+
def find_and_delete_vpc_security_groups(fVPC_client, fVpcId, fRegion):
|
43
|
+
from botocore.exceptions import ClientError
|
44
|
+
|
45
|
+
vpc_security_groups = fVPC_client.describe_security_groups(Filters=[{"Name": "vpc-id", "Values": [fVpcId]}])
|
46
|
+
for x in range(len(vpc_security_groups["SecurityGroups"])):
|
47
|
+
if vpc_security_groups["SecurityGroups"][x]["GroupName"] == "default":
|
48
|
+
logging.info("Only found default security groups. These will auto-delete")
|
49
|
+
continue
|
50
|
+
else:
|
51
|
+
try:
|
52
|
+
logging.info("Deleting security group %s", vpc_security_groups["SecurityGroups"][x]["GroupId"])
|
53
|
+
response = fVPC_client.delete_security_group(
|
54
|
+
GroupId=vpc_security_groups["SecurityGroups"][x]["GroupId"]
|
55
|
+
)
|
56
|
+
except ClientError as my_Error:
|
57
|
+
print(my_Error)
|
58
|
+
return 1
|
59
|
+
return 0
|
60
|
+
|
61
|
+
def find_and_delete_vpc_peering_connections(fVPC_client, fVpcId, fRegion):
|
62
|
+
from botocore.exceptions import ClientError
|
63
|
+
|
64
|
+
vpc_peering_connections = fVPC_client.describe_vpc_peering_connections(
|
65
|
+
Filters=[{"Name": "requester-vpc-info.vpc-id", "Values": [fVpcId]}]
|
66
|
+
)
|
67
|
+
for x in range(len(vpc_peering_connections["VpcPeeringConnections"])):
|
68
|
+
try:
|
69
|
+
response = fVPC_client.delete_vpc_peering_connection(
|
70
|
+
VpcPeeringConnectionId=vpc_peering_connections["VpcPeeringConnections"][x]["VpcPeeringConnectionId"]
|
71
|
+
)
|
72
|
+
except ClientError as my_Error:
|
73
|
+
print(my_Error)
|
74
|
+
return 1
|
75
|
+
return 0
|
76
|
+
|
77
|
+
def find_and_delete_vpc_route_tables(fVPC_client, fVpcId, fRegion):
|
78
|
+
from botocore.exceptions import ClientError
|
79
|
+
|
80
|
+
vpc_route_tables = fVPC_client.describe_route_tables(Filters=[{"Name": "vpc-id", "Values": [fVpcId]}])
|
81
|
+
vpc_route_tables_to_delete = list()
|
82
|
+
for x in range(len(vpc_route_tables["RouteTables"])):
|
83
|
+
rRouteTableId = vpc_route_tables["RouteTables"][x]["RouteTableId"]
|
84
|
+
# Add all route tables to the delete list...
|
85
|
+
vpc_route_tables_to_delete.append(rRouteTableId)
|
86
|
+
for y in range(len(vpc_route_tables["RouteTables"][x]["Associations"])):
|
87
|
+
rIsMain = vpc_route_tables["RouteTables"][x]["Associations"][y]["Main"]
|
88
|
+
# However, since we can't disassociate the "Main" Route Table, and we can't delete it, we remove it from our list.
|
89
|
+
if rIsMain:
|
90
|
+
vpc_route_tables_to_delete.remove(rRouteTableId)
|
91
|
+
continue
|
92
|
+
rRouteAssociation = vpc_route_tables["RouteTables"][x]["Associations"][y]["RouteTableAssociationId"]
|
93
|
+
try:
|
94
|
+
disassociateresponse = fVPC_client.disassociate_route_table(AssociationId=rRouteAssociation)
|
95
|
+
logging.critical("Disassociated Route Table ID: %s", rRouteTableId)
|
96
|
+
except ClientError as my_Error:
|
97
|
+
print(my_Error)
|
98
|
+
return 1
|
99
|
+
|
100
|
+
# pprint.pprint(vpc_route_tables_to_delete)
|
101
|
+
for RtTbl in vpc_route_tables_to_delete:
|
102
|
+
try:
|
103
|
+
deleteresponse = fVPC_client.delete_route_table(RouteTableId=RtTbl)
|
104
|
+
print("Deleted Route Table ID:", RtTbl)
|
105
|
+
vpc_route_tables_to_delete.remove(RtTbl)
|
106
|
+
except ClientError as my_Error:
|
107
|
+
print(my_Error)
|
108
|
+
return 1
|
109
|
+
return 0
|
110
|
+
|
111
|
+
def find_and_delete_vpc_nacls(fVPC_client, fVpcId, fRegion):
|
112
|
+
from botocore.exceptions import ClientError
|
113
|
+
|
114
|
+
vpc_nacls = fVPC_client.describe_network_acls(Filters=[{"Name": "vpc-id", "Values": [fVpcId]}])
|
115
|
+
|
116
|
+
for x in range(len(vpc_nacls["NetworkAcls"])):
|
117
|
+
if vpc_nacls["NetworkAcls"][x]["IsDefault"]:
|
118
|
+
continue
|
119
|
+
else:
|
120
|
+
try:
|
121
|
+
response = fVPC_client.delete_network_acl(NetworkAclId=vpc_nacls["NetworkAcls"][x]["NetworkAclId"])
|
122
|
+
# pprint.pprint(response)
|
123
|
+
except ClientError as my_Error:
|
124
|
+
print(my_Error)
|
125
|
+
return 1
|
126
|
+
return 0
|
127
|
+
|
128
|
+
def find_and_delete_subnets(fVPC_client, fVpcId, fRegion):
|
129
|
+
from botocore.exceptions import ClientError
|
130
|
+
|
131
|
+
subnets = fVPC_client.describe_subnets(Filters=[{"Name": "vpc-id", "Values": [fVpcId]}])
|
132
|
+
# print("Found",len(subnets['Subnets']),"Subnets")
|
133
|
+
|
134
|
+
# pprint.pprint(vpc_nacls)
|
135
|
+
# pprint.pprint(vpc_nacls['NetworkAcls'][0]['IsDefault'])
|
136
|
+
# print("There are "+str(len(subnets['Subnets']))+" subnets")
|
137
|
+
# print("There are "+str(len(vpc_route_tables['RouteTables'][0]['Routes']))+" routes in the first table")
|
138
|
+
for x in range(len(subnets["Subnets"])):
|
139
|
+
try:
|
140
|
+
rSubnetId = subnets["Subnets"][x]["SubnetId"]
|
141
|
+
response = fVPC_client.delete_subnet(SubnetId=rSubnetId)
|
142
|
+
# pprint.pprint(response)
|
143
|
+
except ClientError as my_Error:
|
144
|
+
print(my_Error)
|
145
|
+
return 1
|
146
|
+
return 0
|
147
|
+
|
148
|
+
def find_and_delete_NAT_gateways(fVPC_client, fVpcId, fRegion):
|
149
|
+
import time
|
150
|
+
|
151
|
+
from botocore.exceptions import ClientError
|
152
|
+
|
153
|
+
cyclesWaited = 0
|
154
|
+
nat_gateways = fVPC_client.describe_nat_gateways(
|
155
|
+
Filters=[{"Name": "vpc-id", "Values": [fVpcId]}, {"Name": "state", "Values": ["available"]}]
|
156
|
+
)
|
157
|
+
rNatGWList = []
|
158
|
+
if len(nat_gateways["NatGateways"]) > 0:
|
159
|
+
logging.info("Found %s NAT Gateways", len(nat_gateways["NatGateways"]))
|
160
|
+
for x in range(len(nat_gateways["NatGateways"])):
|
161
|
+
rNatGWList.append(nat_gateways["NatGateways"][x]["NatGatewayId"])
|
162
|
+
try:
|
163
|
+
deleteresponse = fVPC_client.delete_nat_gateway(NatGatewayId=rNatGWList[x])
|
164
|
+
except ClientError as my_Error:
|
165
|
+
print(my_Error)
|
166
|
+
return 1
|
167
|
+
print("Waiting for the NAT Gateways to be fully deleted")
|
168
|
+
verify_nat_gws_are_gone = nat_gateways
|
169
|
+
while len(verify_nat_gws_are_gone["NatGateways"]) > 0:
|
170
|
+
verify_nat_gws_are_gone = fVPC_client.describe_nat_gateways(
|
171
|
+
Filters=[{"Name": "state", "Values": ["available", "pending", "deleting", "failed"]}],
|
172
|
+
NatGatewayIds=rNatGWList,
|
173
|
+
)
|
174
|
+
cyclesWaited += 1
|
175
|
+
if cyclesWaited % 5 == 0:
|
176
|
+
logging.info("Still waiting on NAT Gateways to be deleted...")
|
177
|
+
# print(".", end='', flush=True)
|
178
|
+
time.sleep(10)
|
179
|
+
return 0
|
180
|
+
|
181
|
+
def find_and_delete_gateways(fVPC_client, fVpcId, fRegion):
|
182
|
+
from botocore.exceptions import ClientError
|
183
|
+
|
184
|
+
gateways = fVPC_client.describe_internet_gateways(Filters=[{"Name": "attachment.vpc-id", "Values": [fVpcId]}])
|
185
|
+
for x in range(len(gateways["InternetGateways"])):
|
186
|
+
rGatewayId = gateways["InternetGateways"][x]["InternetGatewayId"]
|
187
|
+
try:
|
188
|
+
detachresponse = fVPC_client.detach_internet_gateway(InternetGatewayId=rGatewayId, VpcId=fVpcId)
|
189
|
+
except ClientError as my_Error:
|
190
|
+
print(my_Error)
|
191
|
+
return 1
|
192
|
+
try:
|
193
|
+
deleteresponse = fVPC_client.delete_internet_gateway(InternetGatewayId=rGatewayId)
|
194
|
+
except ClientError as my_Error:
|
195
|
+
print(my_Error)
|
196
|
+
return 1
|
197
|
+
return 0
|
198
|
+
|
199
|
+
def find_and_delete_virtual_gateways(fVPC_client, fVpcId, fRegion):
|
200
|
+
import time
|
201
|
+
|
202
|
+
from botocore.exceptions import ClientError
|
203
|
+
|
204
|
+
cyclesWaited = 0
|
205
|
+
vgws = fVPC_client.describe_vpn_gateways(
|
206
|
+
Filters=[
|
207
|
+
{"Name": "attachment.vpc-id", "Values": [fVpcId]},
|
208
|
+
{"Name": "attachment.state", "Values": ["attached"]},
|
209
|
+
]
|
210
|
+
)
|
211
|
+
rVPN_GatewayList = list()
|
212
|
+
for x in range(len(vgws["VpnGateways"])):
|
213
|
+
rVPN_GatewayList.append(vgws["VpnGateways"][x]["VpnGatewayId"])
|
214
|
+
try:
|
215
|
+
detachresponse = fVPC_client.detach_vpn_gateway(VpnGatewayId=rVPN_GatewayList[x], VpcId=fVpcId)
|
216
|
+
except ClientError as my_Error:
|
217
|
+
print(my_Error)
|
218
|
+
return 1
|
219
|
+
|
220
|
+
print("Waiting for the VPN Gateways to be fully detached")
|
221
|
+
verify_vgws_are_gone = vgws
|
222
|
+
while len(verify_vgws_are_gone["VpnGateways"]) > 0:
|
223
|
+
verify_vgws_are_gone = fVPC_client.describe_vpn_gateways(
|
224
|
+
Filters=[
|
225
|
+
{"Name": "attachment.state", "Values": ["attached", "detaching"]},
|
226
|
+
],
|
227
|
+
VpnGatewayIds=rVPN_GatewayList,
|
228
|
+
)
|
229
|
+
cyclesWaited += 1
|
230
|
+
if cyclesWaited % 5 == 0:
|
231
|
+
logging.info("Still waiting on VGWS to be deleted...")
|
232
|
+
# pprint.pprint(verify_nat_gws_are_gone)
|
233
|
+
time.sleep(10)
|
234
|
+
return 0
|
235
|
+
|
236
|
+
def delete_vpc(fVPC_client, fVpcId, fRegion):
|
237
|
+
from botocore.exceptions import ClientError
|
238
|
+
|
239
|
+
try:
|
240
|
+
response = fVPC_client.delete_vpc(VpcId=fVpcId)
|
241
|
+
return 0
|
242
|
+
except ClientError as my_Error:
|
243
|
+
print(my_Error)
|
244
|
+
return 1
|
245
|
+
|
246
|
+
###### Main ########################################
|
247
|
+
session_vpc = boto3.Session(
|
248
|
+
aws_access_key_id=ocredentials["AccessKeyId"],
|
249
|
+
aws_secret_access_key=ocredentials["SecretAccessKey"],
|
250
|
+
aws_session_token=ocredentials["SessionToken"],
|
251
|
+
region_name=fRegion,
|
252
|
+
)
|
253
|
+
client_vpc = session_vpc.client("ec2")
|
254
|
+
try:
|
255
|
+
# 1. Call EC2.Client.describe_vpc_endpoints. Filter on your VPC id. Call EC2.client.delete_vpc_endpoints on each
|
256
|
+
print(f"Deleting vpc in {fRegion}...", end="", flush=True)
|
257
|
+
|
258
|
+
logging.info(f"Deleting vpc-endpoints... for vpc {fVPCId} in region {fRegion}")
|
259
|
+
print(".", end="", flush=True)
|
260
|
+
ResultGood = find_and_delete_vpc_endpoints(client_vpc, fVPCId, fRegion) == 0
|
261
|
+
if not ResultGood:
|
262
|
+
logging.error("Something failed in the vpc_endpoints deletion script")
|
263
|
+
return 1 # out of the try
|
264
|
+
# 2. Call VPC.security_groups. Delete the group unless its group_name attribute is "main". The main security group will be deleted via VPC.delete().
|
265
|
+
logging.info("Deleting security groups...")
|
266
|
+
print(".", end="", flush=True)
|
267
|
+
ResultGood = find_and_delete_vpc_security_groups(client_vpc, fVPCId, fRegion) == 0
|
268
|
+
if not ResultGood:
|
269
|
+
logging.error("Something failed in the vpc_security_group deletion script")
|
270
|
+
return 1 # out of the try
|
271
|
+
|
272
|
+
# 3. Call EC2.Client.describe_vpc_peering_connections. Filter on your VPC id as the requester-vpc-info.vpc-id. (My VPC is a requester. There is also accepter-vpc-info.vpc-id among other filters.) Iterate through the entries keyed by VpcPeeringConnections. Get an instance of the peering connection by instantiating a EC2.ServiceResource.VpcPeeringConnection with the VpcPeeringConnectionId. Call VpcPeeringConnection.delete() to remove the peering connection.
|
273
|
+
logging.info("Deleting vpc peering connections...")
|
274
|
+
print(".", end="", flush=True)
|
275
|
+
ResultGood = find_and_delete_vpc_peering_connections(client_vpc, fVPCId, fRegion) == 0
|
276
|
+
if not ResultGood:
|
277
|
+
logging.error("Something failed in the vpc_peering_connection deletion script")
|
278
|
+
return 1 # out of the try
|
279
|
+
|
280
|
+
# Need to figure a way to wait on this operation, if there are Gateways to be deleted.
|
281
|
+
logging.info("Deleting NAT Gateways...")
|
282
|
+
print(".", end="", flush=True)
|
283
|
+
ResultGood = find_and_delete_NAT_gateways(client_vpc, fVPCId, fRegion) == 0
|
284
|
+
if not ResultGood:
|
285
|
+
logging.error("Something failed in the vpc_NAT_gateways deletion script")
|
286
|
+
return 1 # out of the try
|
287
|
+
|
288
|
+
# 4. Call vpc.route_tables.all() and iterate through the route tables. For each route table, iterate through its routes using the RouteTable.routes attribute. Delete the routes where route['Origin'] is 'CreateRoute'. I deleted using EC2.Client.delete_route using EC2.RouteTable.id and route['DestinationCidrBlock']. After removing the routes, call EC2.RouteTable.delete() to remove the route table itself. I set up exception handlers for each delete. Not every route table can be deleted, but I haven't cracked the code. Maybe next week.
|
289
|
+
logging.info("Deleting vpc route tables...")
|
290
|
+
print(".", end="", flush=True)
|
291
|
+
ResultGood = find_and_delete_vpc_route_tables(client_vpc, fVPCId, fRegion) == 0
|
292
|
+
if not ResultGood:
|
293
|
+
logging.error("Something failed in the vpc_route_tables deletion script")
|
294
|
+
return 1 # out of the try
|
295
|
+
|
296
|
+
# 5. Iterate through vpc.network_acls.all(), test12 the NetworkAcl.is_default attribute and call NetworkAcl.delete for non-default acls.
|
297
|
+
logging.info("Deleting vpc network access control lists...")
|
298
|
+
print(".", end="", flush=True)
|
299
|
+
ResultGood = find_and_delete_vpc_nacls(client_vpc, fVPCId, fRegion) == 0
|
300
|
+
if not ResultGood:
|
301
|
+
logging.error("Something failed in the vpc_nacls deletion script")
|
302
|
+
return 1 # out of the try
|
303
|
+
|
304
|
+
#
|
305
|
+
# 6. Iterate through vpc.subnets.all().network_interfaces.all(). Call EC2.NetworkInterface.delete() on each.
|
306
|
+
logging.info("Deleting subnets...")
|
307
|
+
print(".", end="", flush=True)
|
308
|
+
ResultGood = find_and_delete_subnets(client_vpc, fVPCId, fRegion) == 0
|
309
|
+
if not ResultGood:
|
310
|
+
logging.error("Something failed in the vpc_subnets deletion script")
|
311
|
+
return 1 # out of the try
|
312
|
+
#
|
313
|
+
# 7. Iterate through vpc.internet_gateways.all(). Call EC2.InternetGateway.delete() on each.
|
314
|
+
logging.info("Deleting Internet Gateways...")
|
315
|
+
print(".", end="", flush=True)
|
316
|
+
ResultGood = find_and_delete_gateways(client_vpc, fVPCId, fRegion) == 0
|
317
|
+
if not ResultGood:
|
318
|
+
logging.error("Something failed in the vpc_gateways deletion script")
|
319
|
+
return 1 # out of the try
|
320
|
+
|
321
|
+
# Virtual Gateway
|
322
|
+
logging.info("Deleting Virtual Customer Gateways...")
|
323
|
+
print(".", end="", flush=True)
|
324
|
+
ResultGood = find_and_delete_virtual_gateways(client_vpc, fVPCId, fRegion) == 0
|
325
|
+
if not ResultGood:
|
326
|
+
logging.error("Something failed in the vpc_virtual_gateways deletion script")
|
327
|
+
return 1 # out of the try
|
328
|
+
|
329
|
+
#
|
330
|
+
# 8. Call vpc.delete()
|
331
|
+
print("!")
|
332
|
+
ResultGood = delete_vpc(client_vpc, fVPCId, fRegion) == 0
|
333
|
+
if not ResultGood:
|
334
|
+
logging.error("Something failed in the final vpc deletion script")
|
335
|
+
return 1 # out of the try
|
336
|
+
except ClientError as my_Error:
|
337
|
+
print(my_Error)
|
338
|
+
print(f"{Fore.RED}What to do now?{Fore.RESET}")
|
339
|
+
return 1
|
340
|
+
|
341
|
+
return 0
|
@@ -0,0 +1,272 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
"""
|
4
|
+
AWS CloudFormation Stack Drift Detection Enablement Script
|
5
|
+
|
6
|
+
This enterprise-grade script provides comprehensive CloudFormation drift detection enablement
|
7
|
+
across multi-account AWS Organizations environments. Designed for infrastructure teams managing
|
8
|
+
CloudFormation stacks at scale, offering automated drift detection initialization, stack
|
9
|
+
discovery with filtering capabilities, and organizational governance support for infrastructure
|
10
|
+
configuration management and compliance monitoring.
|
11
|
+
|
12
|
+
Key Features:
|
13
|
+
- Multi-account, multi-region CloudFormation stack discovery and drift detection enablement
|
14
|
+
- Fragment-based stack filtering for targeted drift detection operations
|
15
|
+
- Stack status filtering supporting active and deleted stack analysis
|
16
|
+
- Enterprise governance support with organizational context and account exclusion
|
17
|
+
- Comprehensive error handling for authorization and access control issues
|
18
|
+
- Progress tracking and operational feedback for large-scale drift detection operations
|
19
|
+
|
20
|
+
Drift Detection Capabilities:
|
21
|
+
- Automated drift detection enablement for discovered CloudFormation stacks
|
22
|
+
- Stack-level drift detection initialization with operational logging
|
23
|
+
- Regional drift detection coverage for comprehensive infrastructure monitoring
|
24
|
+
- Fragment-based stack targeting for selective drift detection operations
|
25
|
+
- Status-based stack filtering for active vs historical stack analysis
|
26
|
+
|
27
|
+
Authentication & Access:
|
28
|
+
- AWS Organizations support for centralized CloudFormation stack management
|
29
|
+
- Cross-account role assumption for organizational stack visibility
|
30
|
+
- Regional validation and opt-in status verification for CloudFormation availability
|
31
|
+
- Profile-based authentication with comprehensive credential management
|
32
|
+
|
33
|
+
Performance & Scalability:
|
34
|
+
- Progress bars and operational feedback for large-scale drift detection operations
|
35
|
+
- Efficient credential management for multi-account stack enumeration
|
36
|
+
- Regional optimization with targeted CloudFormation API calls
|
37
|
+
- Memory-efficient processing for extensive stack inventories
|
38
|
+
|
39
|
+
Enterprise Use Cases:
|
40
|
+
- Infrastructure configuration drift detection and compliance monitoring
|
41
|
+
- CloudFormation stack governance and change management automation
|
42
|
+
- Organizational infrastructure audit and drift analysis
|
43
|
+
- Automated configuration compliance validation across organizational accounts
|
44
|
+
|
45
|
+
Security & Compliance:
|
46
|
+
- Account exclusion capabilities preventing drift detection on sensitive accounts
|
47
|
+
- Comprehensive audit logging for drift detection operations and stack access
|
48
|
+
- Regional access validation preventing unauthorized stack enumeration
|
49
|
+
- Safe credential handling with automatic session management
|
50
|
+
|
51
|
+
Dependencies:
|
52
|
+
- boto3: AWS SDK for CloudFormation API access
|
53
|
+
- colorama: Terminal output formatting and colored display
|
54
|
+
- Custom modules: Inventory_Modules, ArgumentsClass, account_class for enterprise operations
|
55
|
+
|
56
|
+
Output Format:
|
57
|
+
- Real-time progress feedback with account and region context
|
58
|
+
- Comprehensive drift detection summary with stack counts and regional coverage
|
59
|
+
- Operational logging for audit trails and troubleshooting
|
60
|
+
|
61
|
+
Future Enhancements:
|
62
|
+
- Drift detection status monitoring using describe_stack_drift_detection_status
|
63
|
+
- Enhanced progress tracking and drift detection result analysis
|
64
|
+
- Multi-threaded processing for improved performance at scale
|
65
|
+
"""
|
66
|
+
|
67
|
+
import logging
|
68
|
+
|
69
|
+
import Inventory_Modules
|
70
|
+
from account_class import aws_acct_access
|
71
|
+
from ArgumentsClass import CommonArguments
|
72
|
+
from botocore.exceptions import ClientError
|
73
|
+
from colorama import Fore, init
|
74
|
+
|
75
|
+
init()
|
76
|
+
__version__ = "2023.05.04"
|
77
|
+
|
78
|
+
# Configure comprehensive argument parsing for CloudFormation drift detection operations
|
79
|
+
parser = CommonArguments()
|
80
|
+
parser.singleprofile() # Single AWS profile for organizational drift detection management
|
81
|
+
parser.multiregion() # Multi-region support for comprehensive CloudFormation coverage
|
82
|
+
parser.verbosity() # Logging verbosity controls for operational visibility
|
83
|
+
parser.version(__version__)
|
84
|
+
|
85
|
+
# Enhanced CLI arguments for targeted CloudFormation drift detection operations
|
86
|
+
parser.my_parser.add_argument(
|
87
|
+
"-f",
|
88
|
+
"--fragment",
|
89
|
+
dest="pstackfrag",
|
90
|
+
metavar="CloudFormation stack fragment",
|
91
|
+
default="all",
|
92
|
+
help="String fragment of CloudFormation stack names for targeted drift detection filtering. "
|
93
|
+
"Supports partial name matching for flexible stack identification and selective drift analysis.",
|
94
|
+
)
|
95
|
+
parser.my_parser.add_argument(
|
96
|
+
"-s",
|
97
|
+
"--status",
|
98
|
+
dest="pstatus",
|
99
|
+
metavar="CloudFormation status",
|
100
|
+
default="active",
|
101
|
+
help="Stack status filter determining drift detection scope. "
|
102
|
+
"'active' for CREATE_COMPLETE stacks only, 'all' includes DELETE_COMPLETE stacks for historical analysis.",
|
103
|
+
)
|
104
|
+
parser.my_parser.add_argument(
|
105
|
+
"-k",
|
106
|
+
"--skip",
|
107
|
+
dest="pSkipAccounts",
|
108
|
+
nargs="*",
|
109
|
+
metavar="Accounts to leave alone",
|
110
|
+
default=[],
|
111
|
+
help="Account exclusion list preventing drift detection on sensitive or core organizational accounts. "
|
112
|
+
"Provides safety controls for production and management account protection.",
|
113
|
+
)
|
114
|
+
args = parser.my_parser.parse_args()
|
115
|
+
|
116
|
+
# Extract and configure parameters from parsed command-line arguments
|
117
|
+
pProfile = args.Profile # AWS profile for organizational CloudFormation access
|
118
|
+
pRegionList = args.Regions # Target regions for comprehensive drift detection coverage
|
119
|
+
pstackfrag = args.pstackfrag # Stack name fragments for targeted drift detection filtering
|
120
|
+
pstatus = args.pstatus # Stack status filter for active vs historical analysis
|
121
|
+
AccountsToSkip = args.pSkipAccounts # Account exclusion list for production safety
|
122
|
+
verbose = args.loglevel # Logging verbosity for operational visibility
|
123
|
+
|
124
|
+
# Configure comprehensive logging for CloudFormation drift detection operations
|
125
|
+
logging.basicConfig(level=verbose, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
|
126
|
+
logging.getLogger("boto3").setLevel(logging.CRITICAL) # Suppress AWS SDK logging
|
127
|
+
logging.getLogger("botocore").setLevel(logging.CRITICAL) # Suppress AWS core logging
|
128
|
+
logging.getLogger("s3transfer").setLevel(logging.CRITICAL) # Suppress S3 transfer logging
|
129
|
+
logging.getLogger("urllib3").setLevel(logging.CRITICAL) # Suppress HTTP logging
|
130
|
+
|
131
|
+
"""
|
132
|
+
Future Enhancement: Drift Detection Status Monitoring
|
133
|
+
We should eventually create an argument here that would check on the status of the drift-detection using
|
134
|
+
"describe_stack_drift_detection_status", but we haven't created that function yet...
|
135
|
+
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudformation.html#CloudFormation.Client.describe_stack_drift_detection_status
|
136
|
+
|
137
|
+
This would enable:
|
138
|
+
- Real-time drift detection progress monitoring
|
139
|
+
- Drift detection result analysis and reporting
|
140
|
+
- Automated drift detection completion verification
|
141
|
+
- Enhanced operational visibility for large-scale drift operations
|
142
|
+
"""
|
143
|
+
|
144
|
+
##########################
|
145
|
+
ERASE_LINE = "\x1b[2K" # Terminal control for dynamic output updates
|
146
|
+
|
147
|
+
# Initialize AWS account access and organizational context for drift detection
|
148
|
+
aws_acct = aws_acct_access(pProfile)
|
149
|
+
sts_client = aws_acct.session.client("sts")
|
150
|
+
|
151
|
+
# Handle organizational vs single account drift detection scope
|
152
|
+
if aws_acct.AccountType == "Root":
|
153
|
+
print()
|
154
|
+
# Default to organizational drift detection for automation compatibility
|
155
|
+
try:
|
156
|
+
# Interactive prompt for organizational drift detection scope selection
|
157
|
+
answer = input(
|
158
|
+
f"You've specified a root account to check. \n"
|
159
|
+
f"Do you want to check the entire Org or only the root account? (Enter 'root' for whole Org):"
|
160
|
+
)
|
161
|
+
except EOFError:
|
162
|
+
# Handle non-interactive mode (automated testing)
|
163
|
+
print("Non-interactive mode detected, defaulting to root account only")
|
164
|
+
answer = "single"
|
165
|
+
|
166
|
+
if answer == "root":
|
167
|
+
# Enable organizational drift detection across all child accounts
|
168
|
+
ChildAccounts = aws_acct.ChildAccounts
|
169
|
+
else:
|
170
|
+
# Restrict drift detection to management account only
|
171
|
+
ChildAccounts = [
|
172
|
+
{
|
173
|
+
"MgmtAccount": aws_acct.acct_number, # Management account identifier
|
174
|
+
"AccountId": aws_acct.acct_number, # Account number for single account scope
|
175
|
+
"AccountEmail": aws_acct.MgmtEmail, # Management email for audit context
|
176
|
+
"AccountStatus": aws_acct.AccountStatus, # Account operational status
|
177
|
+
}
|
178
|
+
]
|
179
|
+
|
180
|
+
# Initialize drift detection operation tracking and regional scope configuration
|
181
|
+
NumStacksFound = 0 # Counter for tracking total stacks processed for drift detection
|
182
|
+
print()
|
183
|
+
|
184
|
+
# Configure regional scope for comprehensive CloudFormation drift detection coverage
|
185
|
+
RegionList = Inventory_Modules.get_service_regions("cloudformation", pRegionList)
|
186
|
+
|
187
|
+
# Note: Alternative account discovery approaches for organizational drift detection
|
188
|
+
# ChildAccounts = Inventory_Modules.find_child_accounts2(pProfile) # Direct account discovery
|
189
|
+
# ChildAccounts = Inventory_Modules.RemoveCoreAccounts(ChildAccounts, AccountsToSkip) # Account filtering
|
190
|
+
|
191
|
+
# Configure display formatting for drift detection progress and results
|
192
|
+
fmt = "%-20s %-15s %-15s %-50s"
|
193
|
+
print(fmt % ("Account", "Region", "Stack Status", "Stack Name"))
|
194
|
+
print(fmt % ("-------", "------", "------------", "----------"))
|
195
|
+
|
196
|
+
# Execute comprehensive CloudFormation drift detection across organizational accounts and regions
|
197
|
+
StacksFound = [] # Aggregated list for discovered stacks requiring drift detection
|
198
|
+
for account in ChildAccounts:
|
199
|
+
# Note: Alternative role ARN construction for cross-account CloudFormation access
|
200
|
+
# role_arn = f"arn:aws:iam::{account['AccountId']}:role/AWSCloudFormationStackSetExecutionRole"
|
201
|
+
# logging.info(f"Role ARN: {role_arn}")
|
202
|
+
|
203
|
+
try:
|
204
|
+
# Establish cross-account credentials for CloudFormation stack access
|
205
|
+
account_credentials = Inventory_Modules.get_child_access3(
|
206
|
+
aws_acct,
|
207
|
+
account["AccountId"],
|
208
|
+
)
|
209
|
+
|
210
|
+
# Validate account access and skip failed credential attempts
|
211
|
+
if account_credentials["AccessError"]:
|
212
|
+
logging.error(f"Accessing account {account['AccountId']} didn't work, so we're skipping it")
|
213
|
+
continue
|
214
|
+
|
215
|
+
except ClientError as my_Error:
|
216
|
+
# Handle comprehensive AWS API authorization and access errors
|
217
|
+
if "AuthFailure" in str(my_Error):
|
218
|
+
print(f"{pProfile}: Authorization Failure for account {account['AccountId']}")
|
219
|
+
elif str(my_Error).find("AccessDenied") > 0:
|
220
|
+
print(f"{pProfile}: Access Denied Failure for account {account['AccountId']}")
|
221
|
+
else:
|
222
|
+
print(f"{pProfile}: Other kind of failure for account {account['AccountId']}")
|
223
|
+
print(my_Error)
|
224
|
+
break
|
225
|
+
|
226
|
+
# Iterate through regions for comprehensive regional drift detection coverage
|
227
|
+
for region in RegionList:
|
228
|
+
Stacks = [] # Regional stack collection for drift detection processing
|
229
|
+
try:
|
230
|
+
StackNum = 0 # Regional stack counter for progress tracking
|
231
|
+
|
232
|
+
# Discover CloudFormation stacks with fragment and status filtering
|
233
|
+
Stacks = Inventory_Modules.find_stacks2(account_credentials, region, pstackfrag, pstatus)
|
234
|
+
|
235
|
+
# Log regional stack discovery progress for operational visibility
|
236
|
+
logging.warning(f"Account: {account['AccountId']} | Region: {region} | Found {StackNum} Stacks")
|
237
|
+
logging.info(
|
238
|
+
f"{ERASE_LINE}{Fore.RED}Account: {account['AccountId']} Region: {region} Found {StackNum} Stacks{Fore.RESET}"
|
239
|
+
)
|
240
|
+
|
241
|
+
except ClientError as my_Error:
|
242
|
+
# Handle regional authorization failures during stack discovery
|
243
|
+
if "AuthFailure" in str(my_Error):
|
244
|
+
print(f"{account['AccountId']}: Authorization Failure")
|
245
|
+
|
246
|
+
# Process discovered stacks for drift detection enablement
|
247
|
+
for Stack in Stacks:
|
248
|
+
# Extract stack metadata for drift detection operations
|
249
|
+
StackName = Stack["StackName"] # Human-readable stack identifier
|
250
|
+
StackStatus = Stack["StackStatus"] # Current operational status
|
251
|
+
StackID = Stack["StackId"] # Unique CloudFormation stack identifier
|
252
|
+
|
253
|
+
# Enable drift detection on discovered CloudFormation stack
|
254
|
+
DriftStatus = Inventory_Modules.enable_drift_on_stacks2(account_credentials, region, StackName)
|
255
|
+
|
256
|
+
# Log drift detection enablement for audit trail and operational tracking
|
257
|
+
logging.error(
|
258
|
+
f"Enabled drift detection on {StackName} in account {account_credentials['AccountNumber']} in region {region}"
|
259
|
+
)
|
260
|
+
NumStacksFound += 1 # Increment total drift detection counter
|
261
|
+
|
262
|
+
# Provide comprehensive operational summary with drift detection metrics
|
263
|
+
print(ERASE_LINE)
|
264
|
+
print(
|
265
|
+
f"{Fore.RED}Looked through {NumStacksFound} Stacks across {len(ChildAccounts)} accounts across "
|
266
|
+
f"{len(RegionList)} regions{Fore.RESET}"
|
267
|
+
)
|
268
|
+
print()
|
269
|
+
|
270
|
+
# Display completion message for drift detection operation
|
271
|
+
print("Thanks for using this script...")
|
272
|
+
print()
|