33# SPDX-License-Identifier: MIT-0
44########################################################################
55import boto3
6- from botocore .exceptions import ClientError
76import logging
7+ from botocore .exceptions import ClientError
8+ from concurrent .futures import ThreadPoolExecutor , as_completed
89
910"""
1011The purpose of this script is to check if AWS Config is enabled in each AWS account and region within an AWS Control
1718python3 list-config-recorder-status.py
1819"""
1920
20- # Setup Default Logger
21- logger = logging .getLogger (__name__ )
22- logger .setLevel (logging .INFO )
21+ # Logging Settings
22+ LOGGER = logging .getLogger ()
23+ logging .getLogger ("boto3" ).setLevel (logging .CRITICAL )
24+ logging .getLogger ("botocore" ).setLevel (logging .CRITICAL )
25+ logging .getLogger ("s3transfer" ).setLevel (logging .CRITICAL )
26+ logging .getLogger ("urllib3" ).setLevel (logging .CRITICAL )
2327
2428SESSION = boto3 .Session ()
2529STS_CLIENT = boto3 .client ('sts' )
2630AWS_PARTITION = "aws"
2731ASSUME_ROLE_NAME = "AWSControlTowerExecution"
32+ MAX_THREADS = 16
2833
2934
30- def assume_role (aws_account_number , role_name , session_name ):
35+ def assume_role (aws_account_number : str , role_name : str , session_name : str ):
3136 """
3237 Assumes the provided role in the provided account and returns a session
3338 :param aws_account_number: AWS Account Number
@@ -46,12 +51,12 @@ def assume_role(aws_account_number, role_name, session_name):
4651 aws_secret_access_key = response ["Credentials" ]["SecretAccessKey" ],
4752 aws_session_token = response ["Credentials" ]["SessionToken" ],
4853 )
49- logger .debug (f"Assumed session for { aws_account_number } " )
54+ LOGGER .debug (f"... Assumed session for { aws_account_number } " )
5055
5156 return session
5257 except Exception as exc :
53- print (f"Unexpected error: { exc } " )
54- raise ValueError ( "Error assuming role" )
58+ LOGGER . error (f"Unexpected error: { exc } " )
59+ exit ( 1 )
5560
5661
5762def get_all_organization_accounts (account_info : bool , exclude_account_id : str ):
@@ -76,11 +81,11 @@ def get_all_organization_accounts(account_info: bool, exclude_account_id: str):
7681 accounts .append (account_record )
7782 account_ids .append (acct ["Id" ])
7883 except ClientError as ce :
79- print (f"get_all_organization_accounts error: { ce } " )
84+ LOGGER . error (f"get_all_organization_accounts error: { ce } " )
8085 raise ValueError ("Error getting accounts" )
8186 except Exception as exc :
82- print (f"get_all_organization_accounts error: { exc } " )
83- raise ValueError ( "Unexpected error getting accounts" )
87+ LOGGER . error (f"get_all_organization_accounts error: { exc } " )
88+ exit ( 1 )
8489
8590 if account_info :
8691 return accounts
@@ -100,13 +105,14 @@ def is_region_available(region):
100105 return True
101106 except ClientError as error :
102107 if "InvalidClientTokenId" in str (error ):
103- print (f"Region: { region } is not available" )
108+ LOGGER . error (f"Region: { region } is not available" )
104109 return False
105110 else :
106- print (f"{ error } " )
111+ LOGGER . error (f"{ error } " )
107112
108113
109- def get_available_service_regions (user_regions : str , aws_service : str , control_tower_regions_only : bool = False ) -> list :
114+ def get_available_service_regions (user_regions : str , aws_service : str ,
115+ control_tower_regions_only : bool = False ) -> list :
110116 """
111117 Get the available regions for the AWS service
112118 :param: user_regions
@@ -115,9 +121,10 @@ def get_available_service_regions(user_regions: str, aws_service: str, control_t
115121 :return: available region list
116122 """
117123 available_regions = []
124+ service_regions = []
118125 try :
119126 if user_regions .strip ():
120- print (f"USER REGIONS: { str ( user_regions ) } " )
127+ LOGGER . info (f"USER REGIONS: { user_regions } " )
121128 service_regions = [value .strip () for value in user_regions .split ("," ) if value != '' ]
122129 elif control_tower_regions_only :
123130 cf_client = SESSION .client ('cloudformation' )
@@ -130,19 +137,17 @@ def get_available_service_regions(user_regions: str, aws_service: str, control_t
130137 region_set .add (summary ["Region" ])
131138 service_regions = list (region_set )
132139 else :
133- service_regions = boto3 .session .Session ().get_available_regions (
134- aws_service
135- )
136- print (f"SERVICE REGIONS: { service_regions } " )
140+ service_regions = boto3 .session .Session ().get_available_regions (aws_service )
141+ LOGGER .info (f"SERVICE REGIONS: { service_regions } " )
137142 except ClientError as ce :
138- print (f"get_available_service_regions error: { ce } " )
139- raise ValueError ( "Error getting service regions" )
143+ LOGGER . error (f"get_available_service_regions error: { ce } " )
144+ exit ( 1 )
140145
141146 for region in service_regions :
142147 if is_region_available (region ):
143148 available_regions .append (region )
144149
145- print (f"AVAILABLE REGIONS: { available_regions } " )
150+ LOGGER . info (f"AVAILABLE REGIONS: { available_regions } " )
146151 return available_regions
147152
148153
@@ -167,29 +172,84 @@ def get_service_client(aws_service: str, aws_region: str, session=None):
167172 return service_client
168173
169174
170- if __name__ == "__main__" :
171- account_ids = get_all_organization_accounts (False , "" )
172- available_regions = get_available_service_regions ("" , "config" , True )
173- account_set = set ()
174- for account_id in account_ids :
175- try :
176- session = assume_role (account_id , ASSUME_ROLE_NAME , "ConfigRecorderCheck" )
177- except Exception as error :
178- print (f"Unable to assume { ASSUME_ROLE_NAME } in { account_id } { error } " )
179- continue
180-
181- for region in available_regions :
182- try :
183- session_config = get_service_client ("config" , region , session )
184- response = session_config .describe_configuration_recorders ()
185- if "ConfigurationRecorders" in response and response ["ConfigurationRecorders" ]:
186- # print(f"{account_id} {region} - CONFIG ENABLED")
175+ def get_account_config (account_id , regions ):
176+ """
177+ get_account_config
178+ :param account_id:
179+ :param regions:
180+ :return:
181+ """
182+ region_count = 0
183+ config_recorder_count = 0
184+ all_regions_enabled = False
185+ enabled_regions = []
186+ not_enabled_regions = []
187+
188+ session = assume_role (account_id , ASSUME_ROLE_NAME , "ConfigRecorderCheck" )
189+
190+ for region in regions :
191+ region_count += 1
192+ session_config = get_service_client ("config" , region , session )
193+ config_recorders = session_config .describe_configuration_recorders ()
194+
195+ if config_recorders .get ("ConfigurationRecorders" , "" ):
196+ LOGGER .debug (f"{ account_id } { region } - CONFIG ENABLED" )
197+ config_recorder_count += 1
198+ enabled_regions .append (region )
199+ else :
200+ LOGGER .debug (f"{ account_id } { region } - CONFIG NOT ENABLED" )
201+ not_enabled_regions .append (region )
202+
203+ if region_count == config_recorder_count :
204+ all_regions_enabled = True
205+
206+ return account_id , all_regions_enabled , enabled_regions , not_enabled_regions
207+
208+
209+ def get_config_recorder_status ():
210+ """
211+ get_config_recorder_status
212+ :return:
213+ """
214+ try :
215+ account_ids = get_all_organization_accounts (False , "" )
216+ available_regions = get_available_service_regions ("" , "config" , True )
217+ account_set = set ()
218+ processes = []
219+
220+ if MAX_THREADS > len (account_ids ):
221+ thread_cnt = len (account_ids ) - 2
222+ else :
223+ thread_cnt = MAX_THREADS
224+
225+ with ThreadPoolExecutor (max_workers = thread_cnt ) as executor :
226+ for account_id in account_ids :
227+ try :
228+ processes .append (executor .submit (
229+ get_account_config ,
230+ account_id ,
231+ available_regions
232+ ))
233+ except Exception as error :
234+ LOGGER .error (f"{ error } " )
187235 continue
188- else :
189- print (f"{ account_id } { region } - CONFIG NOT ENABLED" )
190- account_set .add (account_id )
191- except ClientError as error :
192- print (f"Client Error - { error } " )
193- print (f'Accounts to exclude from Organization Conformance Pack: { "," .join (list (account_set ))} ' )
194236
237+ for task in as_completed (processes , timeout = 300 ):
238+ account_id , all_regions_enabled , enabled_regions , not_enabled_regions = task .result ()
239+ LOGGER .info (f"Account ID: { account_id } " )
240+ LOGGER .info (f"Regions Enabled = { enabled_regions } " )
241+ LOGGER .info (f"Regions Not Enabled = { not_enabled_regions } \n " )
242+ if not all_regions_enabled :
243+ account_set .add (account_id )
244+
245+ LOGGER .info (f'!!! Accounts to exclude from Organization Conformance Packs: { "," .join (list (account_set ))} ' )
246+ except Exception as error :
247+ LOGGER .error (f"{ error } " )
248+ exit (1 )
249+
250+
251+ if __name__ == "__main__" :
252+ # Set Log Level
253+ logging .basicConfig (level = logging .INFO , format = "%(message)s" )
195254
255+ get_config_recorder_status ()
0 commit comments