├── README.md └── script.py /README.md: -------------------------------------------------------------------------------- 1 | # AWSInventoryLambda 2 | 3 | ##What? 4 | 5 | A simple python script to save your AWS inventory details to a CSV file, store it on S3 and trigger an email. 6 | 7 | ## How to use? 8 | 9 | Just use the script.py as Lambda function code and schedule a job. You may also schedule it as a standalone job without using lambda. For more details about how to schedule this as a job using lambda, read this [step by step guide](http://blog.powerupcloud.com/2016/02/07/aws-inventory-details-in-csv-using-lambda/) 10 | 11 | 12 | 13 | # Update 14 | 15 | Use AWS System Manager for creating the Inventory 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /script.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import collections 3 | from datetime import datetime 4 | from datetime import timedelta 5 | import csv 6 | from time import gmtime, strftime 7 | import smtplib 8 | from email.MIMEMultipart import MIMEMultipart 9 | from email.MIMEBase import MIMEBase 10 | from email.MIMEText import MIMEText 11 | from email import Encoders 12 | import os 13 | 14 | #Find current owner ID 15 | sts = boto3.client('sts') 16 | identity = sts.get_caller_identity() 17 | ownerId = identity['Account'] 18 | 19 | #Environment Variables 20 | LIST_SNAPSHOTS_WITHIN_THE_LAST_N_DAYS=os.environ["LIST_SNAPSHOTS_WITHIN_THE_LAST_N_DAYS"] 21 | SES_SMTP_USER=os.environ["SES_SMTP_USER"] 22 | SES_SMTP_PASSWORD=os.environ["SES_SMTP_PASSWORD"] 23 | S3_INVENTORY_BUCKET=os.environ["S3_INVENTORY_BUCKET"] 24 | MAIL_FROM=os.environ["MAIL_FROM"] 25 | MAIL_TO=os.environ["MAIL_TO"] 26 | 27 | #Constants 28 | MAIL_SUBJECT="AWS Inventory for " + ownerId 29 | MAIL_BODY=MAIL_SUBJECT + '\n' 30 | 31 | 32 | #EC2 connection beginning 33 | ec = boto3.client('ec2') 34 | #S3 connection beginning 35 | s3 = boto3.resource('s3') 36 | 37 | #lambda function beginning 38 | def lambda_handler(event, context): 39 | #get to the curren date 40 | date_fmt = strftime("%Y_%m_%d", gmtime()) 41 | #Give your file path 42 | filepath ='/tmp/AWS_Resources_' + date_fmt + '.csv' 43 | #Give your filename 44 | filename ='AWS_Resources_' + date_fmt + '.csv' 45 | csv_file = open(filepath,'w+') 46 | 47 | 48 | #IAM connection beginning - THIS Step should run only once not for every region:https://aws.amazon.com/iam/faqs/ 49 | iam = boto3.client('iam') 50 | #No region is needed since running it from lambda fixes it 51 | 52 | #boto3 library IAM API 53 | #http://boto3.readthedocs.io/en/latest/reference/services/iam.html 54 | csv_file.write("%s,%s,%s,%s\n" % ('','','','')) 55 | csv_file.write("%s,%s\n"%('IAM',regname)) 56 | csv_file.write("%s,%s\n" % ('User','Policies')) 57 | csv_file.flush() 58 | users = iam.list_users()['Users'] 59 | for user in users: 60 | user_name = user['UserName'] 61 | policies = '' 62 | user_policies = iam.list_user_policies(UserName=user_name)["PolicyNames"] 63 | for user_policy in user_policies: 64 | if(len(policies) > 0): 65 | policies += ";" 66 | policies += user_policy 67 | attached_user_policies = iam.list_attached_user_policies(UserName=user_name)["AttachedPolicies"] 68 | for attached_user_policy in attached_user_policies: 69 | if(len(policies) > 0): 70 | policies += ";" 71 | policies += attached_user_policy['PolicyName'] 72 | #In case you need to write this at the end of the CSV make sure to move the 2 lines from below before mail function 73 | csv_file.write("%s,%s\n" % (user_name, policies)) 74 | csv_file.flush() 75 | 76 | 77 | #boto3 library ec2 API describe region page 78 | #http://boto3.readthedocs.org/en/latest/reference/services/ec2.html#EC2.Client.describe_regions 79 | regions = ec.describe_regions().get('Regions',[] ) 80 | for region in regions: 81 | reg=region['RegionName'] 82 | regname='REGION :' + reg 83 | #EC2 connection beginning 84 | ec2con = boto3.client('ec2',region_name=reg) 85 | #boto3 library ec2 API describe instance page 86 | #http://boto3.readthedocs.org/en/latest/reference/services/ec2.html#EC2.Client.describe_instances 87 | reservations = ec2con.describe_instances().get( 88 | 'Reservations',[] 89 | ) 90 | instances = sum( 91 | [ 92 | [i for i in r['Instances']] 93 | for r in reservations 94 | ], []) 95 | instanceslist = len(instances) 96 | if instanceslist > 0: 97 | csv_file.write("%s,%s,%s,%s,%s,%s\n"%('','','','','','')) 98 | csv_file.write("%s,%s\n"%('EC2 INSTANCE',regname)) 99 | csv_file.write("%s,%s,%s,%s,%s,%s,%s\n"%('InstanceID','Instance_State','InstanceName','Instance_Type','LaunchTime','Instance_Placement', 'SecurityGroupsStr')) 100 | csv_file.flush() 101 | 102 | for instance in instances: 103 | state=instance['State']['Name'] 104 | Instancename = 'N/A' 105 | if 'Tags' in instance: 106 | for tags in instance['Tags']: 107 | key = tags['Key'] 108 | if key == 'Name' : 109 | Instancename=tags['Value'] 110 | if state =='running': 111 | instanceid=instance['InstanceId'] 112 | instancetype=instance['InstanceType'] 113 | launchtime =instance['LaunchTime'] 114 | Placement=instance['Placement']['AvailabilityZone'] 115 | securityGroups = instance['SecurityGroups'] 116 | securityGroupsStr = '' 117 | for idx, securityGroup in enumerate(securityGroups): 118 | if idx > 0: 119 | securityGroupsStr += '; ' 120 | securityGroupsStr += securityGroup['GroupName'] 121 | csv_file.write("%s,%s,%s,%s,%s,%s,%s\n"% (instanceid,state,Instancename,instancetype,launchtime,Placement,securityGroupsStr)) 122 | csv_file.flush() 123 | 124 | for instance in instances: 125 | state=instance['State']['Name'] 126 | Instancename = 'N/A' 127 | if 'Tags' in instance: 128 | for tags in instance['Tags']: 129 | key = tags['Key'] 130 | if key == 'Name' : 131 | Instancename=tags['Value'] 132 | if state =='stopped': 133 | instanceid=instance['InstanceId'] 134 | instancetype=instance['InstanceType'] 135 | launchtime =instance['LaunchTime'] 136 | Placement=instance['Placement']['AvailabilityZone'] 137 | csv_file.write("%s,%s,%s,%s,%s,%s\n"%(instanceid,state,Instancename,instancetype,launchtime,Placement)) 138 | csv_file.flush() 139 | 140 | #boto3 library ec2 API describe volumes page 141 | #http://boto3.readthedocs.org/en/latest/reference/services/ec2.html#EC2.Client.describe_volumes 142 | ec2volumes = ec2con.describe_volumes().get('Volumes',[]) 143 | volumes = sum( 144 | [ 145 | [i for i in r['Attachments']] 146 | for r in ec2volumes 147 | ], []) 148 | volumeslist = len(volumes) 149 | if volumeslist > 0: 150 | csv_file.write("%s,%s,%s,%s\n"%('','','','')) 151 | csv_file.write("%s,%s\n"%('EBS Volume',regname)) 152 | csv_file.write("%s,%s,%s,%s\n"%('VolumeId','InstanceId','AttachTime','State')) 153 | csv_file.flush() 154 | 155 | for volume in volumes: 156 | VolumeId=volume['VolumeId'] 157 | InstanceId=volume['InstanceId'] 158 | State=volume['State'] 159 | AttachTime=volume['AttachTime'] 160 | csv_file.write("%s,%s,%s,%s\n" % (VolumeId,InstanceId,AttachTime,State)) 161 | csv_file.flush() 162 | 163 | #boto3 library ec2 API describe snapshots page 164 | #http://boto3.readthedocs.org/en/latest/reference/services/ec2.html#EC2.Client.describe_snapshots 165 | ec2snapshot = ec2con.describe_snapshots(OwnerIds=[ 166 | ownerId, 167 | ],).get('Snapshots',[]) 168 | 169 | snapshots_counter = 0 170 | for snapshot in ec2snapshot: 171 | snapshot_id = snapshot['SnapshotId'] 172 | snapshot_state = snapshot['State'] 173 | tz_info = snapshot['StartTime'].tzinfo 174 | # Snapshots that were not taken within the last configured days do not qualify for auditing 175 | timedelta_days=-int(LIST_SNAPSHOTS_WITHIN_THE_LAST_N_DAYS) 176 | if snapshot['StartTime'] > datetime.now(tz_info) + timedelta(days=timedelta_days): 177 | if snapshots_counter == 0: 178 | csv_file.write("%s,%s,%s,%s,%s\n" % ('','','','','')) 179 | csv_file.write("%s,%s\n"%('EC2 SNAPSHOT',regname)) 180 | csv_file.write("%s,%s,%s,%s,%s\n" % ('SnapshotId','VolumeId','StartTime','VolumeSize','Description')) 181 | csv_file.flush() 182 | snapshots_counter += 1 183 | SnapshotId=snapshot['SnapshotId'] 184 | VolumeId=snapshot['VolumeId'] 185 | StartTime=snapshot['StartTime'] 186 | VolumeSize=snapshot['VolumeSize'] 187 | Description=snapshot['Description'] 188 | csv_file.write("%s,%s,%s,%s,%s\n" % (SnapshotId,VolumeId,StartTime,VolumeSize,Description)) 189 | csv_file.flush() 190 | 191 | #boto3 library ec2 API describe addresses page 192 | #http://boto3.readthedocs.org/en/latest/reference/services/ec2.html#EC2.Client.describe_addresses 193 | addresses = ec2con.describe_addresses().get('Addresses',[] ) 194 | addresseslist = len(addresses) 195 | if addresseslist > 0: 196 | csv_file.write("%s,%s,%s,%s,%s\n"%('','','','','')) 197 | csv_file.write("%s,%s\n"%('EIPS INSTANCE',regname)) 198 | csv_file.write("%s,%s,%s,%s\n"%('PublicIp','AllocationId','Domain','InstanceId')) 199 | csv_file.flush() 200 | for address in addresses: 201 | PublicIp=address['PublicIp'] 202 | try: 203 | AllocationId=address['AllocationId'] 204 | except: 205 | AllocationId="empty" 206 | Domain=address['Domain'] 207 | if 'InstanceId' in address: 208 | instanceId=address['InstanceId'] 209 | else: 210 | instanceId='empty' 211 | csv_file.write("%s,%s,%s,%s\n"%(PublicIp,AllocationId,Domain,instanceId)) 212 | csv_file.flush() 213 | 214 | def printSecGroup(groupType, permission): 215 | ipProtocol = permission['IpProtocol'] 216 | try: 217 | fromPort = permission['FromPort'] 218 | except KeyError: 219 | fromPort = None 220 | try: 221 | toPort = permission['ToPort'] 222 | except KeyError: 223 | toPort = None 224 | try: 225 | ipRanges = permission['IpRanges'] 226 | except KeyError: 227 | ipRanges = [] 228 | ipRangesStr = '' 229 | for idx, ipRange in enumerate(ipRanges): 230 | if idx > 0: 231 | ipRangesStr += '; ' 232 | ipRangesStr += ipRange['CidrIp'] 233 | csv_file.write("%s,%s,%s,%s,%s,%s\n"%(groupName,groupType,ipProtocol,fromPort,toPort,ipRangesStr)) 234 | csv_file.flush() 235 | 236 | #boto3 library ec2 API describe security groups page 237 | #http://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_security_groups 238 | securityGroups = ec2con.describe_security_groups( 239 | Filters = [ 240 | { 241 | 'Name': 'owner-id', 242 | 'Values': [ 243 | ownerId, 244 | ] 245 | } 246 | ] 247 | ).get('SecurityGroups') 248 | if len(securityGroups) > 0: 249 | csv_file.write("%s,%s,%s,%s,%s\n"%('','','','','')) 250 | csv_file.write("%s,%s\n"%('SEC GROUPS',regname)) 251 | csv_file.write("%s,%s,%s,%s,%s,%s\n"%('GroupName','GroupType','IpProtocol','FromPort','ToPort','IpRangesStr')) 252 | csv_file.flush() 253 | for securityGroup in securityGroups: 254 | groupName = securityGroup['GroupName'] 255 | ipPermissions = securityGroup['IpPermissions'] 256 | for ipPermission in ipPermissions: 257 | groupType = 'ingress' 258 | printSecGroup (groupType, ipPermission) 259 | ipPermissionsEgress = securityGroup['IpPermissionsEgress'] 260 | for ipPermissionEgress in ipPermissionsEgress: 261 | groupType = 'egress' 262 | printSecGroup (groupType, ipPermissionEgress) 263 | 264 | #RDS Connection beginning 265 | rdscon = boto3.client('rds',region_name=reg) 266 | 267 | #boto3 library RDS API describe db instances page 268 | #http://boto3.readthedocs.org/en/latest/reference/services/rds.html#RDS.Client.describe_db_instances 269 | rdb = rdscon.describe_db_instances().get( 270 | 'DBInstances',[] 271 | ) 272 | rdblist = len(rdb) 273 | if rdblist > 0: 274 | csv_file.write("%s,%s,%s,%s\n" %('','','','')) 275 | csv_file.write("%s,%s\n"%('RDS INSTANCE',regname)) 276 | csv_file.write("%s,%s,%s,%s\n" %('DBInstanceIdentifier','DBInstanceStatus','DBName','DBInstanceClass')) 277 | csv_file.flush() 278 | 279 | for dbinstance in rdb: 280 | DBInstanceIdentifier = dbinstance['DBInstanceIdentifier'] 281 | DBInstanceClass = dbinstance['DBInstanceClass'] 282 | DBInstanceStatus = dbinstance['DBInstanceStatus'] 283 | try: 284 | DBName = dbinstance['DBName'] 285 | except: 286 | DBName = "empty" 287 | csv_file.write("%s,%s,%s,%s\n" %(DBInstanceIdentifier,DBInstanceStatus,DBName,DBInstanceClass)) 288 | csv_file.flush() 289 | 290 | #ELB connection beginning 291 | elbcon = boto3.client('elb',region_name=reg) 292 | 293 | #boto3 library ELB API describe db instances page 294 | #http://boto3.readthedocs.org/en/latest/reference/services/elb.html#ElasticLoadBalancing.Client.describe_load_balancers 295 | loadbalancer = elbcon.describe_load_balancers().get('LoadBalancerDescriptions',[]) 296 | loadbalancerlist = len(loadbalancer) 297 | if loadbalancerlist > 0: 298 | csv_file.write("%s,%s,%s,%s\n" % ('','','','')) 299 | csv_file.write("%s,%s\n"%('ELB INSTANCE',regname)) 300 | csv_file.write("%s,%s,%s,%s\n" % ('LoadBalancerName','DNSName','CanonicalHostedZoneName','CanonicalHostedZoneNameID')) 301 | csv_file.flush() 302 | 303 | for load in loadbalancer: 304 | LoadBalancerName=load['LoadBalancerName'] 305 | DNSName=load['DNSName'] 306 | CanonicalHostedZoneName=load['CanonicalHostedZoneName'] 307 | CanonicalHostedZoneNameID=load['CanonicalHostedZoneNameID'] 308 | csv_file.write("%s,%s,%s,%s\n" % (LoadBalancerName,DNSName,CanonicalHostedZoneName,CanonicalHostedZoneNameID)) 309 | csv_file.flush() 310 | 311 | 312 | def mail(fromadd,to, subject, text, attach): 313 | msg = MIMEMultipart() 314 | msg['From'] = fromadd 315 | msg['To'] = to 316 | msg['Subject'] = subject 317 | msg.attach(MIMEText(text)) 318 | part = MIMEBase('application', 'octet-stream') 319 | part.set_payload(open(attach, 'rb').read()) 320 | Encoders.encode_base64(part) 321 | part.add_header('Content-Disposition','attachment; filename="%s"' % os.path.basename(attach)) 322 | msg.attach(part) 323 | mailServer = smtplib.SMTP("email-smtp.us-east-1.amazonaws.com", 587) 324 | mailServer.ehlo() 325 | mailServer.starttls() 326 | mailServer.ehlo() 327 | mailServer.login(SES_SMTP_USER, SES_SMTP_PASSWORD) 328 | mailServer.sendmail(fromadd, to, msg.as_string()) 329 | # Should be mailServer.quit(), but that crashes... 330 | mailServer.close() 331 | 332 | date_fmt = strftime("%Y_%m_%d", gmtime()) 333 | #Give your file path 334 | filepath ='/tmp/AWS_Resources_' + date_fmt + '.csv' 335 | #Save Inventory 336 | s3.Object(S3_INVENTORY_BUCKET, filename).put(Body=open(filepath, 'rb')) 337 | #Send Inventory 338 | mail(MAIL_FROM, MAIL_TO, MAIL_SUBJECT, MAIL_BODY, filepath) 339 | --------------------------------------------------------------------------------