├── .gitignore ├── LICENSE ├── README.md ├── count_resources.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | .DS_Store 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 DisruptOps, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # resource-counter 2 | This command line tool counts the number of resources in different categories across Amazon regions. 3 | 4 | This is a simple Python app that will count resources across different regions and display them on the command line. It first shows the dictionary of the results for the monitored services on a per-region basis, then it shows totals across all regions in a friendlier format. It tries to use the most-efficient query mechanism for each resource in order to manage the impact of API activity. I wrote this to help me scope out assessments and know where resources are in a target account. 5 | 6 | The development plan is to upgrade the output (probably to CSV file) and to continue to add services. If you have a specific service you want to see added just add a request in the comments. 7 | 8 | The current list incluides: 9 | 10 | * Application and Network Load Balancers 11 | * Autoscale Groups 12 | * Classic Load Balancers 13 | * CloudTrail Trails 14 | * Cloudwatch Rules 15 | * Config Rules 16 | * Dynamo Tables 17 | * Elastic IP Addresses 18 | * Glacier Vaults 19 | * IAM Groups 20 | * Images 21 | * Instances 22 | * KMS Keys 23 | * Lambda Functions 24 | * Launch Configurations 25 | * NAT Gateways 26 | * Network ACLs 27 | * IAM Policies 28 | * RDS Instances 29 | * IAM Roles 30 | * S3 Buckets 31 | * SAML Providers 32 | * SNS Topics 33 | * Security Groups 34 | * Snapshots 35 | * Subnets 36 | * IAM Users 37 | * VPC Endpoints 38 | * VPC Peering Connection 39 | * VPCs 40 | * Volumes 41 | 42 | ## Usage: 43 | 44 | To install just copy it where you want it and instally the requirements: 45 | 46 | pip install -r ./requirements.txt 47 | 48 | This was written in Python 3.6. 49 | 50 | To run: 51 | 52 | python count_resources.py 53 | 54 | By default, it will use whatever AWS credentials are alerady configued on the system. You can also specify an access key/secret at runtime and this is not stored. It only neeeds read permissions for the listed services- I use the ReadOnlyAccess managed policy, but you should also be able to use the SecurityAudit policy. 55 | 56 | Usage: count_resources.py [OPTIONS] 57 | 58 | Options: 59 | --access TEXT AWS Access Key. Otherwise will use the standard credentials 60 | path for the AWS CLI. 61 | --secret TEXT AWS Secret Key 62 | --profile TEXT If you have multiple credential profiles, use this option to 63 | specify one. 64 | --help Show this message and exit. 65 | 66 | ## Sample Output: 67 | 68 | Establishing AWS session using the profile- dev 69 | Current account ID: xxxxxxxxxx 70 | Counting resources across regions. This will take a few minutes... 71 | 72 | Resources by region 73 | {'ap-northeast-1': {'instances': 0, 'volumes': 0, 'security_groups': 1, 'snapshots': 0, 'images': 0, 'vpcs': 1, 'subnets': 3, 'peering connections': 0, 'network ACLs': 1, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 0, 'dynamo tables': 0, 'rds instances': 0}, 'ap-northeast-2': {'instances': 0, 'volumes': 0, 'security_groups': 1, 'snapshots': 0, 'images': 0, 'vpcs': 1, 'subnets': 2, 'peering connections': 0, 'network ACLs': 1, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 0, 'dynamo tables': 0, 'rds instances': 0}, 'ap-south-1': {'instances': 0, 'volumes': 0, 'security_groups': 1, 'snapshots': 0, 'images': 0, 'vpcs': 1, 'subnets': 2, 'peering connections': 0, 'network ACLs': 1, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 0, 'dynamo tables': 0, 'rds instances': 0}, 'ap-southeast-1': {'instances': 0, 'volumes': 0, 'security_groups': 1, 'snapshots': 0, 'images': 0, 'vpcs': 1, 'subnets': 3, 'peering connections': 0, 'network ACLs': 1, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 0, 'dynamo tables': 0, 'rds instances': 0}, 'ap-southeast-2': {'instances': 0, 'volumes': 0, 'security_groups': 1, 'snapshots': 0, 'images': 0, 'vpcs': 1, 'subnets': 3, 'peering connections': 0, 'network ACLs': 1, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 0, 'dynamo tables': 0, 'rds instances': 0}, 'ca-central-1': {'instances': 0, 'volumes': 0, 'security_groups': 1, 'snapshots': 0, 'images': 0, 'vpcs': 1, 'subnets': 2, 'peering connections': 0, 'network ACLs': 1, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 0, 'dynamo tables': 0, 'rds instances': 0}, 'eu-central-1': {'instances': 0, 'volumes': 0, 'security_groups': 1, 'snapshots': 0, 'images': 0, 'vpcs': 1, 'subnets': 3, 'peering connections': 0, 'network ACLs': 1, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 0, 'dynamo tables': 0, 'rds instances': 0}, 'eu-west-1': {'instances': 0, 'volumes': 0, 'security_groups': 1, 'snapshots': 0, 'images': 0, 'vpcs': 1, 'subnets': 3, 'peering connections': 0, 'network ACLs': 1, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 0, 'dynamo tables': 0, 'rds instances': 0}, 'eu-west-2': {'instances': 3, 'volumes': 3, 'security_groups': 1, 'snapshots': 0, 'images': 0, 'vpcs': 1, 'subnets': 3, 'peering connections': 0, 'network ACLs': 1, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 0, 'dynamo tables': 0, 'rds instances': 0}, 'eu-west-3': {'instances': 0, 'volumes': 0, 'security_groups': 1, 'snapshots': 0, 'images': 0, 'vpcs': 1, 'subnets': 3, 'peering connections': 0, 'network ACLs': 1, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 0, 'dynamo tables': 0, 'rds instances': 0}, 'sa-east-1': {'instances': 0, 'volumes': 0, 'security_groups': 1, 'snapshots': 0, 'images': 0, 'vpcs': 1, 'subnets': 3, 'peering connections': 0, 'network ACLs': 1, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 0, 'dynamo tables': 0, 'rds instances': 0}, 'us-east-1': {'instances': 2, 'volumes': 2, 'security_groups': 19, 'snapshots': 0, 'images': 0, 'vpcs': 2, 'subnets': 3, 'peering connections': 0, 'network ACLs': 2, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 1, 'cloudtrail trails': 2, 'sns topics': 3, 'kms keys': 5, 'dynamo tables': 0, 'rds instances': 0}, 'us-east-2': {'instances': 0, 'volumes': 0, 'security_groups': 2, 'snapshots': 0, 'images': 0, 'vpcs': 1, 'subnets': 3, 'peering connections': 0, 'network ACLs': 1, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 0, 'dynamo tables': 0, 'rds instances': 0}, 'us-west-1': {'instances': 1, 'volumes': 3, 'security_groups': 14, 'snapshots': 1, 'images': 0, 'vpcs': 0, 'subnets': 0, 'peering connections': 0, 'network ACLs': 0, 'elastic IPs': 0, 'NAT gateways': 0, 'VPC Endpoints': 0, 'autoscale groups': 0, 'launch configurations': 0, 'classic load balancers': 0, 'application and network load balancers': 0, 'lambdas': 0, 'glacier vaults': 0, 'cloudwatch rules': 0, 'config rules': 0, 'cloudtrail trails': 1, 'sns topics': 0, 'kms keys': 1, 'dynamo tables': 0, 'rds instances': 0}, 'us-west-2': {'instances': 9, 'volumes': 29, 'security_groups': 76, 'snapshots': 171, 'images': 104, 'vpcs': 7, 'subnets': 15, 'peering connections': 1, 'network ACLs': 8, 'elastic IPs': 7, 'NAT gateways': 1, 'VPC Endpoints': 0, 'autoscale groups': 1, 'launch configurations': 66, 'classic load balancers': 1, 'application and network load balancers': 2, 'lambdas': 10, 'glacier vaults': 1, 'cloudwatch rules': 8, 'config rules': 1, 'cloudtrail trails': 1, 'sns topics': 6, 'kms keys': 7, 'dynamo tables': 1, 'rds instances': 0}} 74 | 75 | Resource totals across all regions 76 | Application and Network Load Balancers : 2 77 | Autoscale Groups : 1 78 | Classic Load Balancers : 1 79 | CloudTrail Trails : 16 80 | Cloudwatch Rules : 8 81 | Config Rules : 2 82 | Dynamo Tables : 1 83 | Elastic IP Addresses : 7 84 | Glacier Vaults : 1 85 | Groups : 12 86 | Images : 104 87 | Instances : 15 88 | KMS Keys : 13 89 | Lambda Functions : 10 90 | Launch Configurations : 66 91 | NAT Gateways : 1 92 | Network ACLs : 22 93 | Policies : 15 94 | RDS Instances : 0 95 | Roles : 40 96 | S3 Buckets : 31 97 | SAML Providers : 1 98 | SNS Topics : 9 99 | Security Groups : 122 100 | Snapshots : 172 101 | Subnets : 51 102 | Users : 14 103 | VPC Endpoints : 0 104 | VPC Peering Connections : 1 105 | VPCs : 21 106 | Volumes : 37 107 | 108 | Total resources: 796 109 | 110 | -------------------------------------------------------------------------------- /count_resources.py: -------------------------------------------------------------------------------- 1 | import click 2 | import boto3 3 | import sys 4 | resource_counts = {} 5 | resource_totals = {} 6 | 7 | @click.command() 8 | @click.option('--access', help='AWS Access Key. Otherwise will use the standard credentials path for the AWS CLI.') 9 | @click.option('--secret', help='AWS Secret Key') 10 | @click.option('--profile', help='If you have multiple credential profiles, use this option to specify one.') 11 | def controller(access, secret, profile): 12 | global session 13 | if access: 14 | click.echo('Access Key specified') 15 | if not secret: 16 | click.echo('Secret key not specified. A secret key must be provided when the command line access key option is provided.') 17 | else: 18 | click.echo('Establishing AWS session using the provided access key...') 19 | try: 20 | session = boto3.session.Session(aws_access_key_id=access, aws_secret_access_key=secret) 21 | except: 22 | click.echo('Error establishing AWS connection. Likely bad credentials provided.') 23 | sys.exit() 24 | elif profile: 25 | click.echo('Establishing AWS session using the profile- ' + profile) 26 | try: 27 | session = boto3.session.Session(profile_name=profile) 28 | except: 29 | click.echo('Error establishing AWS connection. Likely bad credentials provided.') 30 | sys.exit() 31 | else: 32 | click.echo('Establishing AWS session using default path credentials...') 33 | try: 34 | session = boto3.session.Session() 35 | except: 36 | click.echo('Error establishing AWS connection. Likely bad credentials provided.') 37 | sys.exit() 38 | 39 | # pull the account ID for use when needed for filtering 40 | iam = session.client('sts') 41 | 42 | # account_id = iam.CurrentUser().arn.split(':')[4] 43 | account_id = iam.get_caller_identity()["Account"] 44 | click.echo('Current account ID: ' + account_id) 45 | 46 | # Initialize dictionary to hold the counts. Pull the regions using EC2, since that is in every region. 47 | # Then build out the master list of regions to then fill in the service counts 48 | # Also build a separate dictionary for cross-region totals 49 | 50 | 51 | region_list = session.get_available_regions('ec2') 52 | for region in region_list: 53 | resource_counts[region] = {} 54 | 55 | 56 | # iterate through the various services to build the counts 57 | click.echo('Counting resources across regions. This will take a few minutes...') 58 | click.echo(' ') 59 | ec2_counter(account_id) 60 | autoscaling_counter() 61 | balancer_counter() 62 | s3_counter() 63 | iam_counter() 64 | lambda_counter() 65 | glacier_counter() 66 | cloudwatch_rules_counter() 67 | config_counter() 68 | cloudtrail_counter() 69 | sns_counter() 70 | kms_counter() 71 | dynamo_counter() 72 | rds_counter() 73 | 74 | # show results 75 | click.echo('Resources by region') 76 | click.echo(resource_counts) 77 | click.echo(' ') 78 | click.echo('Resource totals across all regions') 79 | for key, value in sorted(resource_totals.items()): 80 | click.echo("{} : {}".format(key, value)) 81 | total = sum(resource_totals.values()) 82 | click.echo('') 83 | click.echo('Total resources: ' + str(total)) 84 | 85 | # ec2 = boto3.client('ec2', region_name='us-west-2') 86 | 87 | # ec2 = session.client('ec2', region_name='us-west-2') 88 | 89 | 90 | def ec2_counter(account_id): 91 | # get list of regions supported by EC2 endpoint 92 | region_list = session.get_available_regions('ec2') 93 | 94 | # initialize cross region totals 95 | total_instances = 0 96 | total_groups = 0 97 | total_volumes = 0 98 | total_snapshots = 0 99 | total_images = 0 100 | total_vpcs = 0 101 | total_subnets = 0 102 | total_peering_connections = 0 103 | total_acls = 0 104 | total_IPs = 0 105 | total_NAT = 0 106 | total_endpoints = 0 107 | 108 | for region in region_list: 109 | ec2 = session.resource('ec2', region_name=region) 110 | ec2client = session.client('ec2', region_name=region) 111 | 112 | # build the collections to count 113 | instance_iterator = ec2.instances.all() 114 | volume_iterator = ec2.volumes.all() 115 | security_group_iterator = ec2.security_groups.all() 116 | snapshot_iterator = ec2.snapshots.filter(OwnerIds=[account_id]) 117 | image_iterator = ec2.images.filter(Owners=[account_id]) 118 | vpc_iterator = ec2.vpcs.all() 119 | subnet_iterator = ec2.subnets.all() 120 | vpc_peering_connection_iterator = ec2.vpc_peering_connections.all() 121 | network_acl_iterator = ec2.network_acls.all() 122 | vpc_address_iterator = ec2.vpc_addresses.all() 123 | nat_gateways = ec2client.get_paginator('describe_nat_gateways') 124 | nat_gateway_iterator = nat_gateways.paginate() 125 | endpoints = ec2client.describe_vpc_endpoints() 126 | 127 | 128 | # count resources 129 | instance_counter = len(list(instance_iterator)) 130 | group_counter = len(list(security_group_iterator)) 131 | volume_counter = len(list(volume_iterator)) 132 | snapshot_counter = len(list(snapshot_iterator)) 133 | image_counter = len(list(image_iterator)) 134 | vpc_counter = len(list(vpc_iterator)) 135 | subnet_counter = len(list(subnet_iterator)) 136 | peering_counter = len(list(vpc_peering_connection_iterator)) 137 | acl_counter = len(list(network_acl_iterator)) 138 | ip_counter = len(list(vpc_address_iterator)) 139 | gateway_counter = 0 140 | for gateway in nat_gateway_iterator: 141 | gateway_counter += len(gateway['NatGateways']) 142 | endpoint_counter = len(endpoints['VpcEndpoints']) 143 | 144 | # add to the cross region totals 145 | total_instances = total_instances + instance_counter 146 | total_groups += group_counter 147 | total_volumes += volume_counter 148 | total_snapshots += snapshot_counter 149 | total_images += image_counter 150 | total_vpcs += vpc_counter 151 | total_subnets += subnet_counter 152 | total_peering_connections += peering_counter 153 | total_acls += acl_counter 154 | total_IPs += ip_counter 155 | total_NAT += gateway_counter 156 | total_endpoints += endpoint_counter 157 | 158 | # Add the counts to the per-region counter 159 | resource_counts[region]['instances'] = instance_counter 160 | resource_counts[region]['volumes'] = volume_counter 161 | resource_counts[region]['security_groups'] = group_counter 162 | resource_counts[region]['snapshots'] = snapshot_counter 163 | resource_counts[region]['images'] = image_counter 164 | resource_counts[region]['vpcs'] = vpc_counter 165 | resource_counts[region]['subnets'] = subnet_counter 166 | resource_counts[region]['peering connections'] = peering_counter 167 | resource_counts[region]['network ACLs'] = acl_counter 168 | resource_counts[region]['elastic IPs'] = ip_counter 169 | resource_counts[region]['NAT gateways'] = gateway_counter 170 | resource_counts[region]['VPC Endpoints'] = endpoint_counter 171 | 172 | 173 | resource_totals['Instances'] = total_instances 174 | resource_totals['Volumes'] = total_volumes 175 | resource_totals['Security Groups'] = total_groups 176 | resource_totals['Snapshots'] = total_snapshots 177 | resource_totals['Images'] = total_images 178 | resource_totals['VPCs'] = total_vpcs 179 | resource_totals['Subnets'] = total_subnets 180 | resource_totals['VPC Peering Connections'] = total_peering_connections 181 | resource_totals['Network ACLs'] = total_acls 182 | resource_totals['Elastic IP Addresses'] = total_IPs 183 | resource_totals['NAT Gateways'] = total_NAT 184 | resource_totals['VPC Endpoints'] = total_endpoints 185 | 186 | def iam_counter(): 187 | iam = session.resource('iam', region_name='us-west-2') 188 | 189 | user_iterator = iam.users.all() 190 | group_iterator = iam.groups.all() 191 | role_iterator = iam.roles.all() 192 | policy_iterator = iam.policies.filter(Scope='Local') 193 | saml_provider_iterator = iam.saml_providers.all() 194 | 195 | total_users = len(list(user_iterator)) 196 | total_groups = len(list(group_iterator)) 197 | total_roles = len(list(role_iterator)) 198 | total_policies = len(list(policy_iterator)) 199 | total_saml = len(list(saml_provider_iterator)) 200 | 201 | resource_totals['Users'] = total_users 202 | resource_totals['Groups'] = total_groups 203 | resource_totals['Roles'] = total_roles 204 | resource_totals['Policies'] = total_policies 205 | resource_totals['SAML Providers'] = total_saml 206 | 207 | def autoscaling_counter(): 208 | # get list of supported regions 209 | region_list = session.get_available_regions('autoscaling') 210 | 211 | # initialize cross region totals 212 | total_autoscaling_groups = 0 213 | total_launch_configurations = 0 214 | 215 | # iterate through regions and count 216 | for region in region_list: 217 | client = session.client('autoscaling', region_name=region) 218 | 219 | # pull data using paginators 220 | autoscaling = client.get_paginator('describe_auto_scaling_groups') 221 | configurations = client.get_paginator('describe_launch_configurations') 222 | autoscale_iterator = autoscaling.paginate() 223 | configurations_iterator = configurations.paginate() 224 | 225 | # initialize region counts 226 | autoscale_count = 0 227 | configuration_count = 0 228 | 229 | for autoscale in autoscale_iterator: 230 | autoscale_count += len(autoscale['AutoScalingGroups']) 231 | for configuration in configurations_iterator: 232 | configuration_count += len(configuration['LaunchConfigurations']) 233 | 234 | total_autoscaling_groups += autoscale_count 235 | total_launch_configurations += configuration_count 236 | 237 | 238 | resource_counts[region]['autoscale groups'] = autoscale_count 239 | resource_counts[region]['launch configurations'] = configuration_count 240 | 241 | 242 | resource_totals['Autoscale Groups'] = total_autoscaling_groups 243 | resource_totals['Launch Configurations'] = total_launch_configurations 244 | 245 | def balancer_counter(): 246 | # get list of supported regions 247 | elb_region_list = session.get_available_regions('elb') 248 | elbv2_region_list = session.get_available_regions('elbv2') 249 | 250 | # initalize cross region totals 251 | elb_total = 0 252 | elbv2_total = 0 253 | 254 | # First count up the classic ELBs 255 | for region in elb_region_list: 256 | elb = session.client('elb', region_name=region) 257 | 258 | # pull data using paginator 259 | elb_paginator = elb.get_paginator('describe_load_balancers') 260 | elb_iterator = elb_paginator.paginate() 261 | 262 | #initialize region count 263 | elb_counter = 0 264 | 265 | for balancer in elb_iterator: 266 | elb_counter += len(balancer['LoadBalancerDescriptions']) 267 | 268 | elb_total += elb_counter 269 | resource_counts[region]['classic load balancers'] = elb_counter 270 | 271 | # Now count up the application and network load balancers 272 | for region in elbv2_region_list: 273 | elb = session.client('elbv2', region_name=region) 274 | 275 | # pull data using paginator 276 | elb_paginator = elb.get_paginator('describe_load_balancers') 277 | elb_iterator = elb_paginator.paginate() 278 | 279 | #initialize region count 280 | elb_counter = 0 281 | 282 | for balancer in elb_iterator: 283 | elb_counter += len(balancer['LoadBalancers']) 284 | 285 | elbv2_total += elb_counter 286 | resource_counts[region]['application and network load balancers'] = elb_counter 287 | resource_totals['Classic Load Balancers'] = elb_total 288 | resource_totals['Application and Network Load Balancers'] = elbv2_total 289 | 290 | def s3_counter(): 291 | total_buckets = 0 292 | # S3 gives you a full count no matter what the region setting 293 | s3 = session.resource('s3', region_name='us-west-2') 294 | bucket_iterator = s3.buckets.all() 295 | bucket_counter = len(list(bucket_iterator)) 296 | total_buckets += bucket_counter 297 | # resource_counts[region]['s3 buckets'] = bucket_counter 298 | resource_totals['S3 Buckets'] = total_buckets 299 | 300 | def lambda_counter(): 301 | region_list = session.get_available_regions('lambda') 302 | 303 | total_functions = 0 304 | 305 | for region in region_list: 306 | aws_lambda = session.client('lambda', region_name=region) 307 | function_counter = 0 308 | function_paginator = aws_lambda.get_paginator('list_functions') 309 | function_iterator = function_paginator.paginate() 310 | for function in function_iterator: 311 | function_counter += len(function['Functions']) 312 | total_functions += function_counter 313 | resource_counts[region]['lambdas'] = function_counter 314 | resource_totals['Lambda Functions'] = total_functions 315 | 316 | def glacier_counter(): 317 | region_list = session.get_available_regions('glacier') 318 | 319 | total_vaults = 0 320 | 321 | for region in region_list: 322 | glacier = session.resource('glacier', region_name=region) 323 | vault_iterator = glacier.vaults.all() 324 | vault_counter = len(list(vault_iterator)) 325 | total_vaults += vault_counter 326 | resource_counts[region]['glacier vaults'] = vault_counter 327 | resource_totals['Glacier Vaults'] = total_vaults 328 | 329 | def cloudwatch_rules_counter(): 330 | region_list = session.get_available_regions('events') 331 | 332 | total_events = 0 333 | 334 | for region in region_list: 335 | cloudwatch = session.client('events', region_name=region) 336 | rules = cloudwatch.list_rules() 337 | events_counter = len(rules['Rules']) 338 | total_events += events_counter 339 | resource_counts[region]['cloudwatch rules'] = events_counter 340 | resource_totals['Cloudwatch Rules'] = total_events 341 | 342 | def config_counter(): 343 | region_list = session.get_available_regions('config') 344 | 345 | total_config_rules = 0 346 | 347 | for region in region_list: 348 | config = session.client('config', region_name=region) 349 | config_rules_counter = 0 350 | config_rules_paginator = config.get_paginator('describe_config_rules') 351 | config_rules_iterator = config_rules_paginator.paginate() 352 | for rule in config_rules_iterator: 353 | config_rules_counter += len(rule['ConfigRules']) 354 | total_config_rules += config_rules_counter 355 | resource_counts[region]['config rules'] = config_rules_counter 356 | resource_totals['Config Rules'] = total_config_rules 357 | 358 | def cloudtrail_counter(): 359 | region_list = session.get_available_regions('cloudtrail') 360 | 361 | total_trails = 0 362 | 363 | for region in region_list: 364 | cloudtrail = session.client('cloudtrail', region_name=region) 365 | trails = cloudtrail.describe_trails() 366 | trails_counter = len(trails['trailList']) 367 | total_trails += trails_counter 368 | resource_counts[region]['cloudtrail trails'] = trails_counter 369 | resource_totals['CloudTrail Trails'] = total_trails 370 | 371 | def sns_counter(): 372 | region_list = session.get_available_regions('sns') 373 | 374 | total_topics = 0 375 | 376 | for region in region_list: 377 | sns = session.resource('sns', region_name=region) 378 | topic_iterator = sns.topics.all() 379 | topic_counter = len(list(topic_iterator)) 380 | total_topics += topic_counter 381 | resource_counts[region]['sns topics'] = topic_counter 382 | resource_totals['SNS Topics'] = total_topics 383 | 384 | def kms_counter(): 385 | region_list = session.get_available_regions('kms') 386 | 387 | total_keys = 0 388 | 389 | for region in region_list: 390 | kms = session.client('kms', region_name=region) 391 | keys_counter = 0 392 | kms_paginator = kms.get_paginator('list_keys') 393 | kms_iterator = kms_paginator.paginate() 394 | for key in kms_iterator: 395 | keys_counter += len(key['Keys']) 396 | total_keys += keys_counter 397 | resource_counts[region]['kms keys'] = keys_counter 398 | resource_totals['KMS Keys'] = total_keys 399 | 400 | def dynamo_counter(): 401 | region_list = session.get_available_regions('dynamodb') 402 | 403 | total_tables = 0 404 | 405 | for region in region_list: 406 | dynamodb = session.resource('dynamodb', region_name=region) 407 | table_iterator = dynamodb.tables.all() 408 | table_counter = len(list(table_iterator)) 409 | total_tables += table_counter 410 | resource_counts[region]['dynamo tables'] = table_counter 411 | resource_totals['Dynamo Tables'] = total_tables 412 | 413 | def rds_counter(): 414 | region_list = session.get_available_regions('rds') 415 | 416 | total_dbinstances = 0 417 | 418 | for region in region_list: 419 | rds = session.client('rds', region_name=region) 420 | dbinstances_counter = 0 421 | rds_paginator = rds.get_paginator('describe_db_instances') 422 | rds_iterator = rds_paginator.paginate() 423 | for instance in rds_iterator: 424 | dbinstances_counter += len(instance['DBInstances']) 425 | total_dbinstances += dbinstances_counter 426 | resource_counts[region]['rds instances'] = dbinstances_counter 427 | resource_totals['RDS Instances'] = total_dbinstances 428 | 429 | if __name__ == "__main__": 430 | controller() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click 2 | boto3 3 | botocore 4 | --------------------------------------------------------------------------------