DEV Community

folarin oyenuga
folarin oyenuga

Posted on

I built a tool to find every AWS resource you can't tag

AWS doesn't publish a list of untaggable resources. can-i-tag-aws scrapes the IAM docs so you can write SCP tagging policies that don't break prod.

Tagging is the backbone of AWS cost allocation, compliance, and access control. Every well-run organization eventually writes an SCP that says "you cannot create a resource without a CostCenter tag."

And then someone's deployment breaks, because they tried to create a resource that cannot be tagged.

So I built can-i-tag-aws, an open source tool that automatically detects every AWS resource that cannot be tagged.

This isn't a hypothetical problem. I work on a multi-tenant, cloud-native Kubernetes platform that hosts thousands of team workloads in namespaces, and I've watched SCP tag-enforcement policies block legitimate deployments because they targeted resources that cannot physically carry tags. In one incident, enforcement policies had to be disabled org-wide to unblock every team on the platform while the root cause was found. In another stretch, four separate SCP enforcement failures surfaced in quick succession, one of them during a full deployment halt. Every time, the failure had the same shape: the policy assumed every resource could be tagged.

The problem nobody talks about

Not all AWS resources support tagging. That's not controversial. What's surprising is that AWS does not publish a single, consolidated list of what can't be tagged.

The information exists, but it's scattered across 400+ pages of the IAM Service Authorization Reference, one page per service. To build an SCP tagging policy that actually works, you need three kinds of exclusions:

  1. Service-level: entire services with no tagging API at all
  2. Resource-level: specific resource types inside otherwise-taggable services
  3. Instance-level: AWS-managed default instances of taggable types (like default.redis7 ElastiCache parameter groups, which reject tags because AWS owns them, not your account) Without all three, your policy either blocks legitimate API calls or leaves compliance holes. And the list changes as AWS ships new services.

What AWS gives you (and where it falls short)

AWS has pieces of the puzzle, but none of them answer "what can't I tag?" proactively:

Resource Groups Tagging API: only sees resources that already exist in your account. It can't tell you about a service before you deploy it.
AWS Config rules: reactive. They flag non-compliant resources after creation, and they still assume the resource is taggable.
Per-service documentation: accurate but scattered. Nobody is going to read 400+ pages by hand, and the answers go stale as AWS updates services.
Cost Allocation Reports: silently omit untaggable resources, which makes FinOps teams chase attribution gaps that can never be closed.
The gap: there is no proactive, consolidated, machine-readable answer to "which AWS resources cannot be tagged?" That's what this tool fills.

What can-i-tag-aws does

It parses the IAM Service Authorization Reference and classifies every resource type across all AWS services. A resource is considered taggable if it has the aws:ResourceTag/${TagKey} condition key or is in scope of a tagging action (TagResource, CreateTags, AddTags). Anything with neither is untaggable.

The output is a single JSON file with:

Every untaggable resource, grouped by service, with the reason
Conditionally taggable types (taggable in general, but AWS-managed default instances reject tags), with

the exact ARN patterns to exclude from SCPs
Mixed-support services broken down per resource type

{
"summary": {
"total_services": 475,
"services_without_tagging_api": 124,
"total_untaggable_resources": 511,
"conditionally_taggable_resource_types": 7
}
}
Enter fullscreen mode Exit fullscreen mode

(Numbers from the latest weekly run at the time of writing. The whole point is that they drift, so run it rather than trusting a blog post.)

No AWS credentials needed. It works offline against the docs. If you want account-specific confirmation, an optional --live flag validates findings against your account via boto3.

Fastest way to try it:

docker run --rm -v $(pwd)/output:/app/output ghcr.io/olu-folarin/can-i-tag-aws
Enter fullscreen mode Exit fullscreen mode

The design decision that matters most

  • Instance-level detection, spotting AWS-managed defaults like default.redis7 that reject tags despite their type being taggable, can't be looked up anywhere, because AWS doesn't publish a registry of managed default instances. So the tool infers them by naming convention.

  • Inference means occasional false positives, which forced an explicit choice about which way to fail. The tool errs toward inclusion, and the reasoning is asymmetric:

  • An unnecessary exclusion in your SCP is harmless. You've exempted a resource that would have passed anyway. A missing exclusion blocks a real deployment at create time, in production, for a team that has no idea why.
    One failure mode costs nothing, the other pages someone. So the bias goes toward the harmless one. If you take nothing else from this post: when your detection is heuristic, decide deliberately which direction it fails, and write that decision down (you might just be saving yourself the stress of trying to figure out what informed the operating design decision months/years down the line).

Keeping it true over time

A point-in-time list of untaggable resources starts rotting the day you generate it, so the repo maintains itself:

  • A weekly scheduled run re-scrapes the reference and regenerates the dataset
  • A pre-flight canary checks the docs' structure first and fails loudly if AWS has restructured the pages, so the run aborts rather than parsing garbage
  • A post-scrape invariant gate sanity-checks the results (counts within plausible bounds) before anything is committed
  • Graduated auto-merge: small drifts merge automatically, anything changing more than 25% of the dataset is held for human review, because a swing that large is more likely a parsing problem than AWS reinventing itself overnight
  • A dead-man's-switch opens a GitHub issue (deduplicated) when a run fails, so a silent breakage can't quietly become a stale dataset The tool also keeps timestamped history with a diff command, so you can see exactly when AWS added or dropped tagging support for anything, and update your policies on evidence rather than on rumour.

Who it's for (and uses beyond the README)

The obvious audience: platform engineers writing SCP tagging policies, and security teams auditing tag enforcement. But the output is just structured data, so it plugs into more workflows:

FinOps: identify which resources will never appear in cost allocation reports, so you stop investigating attribution gaps that are structurally impossible to close.
Policy-as-code (OPA, Conftest, Checkov): skip mandatory-tag checks on resource types that can never pass them, cutting false positives from CI.
Landing zone rollouts: run it as a pre-flight check before enabling tag-enforcement SCPs on new OUs or acquired accounts.
CMDB and asset inventory: don't design workflows that depend on tags for resources that can't carry them.
Drift monitoring: track when AWS adds or drops tagging support, and update policies accordingly.

Honest limitations

It scrapes HTML docs, so if AWS restructures the IAM reference, parsing can break. The canary catches this and opens an issue rather than publishing bad data. It's a point-in-time snapshot, which is why the weekly re-run matters. And instance-level detection is convention-based, with the fail-safe bias described above.

Wrapping up

If you've ever had a tagging SCP break a deployment, or a FinOps report with unexplainable gaps, this is the reference list you previously had to build by hand. It's MIT licensed, runs in Docker with zero credentials, and the output is plain JSON you can feed into whatever governance pipeline you already have.

Repo: can-i-tag-aws
Docker Image: can-i-tag-aws
Issues and PRs welcome, especially reports of AWS-managed resources that reject tags despite their type showing as taggable.

Top comments (0)