AWS Tagging

Notes

  • Use namespaces (key name separated by colon :). Example: `company-name:department-name:standard-key’
  • Cover RACI (Responsible, Accountable, Consulted and Informed) details with multiple tags.
  • Define tags based on the need. What questions we are going to answer and what tags needed for integration.
  • Implement process for Tag strategy iteration and improvement cycle
  • Identify and promote a team member who is responsible for implementing and guiding tag strategy.
  • Define mandatory tags and apply tagging policies to report or prevent missing tags.
  • Plan review/measure the effectiveness of tags against target questions and goals. Downgrade mandatory tags to optional.
  • Avoid analysis paralasis on keys as it can be altered with commands/scripting or tools like tag editor.

Quotes from resources

While we(AWS) strongly recommend that you adopt the practices outlined in Organizing Your AWS Environment Using Multiple Accounts whitepaper, realistically customers often find themselves with mixed and complex account structures that take time to migrate away from. Implementing a rigorous and effective tagging strategy is the key in this scenario, followed by activating relevant tags for cost allocation in the Billing and Cost Management console (in AWS Organizations, tags can be activated for cost allocation only from the Management Payer account).

Cost Allocation Tags

Once you defined the taging strategy, define cost allocation tags if the tag can answer any FINOPS questions.

Pricing Awareness

AWS pricing pages has very detaild documentation with samples. However it takes time to understand and figure out what is the cost of our resource based on the option we have choosen while creating.

We can tag the cost factor in the tag based on the provision we have made. This creates awareness to the engineers. For example cost-factor: $16/month

Use Tag Editor

Tag editor can be very useful to bulk edit tags to refactor existing tags or add tags by filtering resources.

Brownfield Tagging - Use programs

Tagging existing infrastructure with 1000s of resources will be a overwhelming task. Especially if the resources are not part of infrastructure as code. If you can derive the tag values based on existing attributes or names of the resource, then we could write program to select the resource with filtering and apply tags programatically. Here are few python example bulk tag examples.

VPC Endpoints

import boto3
import logging

# Configure logging
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(levelname)s - %(message)s')

def list_vpc_endpoints(region_name):
    """
    Lists all VPC endpoints in a given AWS region.

    Args:
        region_name (str): The AWS region to list VPC endpoints in (e.g., 'us-east-1').

    Returns:
        list: A list of dictionaries, where each dictionary represents a VPC endpoint.
              Returns an empty list if no VPC endpoints are found or if an error occurs.
    """
    try:
        # Create an EC2 client for the specified region
        ec2_client = boto3.client('ec2', region_name=region_name)

        # Describe VPC endpoints
        response = ec2_client.describe_vpc_endpoints()

        # Extract VPC endpoint information
        vpc_endpoints = response.get('VpcEndpoints', [])  # Safe handling if 'VpcEndpoints' key is missing

        if vpc_endpoints:
            logging.info(f"Found {len(vpc_endpoints)} VPC endpoints in region {region_name}.")
            return vpc_endpoints
        else:
            logging.info(f"No VPC endpoints found in region {region_name}.")
            return []

    except Exception as e:
        logging.error(f"An error occurred: {e}")
        return []



def tag_vpc_endpoint(region_name, endpoint_id, endpoint_service_name):
    """
    Tags a VPC endpoint with the tag "app-name" and the endpoint name as the tag value.

    Args:
        region_name (str): The AWS region where the VPC endpoint exists.
        endpoint_id (str): The ID of the VPC endpoint to tag.
        endpoint_service_name (str): The desired name for the endpoint to be used as the tag value.
    """
    try:
        ec2_client = boto3.client('ec2', region_name=region_name)

        tags = [{
            'Key': 'service-name',
            'Value': endpoint_service_name
        },
        {
            'Key': 'resource-type',
            'Value': 'vpc-endpoint'
        },
        {
            'Key': 'approx-cost',
            'Value': '$16/month' # modify based on your observation/calculation
        }]

        ec2_client.create_tags(Resources=[endpoint_id], Tags=tags)
        logging.info(f"Successfully tagged VPC endpoint {endpoint_id} with app-name:{endpoint_service_name}")

    except Exception as e:
        logging.error(f"Error tagging VPC endpoint {endpoint_id}: {e}")


def main():
    """
    Main function to list VPC endpoints and tag them.
    """

    region = input("Enter the AWS region (e.g., us-east-1): ")
    if not region:
        print("Region cannot be empty. Please provide a valid AWS region.")
        return

    vpc_endpoints = list_vpc_endpoints(region)

    if vpc_endpoints:
        print("\nVPC Endpoints:")
        for endpoint in vpc_endpoints:
            endpoint_id = endpoint['VpcEndpointId']
            endpoint_service_name = endpoint.get('ServiceName', 'UnknownEndpoint') #Default value if ServiceName is empty
            print("------------------------------------")
            print(f"VPC Endpoint ID: {endpoint_id}")
            print(f"VPC ID: {endpoint['VpcId']}")
            print(f"Service Name: {endpoint['ServiceName']}")
            print(f"State: {endpoint['State']}")
            print(f"Creation Timestamp: {endpoint['CreationTimestamp']}")
            # Tag the VPC endpoint
            tag_vpc_endpoint(region, endpoint_id, endpoint_service_name)

    else:
        print(f"No VPC endpoints found or an error occurred in region {region}.")

if __name__ == "__main__":
    main()

DynamoDB

import boto3

sts_client = boto3.client('sts')  # Create STS client
account_id = sts_client.get_caller_identity()['Account']
print(account_id)

def tag_dynamodb_tables():
    """Tags all DynamoDB tables with the 'app-name' tag,
    using the table name as the tag value.
    """
    dynamodb = boto3.client('dynamodb')
    tables = getAllTableByPaginating(dynamodb)
    tagTables(dynamodb, tables)




def getAllTableByPaginating(dynamodb):
    table_names = []
    last_evaluated_table_name = ''

    try:
        # List all DynamoDB tables using pagination
        while True:
            if last_evaluated_table_name:
                response = dynamodb.list_tables(ExclusiveStartTableName=last_evaluated_table_name)
            else:
                response = dynamodb.list_tables()

            table_names.extend(response.get('TableNames', []))

            last_evaluated_table_name = response.get('LastEvaluatedTableName')

            if not last_evaluated_table_name:
                break
    except Exception as e:
        print(f"Error listing tables: {e}")
        return []

    return table_names

def tagTables(dynamodb, tables):
    for table_name in tables:
        try:
                # Check if the table already has the 'app-name' tag.  This avoids unnecessary API calls.
            existing_tags = dynamodb.list_tags_of_resource(
                    ResourceArn=f'arn:aws:dynamodb:{boto3.session.Session().region_name}:{account_id}:table/{table_name}'
                )

            app_name_tag_exists = False
            if 'Tags' in existing_tags:
                for tag in existing_tags['Tags']:
                    if tag['Key'] == 'app-name':
                        app_name_tag_exists = True
                        break

            if not app_name_tag_exists: # Only tag if it doesn't exist.
                    # Tag the table
                dynamodb.tag_resource(
                        ResourceArn=f'arn:aws:dynamodb:{boto3.session.Session().region_name}:{account_id}:table/{table_name}',
                        Tags=[
                            {
                                'Key': 'app-name',
                                'Value': table_name
                            },
                        ]
                    )
                print(f"Tagged table: {table_name}")
            else:
                print(f"Table {table_name} already has the app-name tag. Skipping.")

        except Exception as e:
            print(f"Error tagging table {table_name}: {e}")


if __name__ == "__main__":
    tag_dynamodb_tables()

Workspaces

import boto3

def tag_workspaces():
    """
    Lists all AWS WorkSpaces and adds a tag with Key='primary-owner' and Value equal to the Workspace's username.

    Prerequisites:
        - The IAM role/user running this script needs permission for:
            workspaces:DescribeWorkspaces
            workspaces:CreateTags
    """
    try:
        workspaces = boto3.client('workspaces')
        all_workspaces = []
        next_token = ''

        while True:
            if next_token:
                response = workspaces.describe_workspaces(NextToken=next_token)
            else:
                response = workspaces.describe_workspaces()

            workspaces_data = response.get('Workspaces', [])

            for workspace in workspaces_data:
                workspace_id = workspace.get('WorkspaceId')
                username = workspace.get('UserName')

                if workspace_id and username:
                    try:
                        # Tag the workspace
                        workspaces.create_tags(
                            ResourceId=workspace_id,
                            Tags=[
                                {
                                    'Key': 'primary-owner',
                                    'Value': username
                                }
                            ]
                        )
                        print(f"Successfully tagged Workspace '{workspace_id}' with primary-owner='{username}'")
                    except Exception as tag_err:
                        print(f"Error tagging Workspace '{workspace_id}': {tag_err}")

            next_token = response.get('NextToken')
            if not next_token:
                break


    except Exception as e:
        print(f"Error listing or tagging WorkSpaces: {e}")


if __name__ == "__main__":
    tag_workspaces()

Resources

  • https://docs.aws.amazon.com/whitepapers/latest/tagging-best-practices/tagging-best-practices.html
  • https://wellarchitectedlabs.com/cost-optimization/
  • https://workshops.aws/categories/Cost%20Management