I was working on a task that requires to update certain tags on all the resources in an AWS account. While there are multiple ways to apporach this problem, I opted to use a AWS native solution AWS Resource Groups Tagging API. It provides a nice simple API to get resources by filters and modify tags on those resources. Another way to achieve this is by putting a Conformance Pack with both rules and possibly a custom lambda based remediation. Anyways this post is about resource tagging api.

Challenge

It sounds easy unless you try to drill down each and every action that different services provides. For example if you want to add tags to a ec2 instance then your role should have permission ec2:CreateTags assigned. Similary you should have permission elasticbeanstalk:AddTags in order to put tags on a elasticbeanstalk application. As you can see it can become quite cumbersome and for worst, AWS service authorization does not guarantee any naming conventions.

Solution

Luckily, one of my coworker found a URL which actually AWS calls itself for their policy simulator UI [https://awspolicygen.s3.amazonaws.com/js/policies.js]. This gives us a map of service prefixes and their respective allowed actions.

With this data, It becomes very easy to filter out all the tagging related operations for every (fingers crossed) AWS service. I just had to write a little dirty script

import json
from urllib.request import Request, urlopen
from pprint import pprint

# This is a helper file to generate most of the tagging permissions defined by various AWS sevices.
def get_iam_action_prefixes() -> str:
    """Gets all action prefixes in the IAM policy generator."""
    policies_url = "https://awspolicygen.s3.amazonaws.com/js/policies.js"
    with urlopen(Request(policies_url)) as response:
        body: str = response.read().decode()
    policies_markup = body.split("=")[1]
    policies_doc = json.loads(policies_markup)
    action_prefixes = {
        service["StringPrefix"]: [action for action in service['Actions'] if 'Tag' in action and not(action.startswith(('Describe', 'List', 'Get', 'Delete', 'Remove')))] for service in policies_doc["serviceMap"].values()
    }
    action_prefixes = dict((k, v) for k, v in action_prefixes.items() if v)

    string = f""
    for service, actions in action_prefixes.items():
        for action in actions:
            string = string + f"- '{service}:{action}'" + "\n"

    return string

print(get_iam_action_prefixes())

Running this script will output a formatted list of permissions that can be easily pasted into a cloudformation template

- 's3:GetObject'
- 'cloudformation:UpdateStack'
- 'comprehend:TagResource'
- 'elasticfilesystem:CreateTags'
- 'elasticfilesystem:TagResource'
- 'glue:TagResource'
- 'iotthingsgraph:TagResource'
- 'evidently:TagResource'
- 'savingsplans:TagResource'
- 'ssm:AddTagsToResource'
- 'ssm:GetParameters'
- 'sso:TagResource'
- 'iot:TagResource'
- 'fis:TagResource'
- 'lambda:TagResource'
- 'mgn:TagResource'
- 'dataexchange:TagResource'
- 'machinelearning:AddTags'
- 'auditmanager:TagResource'
- 'guardduty:TagResource'
- 'events:TagResource'
- 'lex:TagResource'
- 'proton:TagResource'
- 'ram:TagResource'
- 'mediaconnect:TagResource'
- 's3:PutBucketTagging'
- 's3:PutJobTagging'
- 's3:PutObjectTagging'
- 's3:PutObjectVersionTagging'
- 's3:PutStorageLensConfigurationTagging'
- 's3:GetBucketTagging'
- 's3:ReplicateTags'
- 'sagemaker:AddTags'
- 'lakeformation:AddLFTagsToResource'
- 'lakeformation:CreateLFTag'
- 'lakeformation:SearchDatabasesByLFTags'
- 'lakeformation:SearchTablesByLFTags'
- 'lakeformation:UpdateLFTag'
- 'aps:TagResource'
- 'globalaccelerator:TagResource'
- 'profile:TagResource'
- 'forecast:TagResource'
- 'clouddirectory:TagResource'
- 'mediatailor:TagResource'
- 'route53:ChangeTagsForResource'
- 'sts:TagSession'
- 'mediapackage:TagResource'
- 'cassandra:TagResource'
- 'resiliencehub:TagResource'
- 'athena:TagResource'
- 'mobiletargeting:TagResource'
- 'route53domains:UpdateTagsForDomain'
- 'opsworks:TagResource'
- 'codedeploy:AddTagsToOnPremisesInstances'
- 'codedeploy:TagResource'
- 'iam:TagInstanceProfile'
- 'iam:TagMFADevice'
- 'iam:TagOpenIDConnectProvider'
- 'iam:TagPolicy'
- 'iam:TagRole'
- 'iam:TagSAMLProvider'
- 'iam:TagServerCertificate'
- 'iam:TagUser'
- 'route53resolver:TagResource'
- 'workmail:TagResource'
- 'route53-recovery-readiness:TagResource'
- 'iotanalytics:TagResource'
- 'connect:TagResource'
- 'ce:TagResource'
- 'ce:UpdateCostAllocationTagsStatus'
- 'synthetics:TagResource'
- 'elastic-inference:TagResource'
- 'refactor-spaces:TagResource'
- 'gamesparks:TagResource'
- 'sqlworkbench:TagResource'
- 'inspector2:TagResource'
- 'appflow:TagResource'
- 'config:TagResource'
- 'rds:AddTagsToResource'
- 'swf:TagResource'
- 'appsync:TagResource'
- 'acm:AddTagsToCertificate'
- 'ssm-incidents:TagResource'
- 'xray:TagResource'
- 'rum:TagResource'
- 'cloudfront:CreateStreamingDistributionWithTags'
- 'cloudfront:TagResource'
- 'eks:TagResource'
- 'fms:TagResource'
- 'kinesis:AddTagsToStream'
- 'ds:AddTagsToResource'
- 'iotsitewise:TagResource'
- 'codestar-notifications:TagResource'
- 'frauddetector:TagResource'
- 'worklink:TagResource'
- 'codestar-connections:TagResource'
- 'workspaces:CreateTags'
- 'lookoutvision:TagResource'
- 'chime:TagAttendee'
- 'chime:TagMeeting'
- 'chime:TagResource'
- 'elasticache:AddTagsToResource'
- 'iotwireless:TagResource'
- 'firehose:TagDeliveryStream'
- 'storagegateway:AddTagsToResource'
- 'elasticmapreduce:AddTags'
- 'batch:TagResource'
- 'connect-campaigns:TagResource'
- 'iotevents:TagResource'
- 'billingconductor:TagResource'
- 'cloudtrail:AddTags'
- 'dynamodb:TagResource'
- 'es:AddTags'
- 'deepracer:TagResource'
- 'voiceid:TagResource'
- 'emr-containers:TagResource'
- 'schemas:TagResource'
- 'networkmanager:TagResource'
- 'cognito-identity:SetPrincipalTagAttributeMap'
- 'cognito-identity:TagResource'
- 'appconfig:TagResource'
- 'apprunner:TagResource'
- 'license-manager:TagResource'
- 'a4b:TagResource'
- 'acm-pca:TagCertificateAuthority'
- 'states:TagResource'
- 'wisdom:TagResource'
- 'greengrass:TagResource'
- 'redshift:CreateTags'
- 'deepcomposer:TagResource'
- 'managedblockchain:TagResource'
- 'waf:TagResource'
- 'appstream:TagResource'
- 'quicksight:TagResource'
- 'wafv2:TagResource'
- 'cases:TagResource'
- 'dlm:TagResource'
- 'wellarchitected:TagResource'
- 'kendra:TagResource'
- 'ivs:TagResource'
- 'lightsail:TagResource'
- 'cloudsearch:AddTags'
- 'emr-serverless:TagResource'
- 'iotfleetwise:TagResource'
- 'backup:TagResource'
- 'databrew:TagResource'
- 'braket:TagResource'
- 'dms:AddTagsToResource'
- 'network-firewall:TagResource'
- 'ssm-contacts:TagResource'
- 'transcribe:TagResource'
- 'mediapackage-vod:TagResource'
- 'devicefarm:TagResource'
- 'groundstation:TagResource'
- 'signer:TagResource'
- 'resource-groups:Tag'
- 'honeycode:TagResource'
- 'amplifyuibuilder:TagResource'
- 'workspaces-web:TagResource'
- 'ecr-public:TagResource'
- 'snow-device-management:TagResource'
- 'rolesanywhere:TagResource'
- 'elemental-activations:TagResource'
- 'grafana:TagResource'
- 'appmesh:TagResource'
- 'kafka:TagResource'
- 'codeguru-reviewer:TagResource'
- 'codeguru-reviewer:UnTagResource'
- 'memorydb:TagResource'
- 'cloudwatch:TagResource'
- 'autoscaling:CreateOrUpdateTags'
- 'shield:TagResource'
- 'iottwinmaker:TagResource'
- 'secretsmanager:TagResource'
- 'fsx:TagResource'
- 'amplify:TagResource'
- 'kinesisvideo:TagResource'
- 'kinesisvideo:TagStream'
- 'medialive:CreateTags'
- 'geo:TagResource'
- 'kms:TagResource'
- 'cloudhsm:AddTagsToResource'
- 'cloudhsm:TagResource'
- 'ec2:CreateTags'
- 'datapipeline:AddTags'
- 'monitron:TagResource'
- 'rbin:TagResource'
- 'macie2:TagResource'
- 'migrationhub-orchestrator:TagResource'
- 'm2:TagResource'
- 'outposts:TagResource'
- 'gamelift:TagResource'
- 'iotfleethub:TagResource'
- 'route53-recovery-control-config:TagResource'
- 'opsworks-cm:TagResource'
- 'timestream:TagResource'
- 'ivschat:TagResource'
- 'discovery:CreateTags'
- 'codecommit:TagResource'
- 'codeguru-profiler:TagResource'
- 'iotdeviceadvisor:TagResource'
- 'sns:TagResource'
- 'cognito-idp:TagResource'
- 'elasticbeanstalk:AddTags'
- 'elasticbeanstalk:UpdateTagsForResource'
- 'applicationinsights:TagResource'
- 'elasticloadbalancing:AddTags'
- 'lookoutequipment:TagResource'
- 'lookoutmetrics:TagResource'
- 'waf-regional:TagResource'
- 'ecs:TagResource'
- 'ecr:PutImageTagMutability'
- 'ecr:TagResource'
- 'dax:TagResource'
- 'tag:TagResources'
- 'logs:TagLogGroup'
- 'redshift-serverless:TagResource'
- 'backup-gateway:TagResource'
- 'servicecatalog:AssociateTagOptionWithResource'
- 'servicecatalog:CreateTagOption'
- 'servicecatalog:DisassociateTagOptionFromResource'
- 'servicecatalog:TagResource'
- 'servicecatalog:UpdateTagOption'
- 'drs:TagResource'
- 'mq:CreateTags'
- 'nimble:TagResource'
- 'airflow:TagResource'
- 's3-object-lambda:PutObjectTagging'
- 's3-object-lambda:PutObjectVersionTagging'
- 'personalize:TagResource'
- 'cloud9:TagResource'
- 'elemental-appliances-software:TagResource'
- 'detective:TagResource'
- 'transfer:TagResource'
- 'panorama:TagResource'
- 'access-analyzer:TagResource'
- 'app-integrations:TagResource'
- 'finspace:TagResource'
- 's3-outposts:PutBucketTagging'
- 's3-outposts:PutObjectTagging'
- 'mediastore:TagResource'
- 'bugbust:TagResource'
- 'healthlake:TagResource'
- 'iot1click:TagResource'
- 'codepipeline:TagResource'
- 'securityhub:TagResource'
- 'imagebuilder:TagResource'
- 'sqs:TagQueue'
- 'servicediscovery:TagResource'
- 'glacier:AddTagsToVault'
- 'rekognition:TagResource'
- 'mediaconvert:TagResource'
- 'servicequotas:TagResource'
- 'inspector:SetTagsForResource'
- 'robomaker:TagResource'
- 'qldb:TagResource'
- 'codestar:TagProject'
- 'codeartifact:TagResource'
- 'directconnect:TagResource'
- 'datasync:TagResource'
- 'organizations:TagResource'
- 'kinesisanalytics:TagResource'

Hope it helps :)