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