Making Terraform refuse an untagged resource
AWS Organizations tag policies have been able to describe required tags for years. Since provider 6.22.0, Terraform can fail the plan on them, which is the first time the rule bites before the resource exists.
Every AWS account we review has a tagging policy written down somewhere and a bill nobody can attribute. The gap is not the policy. The gap is that nothing stops an untagged resource from being created. Since AWS provider 6.22.0, Terraform can refuse the plan before the resource exists.
Why tagging standards decay
A tag policy in AWS Organizations tells you, after the fact, which resources do not comply. That report is useful and almost nobody acts on it, because by the time it appears the resource is running, someone depends on it, and adding a tag means opening a ticket against a team that has moved on.
So the backlog grows. Six months later finance asks which team spent the money and the honest answer is that half the estate cannot be attributed to anyone. We covered this in what a Well-Architected review actually finds, where it is the single most common finding and the one with the biggest payoff.
The fix is to move the check left, into the plan.
What the AWS provider added
Version 6.22.0 of the hashicorp/aws provider added a tag_policy_compliance argument. Set it, and the provider reads your account's effective tag policy during planning and checks every resource it is about to create against it. No API call to create anything. The plan just fails.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 6.22.0"
}
}
}
provider "aws" {
region = "us-east-1"
tag_policy_compliance = "error"
}
Two values matter:
warning: the plan succeeds and prints a diagnostic for every non-compliant resource. This is the roll-out setting.error: the plan fails immediately. This is the enforcement setting.
A failure reads like this:
Error: Tag Policy Compliance
Resource is missing required tag "Owner" as defined in the
effective tag policy for this account.
The engineer sees it in the same 30 seconds they were already waiting on the plan, not three weeks later in a compliance report.
The policy itself
The provider reads a policy you still have to define in Organizations. Tag policies need feature_set = "ALL" and the policy type enabled on the organization:
resource "aws_organizations_organization" "this" {
feature_set = "ALL"
enabled_policy_types = ["TAG_POLICY"]
}
resource "aws_organizations_policy" "required_tags" {
name = "required-tags"
type = "TAG_POLICY"
content = jsonencode({
tags = {
Environment = {
tag_key = { "@@assign" = "Environment" }
tag_value = { "@@assign" = ["prod", "staging", "dev"] }
enforced_for = {
"@@assign" = ["ec2:instance", "s3:bucket", "rds:db", "lambda:function"]
}
}
Owner = {
tag_key = { "@@assign" = "Owner" }
enforced_for = { "@@assign" = ["ec2:instance", "s3:bucket", "rds:db"] }
}
}
})
}
resource "aws_organizations_policy_attachment" "required_tags" {
policy_id = aws_organizations_policy.required_tags.id
target_id = aws_organizations_organizational_unit.workloads.id
}
The plan-time check needs one extra permission on whatever role runs Terraform:
{
"Effect": "Allow",
"Action": ["organizations:ListRequiredTags"],
"Resource": "*"
}
Rolling it out without stopping delivery
Turning this on at error across an existing estate breaks every pipeline on Monday morning. Four steps, in this order:
- Attach the policy to a sandbox OU only, and read the compliance report. This tells you the size of the backlog before anyone is blocked.
- Set
tag_policy_compliance = "warning"everywhere. Plans still pass. Diagnostics start appearing in pipeline output, and teams start fixing their own modules. - Flip to
erroronce the warnings have gone quiet. Do it per OU if the backlog is uneven. - Attach the policy at the organization root.
On a mid-sized estate this takes a few weeks, most of which is waiting for teams to clear their own warnings.
Four things that will catch you out
The argument does not exist below provider 6.22.0. On an older version Terraform reports an unsupported argument, which at least fails loudly rather than silently skipping the check.
The enforced_for block is not optional in practice. Without it the shift-left check will not flag that resource type even in error mode, so a policy that lists Owner but names no resource types enforces nothing.
Tag keys are case-sensitive. Environment and environment are two different tags to AWS, and a team that standardised on lowercase will fail every plan until someone notices.
Provider default_tags are evaluated too. That is usually what you want, but the values still have to appear in the allowed list, so a default tag of Environment = "production" fails a policy that permits prod.
What this does not fix
Resources created outside Terraform are untouched by this. Console clicks, CloudFormation stacks, another team's CDK app: all of it still needs the after-the-fact compliance report. The plan-time check narrows the inflow, it does not close it. If most of your estate is not in Terraform yet, fix that first and the tagging follows.
It also says nothing about whether the tag values are true. A resource tagged Owner = "platform-team" passes even if that team was dissolved a year ago.
Sources
Which Terraform provider version is needed?
hashicorp/aws 6.22.0 or later. Earlier versions have no tag_policy_compliance argument, so the plan runs regardless of the tag policy and you only find out at apply time, or not at all.
Does this replace default_tags?
No, it checks them. default_tags still applies the tags; the policy check makes sure the values you set are the ones the organisation allows. Both are needed, and a default_tags value outside the allowed list will now fail the plan.
What does this not catch?
Anything created outside Terraform. Console clicks, CloudFormation and CDK stacks are unaffected by the provider check, though the tag policy itself still governs them at the API if enforced_for lists the resource type.
Should we start with warning or error?
Warning, everywhere, for long enough to see the noise. Existing estates fail hundreds of resources on the first run, and switching straight to error blocks every pipeline at once.
More notes
What a Well-Architected review actually finds
Two weeks of reading someone else's AWS account, and the same handful of problems keep showing up: single-AZ databases, IAM nobody has audited since 2021, and instances paid for at on-demand rates for three years.
Writing a cloud roadmap people will actually follow
Most cloud roadmaps are a list of technologies with quarters next to them. A useful one names the constraint you are working against, what you will not do, and who owns each piece.
Send us your AWS bill
Thirty minutes with an engineer, no slide deck. We will tell you what we would change first and what it would cost to have us do it.