DEV Community

Dmitry Romanoff
Dmitry Romanoff

Posted on

Find Untagged AWS Resources Across All Regions with Python

Resource tagging is crucial for AWS cost management, security, and governance. Untagged resources can lead to unexpected costs and make it difficult to track resource ownership. Here's a Python script that scans all AWS regions to find untagged resources.

The Problem

AWS resources without proper tags create several issues:

  • Cost tracking: Can't allocate costs to projects or teams
  • Security: Difficult to identify resource owners
  • Compliance: Many organizations require mandatory tags
  • Cleanup: Hard to identify unused resources

The Solution

This Python script uses boto3 and concurrent processing to efficiently scan all AWS regions for untagged resources.

#!/usr/bin/env python3
import boto3
from concurrent.futures import ThreadPoolExecutor, as_completed

def get_untagged_resources_in_region(region):
    untagged = []
    try:
        session = boto3.Session()

        # EC2 Instances
        ec2 = session.client('ec2', region_name=region)
        instances = ec2.describe_instances()
        for reservation in instances['Reservations']:
            for instance in reservation['Instances']:
                if not instance.get('Tags'):
                    untagged.append(f"EC2 Instance: {instance['InstanceId']}")

        # EBS Volumes
        volumes = ec2.describe_volumes()
        for volume in volumes['Volumes']:
            if not volume.get('Tags'):
                untagged.append(f"EBS Volume: {volume['VolumeId']}")

        # Lambda Functions
        lambda_client = session.client('lambda', region_name=region)
        functions = lambda_client.list_functions()
        for function in functions['Functions']:
            try:
                tags = lambda_client.list_tags(Resource=function['FunctionArn'])
                if not tags.get('Tags'):
                    untagged.append(f"Lambda Function: {function['FunctionName']}")
            except:
                untagged.append(f"Lambda Function: {function['FunctionName']}")

        # RDS Instances
        rds = session.client('rds', region_name=region)
        instances = rds.describe_db_instances()
        for instance in instances['DBInstances']:
            try:
                tags = rds.list_tags_for_resource(ResourceName=instance['DBInstanceArn'])
                if not tags.get('TagList'):
                    untagged.append(f"RDS Instance: {instance['DBInstanceIdentifier']}")
            except:
                untagged.append(f"RDS Instance: {instance['DBInstanceIdentifier']}")

        # VPCs
        vpcs = ec2.describe_vpcs()
        for vpc in vpcs['Vpcs']:
            if not vpc.get('Tags'):
                untagged.append(f"VPC: {vpc['VpcId']}")

        # Security Groups
        security_groups = ec2.describe_security_groups()
        for sg in security_groups['SecurityGroups']:
            if not sg.get('Tags'):
                untagged.append(f"Security Group: {sg['GroupId']}")

        # Subnets
        subnets = ec2.describe_subnets()
        for subnet in subnets['Subnets']:
            if not subnet.get('Tags'):
                untagged.append(f"Subnet: {subnet['SubnetId']}")

    except:
        pass

    return region, untagged

def main():
    session = boto3.Session()
    regions = [r['RegionName'] for r in session.client('ec2').describe_regions()['Regions']]

    with ThreadPoolExecutor(max_workers=15) as executor:
        futures = [executor.submit(get_untagged_resources_in_region, region) for region in regions]

        for future in as_completed(futures):
            region, untagged = future.result()
            if untagged:
                print(f"\n{region} ({len(untagged)} untagged resources):")
                for resource in untagged:
                    print(f"  - {resource}")

    # S3 Buckets (global)
    try:
        s3 = session.client('s3')
        buckets = s3.list_buckets()
        untagged_buckets = []
        for bucket in buckets['Buckets']:
            try:
                s3.get_bucket_tagging(Bucket=bucket['Name'])
            except:
                untagged_buckets.append(f"S3 Bucket: {bucket['Name']}")

        if untagged_buckets:
            print(f"\nGlobal S3 ({len(untagged_buckets)} untagged buckets):")
            for bucket in untagged_buckets:
                print(f"  - {bucket}")
    except:
        pass

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Key Features

  • Multi-region scanning: Checks all enabled AWS regions
  • Concurrent processing: Uses ThreadPoolExecutor for fast execution
  • Multiple resource types: EC2, EBS, VPCs, Security Groups, Subnets, Lambda, RDS, and S3
  • Error handling: Gracefully handles permission issues

Setup

  1. Install boto3:
pip install boto3
Enter fullscreen mode Exit fullscreen mode
  1. Configure AWS credentials:
aws configure
Enter fullscreen mode Exit fullscreen mode

Required IAM Permissions

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:DescribeVolumes",
                "ec2:DescribeVpcs",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeRegions",
                "s3:ListAllMyBuckets",
                "s3:GetBucketTagging",
                "lambda:ListFunctions",
                "lambda:ListTags",
                "rds:DescribeDBInstances",
                "rds:ListTagsForResource"
            ],
            "Resource": "*"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Sample Output

us-east-1 (5 untagged resources):
  - EC2 Instance: i-1234567890abcdef0
  - EBS Volume: vol-1234567890abcdef0
  - VPC: vpc-1234567890abcdef0
  - Security Group: sg-1234567890abcdef0
  - Lambda Function: my-function

us-west-2 (2 untagged resources):
  - RDS Instance: my-database
  - Subnet: subnet-1234567890abcdef0

Global S3 (2 untagged buckets):
  - S3 Bucket: my-bucket-1
  - S3 Bucket: my-bucket-2
Enter fullscreen mode Exit fullscreen mode

Next Steps

Once you identify untagged resources, you can:

  1. Tag them manually through the AWS console
  2. Use AWS CLI to bulk tag resources
  3. Implement tagging policies to prevent future untagged resources
  4. Set up AWS Config rules for automated compliance checking

Conclusion

Regular auditing of untagged resources is essential for AWS governance. This script provides a quick way to identify resources that need attention across your entire AWS infrastructure.

The concurrent approach makes it efficient even for accounts with resources across many regions, helping you maintain better control over your AWS environment.

Top comments (0)