An AWS Organizations tag policy does not require a tag. It governs what a tag key may look like once a resource is already tagged, and AWS says so in one line: "Untagged resources or tags that aren't defined in the tag policy aren't evaluated for compliance with the tag policy." Enforcement mode is real, but it is opt-in and partial. Counting AWS's own table of supported resource types gives 1,493 distinct types, of which 343 support enforcement and 1,148 can only be reported on.
What does an AWS Organizations tag policy actually enforce?
Capitalization and allowed values, on tags that already exist. The tag policies overview defines the scope precisely: "In a tag policy, you specify tagging rules applicable to resources when they are tagged." A policy names a tag key with the capitalization you want, optionally lists acceptable values, and optionally opts into enforcement for named resource types. This is the basic syntax, quoted from the tag policy syntax page:
{
"tags": {
"costcenter": {
"tag_key": {
"@@assign": "CostCenter"
},
"tag_value": {
"@@assign": [
"100",
"200",
"300*"
]
},
"enforced_for": {
"@@assign": [
"secretsmanager:ALL_SUPPORTED"
]
}
}
}
}
Two defaults in that syntax do more damage than anything else in the feature.
The first: "If the tag policy doesn't specify a tag value for a tag key, any
value (including no value at all) is considered compliant." A policy that
names only a key therefore passes an empty string. The second: "If case
treatment isn't defined, lowercase is the default case treatment for tag
keys." Write a policy that omits tag_key and you have quietly
standardized your organization on costcenter, not
CostCenter, which is not the key most teams think they are
reporting on.
Can a tag policy stop an untagged resource from being created?
No, and AWS states it in an Important block on the enforcement page: "Basic compliance rules do not enforce tag compliance on resources that are created without tags. This capability does not enforce missing tag keys. You cannot use this capability to ensure that required or mandatory tag keys are configured at resource creation." The same paragraph hands the job to another service: "Use SCPs to prevent IAM users and roles in target accounts from creating certain resource types if the request doesn't include the specified tags."
The reporting side has the matching hole. The
tagging compliance report
"includes only resources that have had at least one user-defined tag at any
point in their lifecycle", and, again in an Important block, "Untagged
resources don't appear as non-compliant in results." A tag policy compliance
score of 100% is therefore compatible with an account full of untagged EC2
instances. AWS's own answer on that page is a different service: run AWS
Resource Explorer with the
special tag:none filter,
which comes with a caveat worth reading twice: "The tag:none filter applies to
only tags that are created by the user. Tags that are generated and maintained
by AWS are exempt from this filter and still appear in the results."
There is a second capability, "Required tag key", and its own field name gives
the game away: in a policy it is written report_required_tag_for.
Enforcement for it does not happen in the control plane at all. It happens in
your pipeline, through the
infrastructure-as-code integration,
which offers "Warn mode: Allows deployments to proceed but generates warnings
when required tags are missing" and "Fail mode: Blocks deployments that are
missing required tags". It is not organization-wide either: "you must activate
the AWS::TagPolicies::TaggingComplianceValidator hook in every AWS account and
Region where you want to enforce required tagging compliance", and the
Terraform path needs a provider bump, since "you need to update your Terraform
AWS Provider to 6.22.0 or above and enable tag policy validation in your
provider configuration". The CloudFormation side is narrower still, because
"This hook only functions as a StackHook. It has no effect when used as a
resource hook." Anything created by a console click, a raw API call or a
service acting on your behalf never passes through that check.
Which AWS resource types support tag policy enforcement?
Fewer than a quarter of them. AWS publishes the list as one long table on the
services and resource types that support enforcement
page, with a Yes or No in four columns per row and no totals anywhere. We
parsed it: 1,598 rows, of which 94 are ALL_SUPPORTED wildcard
pseudo-rows rather than resource types. That leaves 1,504 named rows, and
collapsing 11 duplicate listings (the same resource type listed once per
CloudFormation alias, agreeing on every column) gives 1,493 distinct resource
types across 317 services.
| Counted from AWS's supported-resource-types table | Resource types | Share |
|---|---|---|
| Distinct resource types listed | 1,493 | 100% |
| Basic compliance rules, reporting mode | 1,491 | 99.9% |
| Basic compliance rules, enforcement mode | 343 | 23.0% |
| Required tag key, enforce for IaC | 650 | 43.5% |
| Reportable but not enforceable | 1,148 | 76.9% |
By service the split is starker. Of the 317 services in the table, 103 have at least one resource type you can enforce on, and 214 have none: you can report on them and nothing more. The per-type detail is where a tagging standard meets reality, because the gaps are not in obscure services.
| Resource type | Reporting mode | Enforcement mode | Enforce for IaC |
|---|---|---|---|
ec2:instance | Yes | Yes | Yes |
ec2:volume | Yes | Yes | Yes |
ec2:natgateway | Yes | Yes | Yes |
s3:bucket | Yes | Yes | Yes |
rds:db | Yes | No | Yes |
rds:cluster | Yes | No | Yes |
lambda:function | Yes | Yes | Yes |
eks:cluster | Yes | Yes | Yes |
dynamodb:table | Yes | Yes | Yes |
elasticloadbalancing:loadbalancer | Yes | Yes | Yes |
A database instance is not enforceable. rds:db and
rds:cluster both carry Yes for reporting and No for enforcement
mode, so the tag policy that blocks a mistyped CostCenter value
on an EC2 instance will let the same mistake through on the RDS instance
beside it. AWS states the constraint without quantifying it: "You can only
enforce compliance with tag policies on supported resource types", and the
wildcard does not rescue you, because "You can't use a wildcard to specify all
services, or to specify a resource type across all services." AWS also warns
that turning enforcement on has blast radius of its own: "If you enable
enforcement, the tag policy prevents resources from being tagged and may block
dynamic scaling and provisioning." These counts move as AWS adds services, so
the table is the authority, not the totals.
How do you require a tag at creation time with an SCP?
With the aws:RequestTag and aws:TagKeys global
condition keys, which inspect the tags in the request rather than the tags on
the resource. The
global condition key reference
is clear about what each one sees: aws:RequestTag is "included in
the request context when tag key-value pairs are passed in the request", and
aws:TagKeys is "included in the request context if the operation
supports passing tags in the request". This is the pattern, quoted verbatim
from
controlling access to AWS resources using tags.
It is an identity-based policy in AWS's example, not an SCP, but the condition
block is the part an SCP reuses:
{
"Version":"2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "ec2:CreateTags",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringEquals": {
"aws:RequestTag/environment": [
"preprod",
"production"
]
},
"ForAllValues:StringEquals": {"aws:TagKeys": "environment"}
}
}
}
The piece that turns a value restriction into a tag requirement is the
Null operator. AWS's second example on that page uses it and
explains why: "The Null condition ensures that the condition evaluates to
false if there are no tags in the request." The
condition operator reference
gives the semantics to invert for a Deny: "Use a Null condition operator to
check if a condition key is absent at the time of authorization."
Two details decide whether such a policy works. First, tagging on create is often a separate action from creating, so the policy has to name both. AWS spells this out for snapshots: "to limit tags when someone creates an Amazon EC2 snapshot, you must include the ec2:CreateSnapshot creation action and the ec2:CreateTags tagging action in the policy." Second, an SCP has exemptions that a tag policy does not: "SCPs don't affect users or roles in the management account", and you cannot use one to restrict "Any action performed using permissions that are attached to a service-linked role". Remember too that "an SCP never grants permissions", so the guardrail only narrows what an identity policy already allows. Organizations no longer prints tagging SCP examples in the user guide at all: its SCP examples page now points at a GitHub repository that "contains example policies to get started or mature your usage of AWS SCPs".
Why does an SCP requiring CostCenter accept costcenter?
Because tags are case sensitive and the condition keys that police them are not. The Tagging AWS Resources guide is unambiguous: "Tag keys are case sensitive." The IAM condition key reference says the opposite about matching, in the same words reversed: "Tag keys are not case-sensitive." Values behave the other way round again, since "Values in these tag key/value pairs are case-sensitive."
Read together, the two pages describe the exact failure mode. AWS names it
directly: "AWS services that support tags might allow you to create multiple
tag key names that differ only by case, such as tagging an Amazon EC2 instance
with stack=production and Stack=test", and "Key names are not case sensitive in
policy conditions." So an SCP demanding
aws:RequestTag/CostCenter is satisfied by a request carrying
costcenter. The guardrail passes. The bill then shows two
separate tag keys, each needing its own activation, and the chargeback query
that groups by one of them silently drops the other. That is the enforcement
gap most often mistaken for a data problem in the billing console.
The fix is to use both mechanisms for the halves they each cover. AWS's own
recommendation for the policy side is aws:TagKeys with
ForAllValues, because it "stops users from including other keys,
such as accidentally using Environment instead of environment". The tag policy
side supplies the capitalization rule that an IAM condition cannot express.
The enforcement page describes that option as treating CostCenter,
costCenter and Costcenter as unique tag keys, which
is the check no condition key will do for you.
What do AWS Config and the Resource Groups Tagging API catch that tag policies miss?
Everything that already exists, which is the majority of the problem on any account older than a week. The managed rule required-tags evaluates resources against a list of required keys, with two limits stated on the page: "You can check up to 6 tags at a time", and the rule is detective only, since "this rule does not prevent you from creating resources with incorrect tags". Its coverage is narrower than tag policies by an order of magnitude: the rule lists 30 resource types, against the 1,493 in the tag policy table, which is 2.0% of them. It is also not a free control: a broad recorder plus a required-tags rule is a real line item, and the meters are in what AWS Config costs.
For a point-in-time sweep instead of a continuous one, the Resource Groups Tagging API reads tags across services in one call, paginated: "A request can include up to 50 keys, and each key can include up to 20 values", and each page returns at most 100 resources, since "You can specify a minimum of 1 and a maximum value of 100". It answers which resources carry which tags. It does not answer what the untagged ones cost, which is a question only the billing data can settle.
Does enforcing a tag make it appear in your AWS bill?
No. Enforcement and billing are separate systems with separate switches. Activating user-defined cost allocation tags opens with the rule: "For tags to appear on your billing reports, you must activate them." A tag policy lives in AWS Organizations and is managed by whoever owns governance. Activation lives in the Billing and Cost Management console of the management account and is managed by whoever owns the bill. Nothing connects the two, so the common outcome is a perfectly enforced tag that produces no billing column at all.
The gap is not only organizational. Activation is not retroactive by default, and the recovery path has its own conditions, which is the subject of why cost allocation tags show up empty. For costs that no tag can reach, because they belong to a shared resource rather than a workload, cost categories with split charges are the allocation mechanism rather than more tagging. And if the tagged totals still do not agree with each other, the reconciliation is in why Cost Explorer and your CUR differ.
Which tag enforcement mechanism should you use for which job?
All three, for different halves of the problem. None of them covers the others.
| Mechanism | Blocks an untagged create? | What it does cover | Documented limit |
|---|---|---|---|
| Organizations tag policy | No | Allowed values and capitalization on tags that exist; compliance reporting | 343 of 1,493 resource types support enforcement mode; untagged resources are never evaluated |
| Tag policy, required tag key with IaC | In the pipeline only | CloudFormation, Terraform and Pulumi deployments missing a required key | Hook activated per account and per Region; console and raw API calls bypass it |
SCP with aws:RequestTag and aws:TagKeys |
Yes | Deny creates whose request carries no tag, or the wrong tag keys | No effect in the management account or on service-linked roles; key matching ignores case |
AWS Config required-tags |
No | Continuous detection of existing resources missing required keys | 30 resource types, up to 6 tag keys per rule, and it never blocks a create |
Resource Explorer tag:none, Tagging API |
No | Finding untagged resources, which compliance reports exclude by design | Point-in-time inventory, and AWS-generated tags are exempt from the filter |
A workable order follows from the limits rather than from preference. Write
the tag policy first, in reporting mode, with tag_key set
explicitly so the lowercase default cannot pick your standard for you. Add the
SCP for the handful of create actions that account for most of the spend,
because that is the only layer that refuses an untagged resource. Use Resource
Explorer to find what is already untagged, since the compliance report will
not show it. Activate the tag keys for cost allocation, in the management
account, before anyone measures anything. Then turn on enforced_for
for the resource types that support it, on one account first, because AWS's
own warning about blocked scaling and provisioning is the failure mode that
reaches production fastest.