1+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+ # SPDX-License-Identifier: Apache-2.0
3+
4+ """
5+ Purpose
6+
7+ Shows how to use the AWS SDK for Python (Boto3) to work with AWS Config.
8+ This scenario demonstrates how to:
9+ - Set up a configuration recorder to track AWS resource configurations
10+ - Create a delivery channel to specify where Config sends configuration snapshots
11+ - Start the configuration recorder to begin monitoring resources
12+ - Monitor configuration recorder status and settings
13+ - Discover AWS resources in the account
14+ - Retrieve configuration history for specific resources
15+ - Clean up resources when done
16+
17+ This example requires an S3 bucket and IAM role with appropriate permissions.
18+ """
19+
20+ import logging
21+ import time
22+ import boto3
23+ from botocore .exceptions import ClientError
24+
25+ from config_rules import ConfigWrapper
26+ import sys
27+ import os
28+ sys .path .append (os .path .join (os .path .dirname (__file__ ), '..' , '..' ))
29+ import demo_tools .question as q
30+
31+ logger = logging .getLogger (__name__ )
32+
33+
34+ # snippet-start:[python.example_code.config-service.Scenario_ConfigBasics]
35+ class ConfigBasicsScenario :
36+ """
37+ Runs an interactive scenario that shows how to get started with AWS Config.
38+ """
39+
40+ def __init__ (self , config_wrapper , s3_resource , iam_resource ):
41+ """
42+ :param config_wrapper: An object that wraps AWS Config operations.
43+ :param s3_resource: A Boto3 S3 resource.
44+ :param iam_resource: A Boto3 IAM resource.
45+ """
46+ self .config_wrapper = config_wrapper
47+ self .s3_resource = s3_resource
48+ self .iam_resource = iam_resource
49+ self .recorder_name = None
50+ self .channel_name = None
51+ self .bucket_name = None
52+ self .role_arn = None
53+
54+ def run_scenario (self ):
55+ """
56+ Runs the scenario.
57+ """
58+ print ("-" * 88 )
59+ print ("Welcome to the AWS Config basics scenario!" )
60+ print ("-" * 88 )
61+
62+ print (
63+ "AWS Config provides a detailed view of the resources associated with your AWS account, "
64+ "including how they are configured, how they are related to one another, and how the "
65+ "configurations and their relationships have changed over time."
66+ )
67+ print ()
68+
69+ # Setup phase
70+ if not self ._setup_resources ():
71+ return
72+
73+ try :
74+ # Configuration monitoring phase
75+ self ._demonstrate_configuration_monitoring ()
76+
77+ # Resource discovery phase
78+ self ._demonstrate_resource_discovery ()
79+
80+ # Configuration history phase
81+ self ._demonstrate_configuration_history ()
82+
83+ finally :
84+ # Cleanup phase
85+ self ._cleanup_resources ()
86+
87+ print ("Thanks for watching!" )
88+ print ("-" * 88 )
89+
90+ def _setup_resources (self ):
91+ """
92+ Sets up the necessary resources for the scenario.
93+ """
94+ print ("\n " + "-" * 60 )
95+ print ("Setup" )
96+ print ("-" * 60 )
97+
98+ # Get S3 bucket for delivery channel
99+ self .bucket_name = q .ask (
100+ "Enter the name of an S3 bucket for Config to deliver configuration snapshots "
101+ "(the bucket must exist and have appropriate permissions): " ,
102+ q .non_empty
103+ )
104+
105+ # Verify bucket exists
106+ try :
107+ self .s3_resource .meta .client .head_bucket (Bucket = self .bucket_name )
108+ print (f"✓ S3 bucket '{ self .bucket_name } ' found." )
109+ except ClientError as err :
110+ if err .response ['Error' ]['Code' ] == '404' :
111+ print (f"✗ S3 bucket '{ self .bucket_name } ' not found." )
112+ return False
113+ else :
114+ print (f"✗ Error accessing S3 bucket: { err } " )
115+ return False
116+
117+ # Get IAM role ARN
118+ self .role_arn = q .ask (
119+ "Enter the ARN of an IAM role that grants AWS Config permissions to access your resources "
120+ "(e.g., arn:aws:iam::123456789012:role/config-role): " ,
121+ q .non_empty
122+ )
123+
124+ # Verify role exists
125+ try :
126+ role_name = self .role_arn .split ('/' )[- 1 ]
127+ self .iam_resource .Role (role_name ).load ()
128+ print (f"✓ IAM role found." )
129+ except ClientError as err :
130+ print (f"✗ Error accessing IAM role: { err } " )
131+ return False
132+
133+ # Create configuration recorder
134+ self .recorder_name = "demo-config-recorder"
135+ print (f"\n Creating configuration recorder '{ self .recorder_name } '..." )
136+ try :
137+ self .config_wrapper .put_configuration_recorder (
138+ self .recorder_name ,
139+ self .role_arn
140+ )
141+ print ("✓ Configuration recorder created successfully." )
142+ except ClientError as err :
143+ if 'MaxNumberOfConfigurationRecordersExceededException' in str (err ):
144+ print ("✗ Maximum number of configuration recorders exceeded." )
145+ print ("You can have only one configuration recorder per region." )
146+ return False
147+ else :
148+ print (f"✗ Error creating configuration recorder: { err } " )
149+ return False
150+
151+ # Create delivery channel
152+ self .channel_name = "demo-delivery-channel"
153+ print (f"\n Creating delivery channel '{ self .channel_name } '..." )
154+ try :
155+ self .config_wrapper .put_delivery_channel (
156+ self .channel_name ,
157+ self .bucket_name ,
158+ "config-snapshots/"
159+ )
160+ print ("✓ Delivery channel created successfully." )
161+ except ClientError as err :
162+ print (f"✗ Error creating delivery channel: { err } " )
163+ return False
164+
165+ # Start configuration recorder
166+ print (f"\n Starting configuration recorder '{ self .recorder_name } '..." )
167+ try :
168+ self .config_wrapper .start_configuration_recorder (self .recorder_name )
169+ print ("✓ Configuration recorder started successfully." )
170+ print ("AWS Config is now monitoring your resources!" )
171+ except ClientError as err :
172+ print (f"✗ Error starting configuration recorder: { err } " )
173+ return False
174+
175+ return True
176+
177+ def _demonstrate_configuration_monitoring (self ):
178+ """
179+ Demonstrates configuration monitoring capabilities.
180+ """
181+ print ("\n " + "-" * 60 )
182+ print ("Configuration Monitoring" )
183+ print ("-" * 60 )
184+
185+ # Show recorder status
186+ print ("Checking configuration recorder status..." )
187+ try :
188+ statuses = self .config_wrapper .describe_configuration_recorder_status ([self .recorder_name ])
189+ if statuses :
190+ status = statuses [0 ]
191+ print (f"Recorder: { status ['name' ]} " )
192+ print (f"Recording: { status ['recording' ]} " )
193+ print (f"Last Status: { status .get ('lastStatus' , 'N/A' )} " )
194+ if 'lastStartTime' in status :
195+ print (f"Last Started: { status ['lastStartTime' ]} " )
196+ except ClientError as err :
197+ print (f"Error getting recorder status: { err } " )
198+
199+ # Show recorder configuration
200+ print ("\n Configuration recorder settings:" )
201+ try :
202+ recorders = self .config_wrapper .describe_configuration_recorders ([self .recorder_name ])
203+ if recorders :
204+ recorder = recorders [0 ]
205+ recording_group = recorder .get ('recordingGroup' , {})
206+ print (f"Recording all supported resources: { recording_group .get ('allSupported' , False )} " )
207+ print (f"Including global resources: { recording_group .get ('includeGlobalResourceTypes' , False )} " )
208+
209+ if not recording_group .get ('allSupported' , True ):
210+ resource_types = recording_group .get ('resourceTypes' , [])
211+ print (f"Specific resource types: { ', ' .join (resource_types )} " )
212+ except ClientError as err :
213+ print (f"Error getting recorder configuration: { err } " )
214+
215+ # Wait a moment for resources to be discovered
216+ print ("\n Waiting for AWS Config to discover resources..." )
217+ time .sleep (10 )
218+
219+ def _demonstrate_resource_discovery (self ):
220+ """
221+ Demonstrates resource discovery capabilities.
222+ """
223+ print ("\n " + "-" * 60 )
224+ print ("Resource Discovery" )
225+ print ("-" * 60 )
226+
227+ # Common resource types to check
228+ resource_types = [
229+ 'AWS::S3::Bucket' ,
230+ 'AWS::EC2::Instance' ,
231+ 'AWS::IAM::Role' ,
232+ 'AWS::Lambda::Function'
233+ ]
234+
235+ print ("Discovering AWS resources in your account..." )
236+ total_resources = 0
237+
238+ for resource_type in resource_types :
239+ try :
240+ resources = self .config_wrapper .list_discovered_resources (resource_type , limit = 10 )
241+ count = len (resources )
242+ total_resources += count
243+ print (f"{ resource_type } : { count } resources" )
244+
245+ # Show details for first few resources
246+ if resources and count > 0 :
247+ print (f" Sample resources:" )
248+ for i , resource in enumerate (resources [:3 ]):
249+ print (f" { i + 1 } . { resource .get ('resourceId' , 'N/A' )} ({ resource .get ('resourceName' , 'Unnamed' )} )" )
250+ if count > 3 :
251+ print (f" ... and { count - 3 } more" )
252+ print ()
253+
254+ except ClientError as err :
255+ print (f"Error listing { resource_type } : { err } " )
256+
257+ print (f"Total resources discovered: { total_resources } " )
258+
259+ def _demonstrate_configuration_history (self ):
260+ """
261+ Demonstrates configuration history capabilities.
262+ """
263+ print ("\n " + "-" * 60 )
264+ print ("Configuration History" )
265+ print ("-" * 60 )
266+
267+ # Try to get configuration history for the S3 bucket we're using
268+ print (f"Getting configuration history for S3 bucket '{ self .bucket_name } '..." )
269+ try :
270+ config_items = self .config_wrapper .get_resource_config_history (
271+ 'AWS::S3::Bucket' ,
272+ self .bucket_name ,
273+ limit = 5
274+ )
275+
276+ if config_items :
277+ print (f"Found { len (config_items )} configuration item(s):" )
278+ for i , item in enumerate (config_items ):
279+ print (f"\n Configuration { i + 1 } :" )
280+ print (f" Configuration Item Capture Time: { item .get ('configurationItemCaptureTime' , 'N/A' )} " )
281+ print (f" Configuration State Id: { item .get ('configurationStateId' , 'N/A' )} " )
282+ print (f" Configuration Item Status: { item .get ('configurationItemStatus' , 'N/A' )} " )
283+ print (f" Resource Type: { item .get ('resourceType' , 'N/A' )} " )
284+ print (f" Resource Id: { item .get ('resourceId' , 'N/A' )} " )
285+
286+ # Show some configuration details
287+ config_data = item .get ('configuration' )
288+ if config_data and isinstance (config_data , dict ):
289+ print (f" Sample configuration keys: { list (config_data .keys ())[:5 ]} " )
290+ else :
291+ print ("No configuration history found yet. This is normal for newly monitored resources." )
292+ print ("Configuration history will be available after resources are modified." )
293+
294+ except ClientError as err :
295+ if 'ResourceNotDiscoveredException' in str (err ):
296+ print ("Resource not yet discovered by AWS Config. This is normal for new setups." )
297+ else :
298+ print (f"Error getting configuration history: { err } " )
299+
300+ def _cleanup_resources (self ):
301+ """
302+ Cleans up resources created during the scenario.
303+ """
304+ print ("\n " + "-" * 60 )
305+ print ("Cleanup" )
306+ print ("-" * 60 )
307+
308+ if self .recorder_name :
309+ cleanup = q .ask (
310+ f"Do you want to stop and delete the configuration recorder '{ self .recorder_name } '? "
311+ "This will stop monitoring your resources. (y/n): " ,
312+ q .is_yesno
313+ )
314+
315+ if cleanup :
316+ # Stop the configuration recorder
317+ print (f"Stopping configuration recorder '{ self .recorder_name } '..." )
318+ try :
319+ self .config_wrapper .stop_configuration_recorder (self .recorder_name )
320+ print ("✓ Configuration recorder stopped." )
321+ except ClientError as err :
322+ print (f"Error stopping configuration recorder: { err } " )
323+
324+ # Note: In a real scenario, you might also want to delete the recorder and delivery channel
325+ # However, this example leaves them for the user to manage manually
326+ print ("\n Note: The configuration recorder and delivery channel have been left in your account." )
327+ print ("You can manage them through the AWS Console or delete them manually if needed." )
328+ else :
329+ print ("Configuration recorder left running. You can manage it through the AWS Console." )
330+
331+ print ("\n Scenario completed!" )
332+
333+
334+ # snippet-end:[python.example_code.config-service.Scenario_ConfigBasics]
335+
336+
337+ def main ():
338+ """
339+ Runs the Config basics scenario.
340+ """
341+ logging .basicConfig (level = logging .INFO , format = "%(levelname)s: %(message)s" )
342+
343+ print ("-" * 88 )
344+ print ("Welcome to the AWS Config basics scenario!" )
345+ print ("-" * 88 )
346+
347+ config_wrapper = ConfigWrapper (boto3 .client ('config' ))
348+ s3_resource = boto3 .resource ('s3' )
349+ iam_resource = boto3 .resource ('iam' )
350+
351+ scenario = ConfigBasicsScenario (config_wrapper , s3_resource , iam_resource )
352+ scenario .run_scenario ()
353+
354+
355+ if __name__ == '__main__' :
356+ main ()
0 commit comments