Enforcing AWS Resource Tagging with Tag Policies and SCPs
A junior-friendly SOP for enforcing mandatory AWS resource tags using Organizations tag policies, a preventive SCP, and AWS Config compliance checks.

In this lesson
Table of Contents
Table of contents
Before you begin
- AWS Organizations enabled with all features
- IAM permissions for Organizations policy management in the management account
- Basic familiarity with AWS CLI and JSON policy syntax
Track this tutorial
Choose your current status and tick each safety check as you complete it. Sign in to sync progress between devices.
Current status
Before you apply the change
Confirm these production-safety controls during the tutorial.
#Operational requirement
Untagged resources are a production liability, not a cosmetic gap. When cost reports show a chunk of spend sitting under “unassigned”, nobody can charge the right business unit, nobody can identify the owner of a stray EBS volume that has billed for months, and incident responders cannot filter CloudTrail or Security Hub findings by environment or owner during a live investigation. An auditor asking “show me every production database in scope for this control” will expose the gap immediately if tagging coverage is patchy.
Your job as the on-call engineer building this control: enforce four mandatory tags — Environment, Owner, CostCentre, Application — using an AWS Organizations tag policy plus a preventive Service Control Policy (SCP) that denies resource creation when tags are missing. AWS Config then covers anything the SCP cannot reach at creation time, such as CloudFormation drift or third-party tooling.
Retain as evidence: the tag policy JSON, the SCP JSON, a captured denied API call, and a Config compliance summary showing before/after remediation counts. Raise a change record before touching any OU — this is a preventive control and it can lock out legitimate deployments if misapplied. Treat this as you would any change with blast radius across multiple accounts: peer review the JSON, schedule a maintenance window for the first attachment, and have a second engineer available to action the rollback command if something goes sideways.
#Prerequisites
- AWS Organizations with all features enabled (not just consolidated billing)
- Management account or delegated administrator access
- Permissions:
organizations:CreatePolicy,organizations:AttachPolicy,organizations:EnablePolicyType - AWS Config already recording in the target member account
- A non-production OU that mirrors the account structure you intend to enforce against, including at least one account running Auto Scaling and one running CloudFormation stacks
- Read access to CloudTrail in the target accounts so denials can be traced back to the SCP evaluation rather than an unrelated IAM boundary
- A documented list of every automation pipeline, CI/CD role and third-party tool that creates taggable resources in scope — this list is what you test against before wide rollout
Before you request any of the Organizations permissions above, confirm who else holds them. Tag policy and SCP authoring is a small blast-radius activity in terms of API calls but a large blast-radius activity in terms of consequence — a single mistyped condition can silently block every EC2 launch across an OU. Limit the number of principals who can create or attach these policies, and log every attach and detach action as a change record, not just a CloudTrail entry.
#Step 1: Enable tag policies
1aws organizations enable-policy-type --root-id r-abcd --policy-type TAG_POLICY
2aws organizations list-roots --query 'Roots[0].PolicyTypes'Remember: the tag policy reports non-compliance but does not block anything on its own. Enforcement comes from the SCP. This distinction matters for change control — attaching a tag policy is low-risk and can go through a standard change, while attaching the SCP requires the higher-scrutiny process described later.
Extend the tag policy JSON to cover Owner, CostCentre and Application using the same structure shown for Environment. Keep the accepted value lists tight where you can — an open-ended Owner tag with no allowed pattern invites inconsistent free-text entries such as “j.smith”, “John Smith” and “jsmith@example.com” all referring to the same person, which defeats the purpose of the control when you try to query by owner later.
#Step 3: Deploy the preventive SCP
1{"Version":"2012-10-17","Statement":[{"Sid":"DenyEC2WithoutMandatoryTags","Effect":"Deny","Action":"ec2:RunInstances","Resource":"arn:aws:ec2:*:*:instance/*","Condition":{"Null":{"aws:RequestTag/Environment":"true","aws:RequestTag/Owner":"true","aws:RequestTag/CostCentre":"true","aws:RequestTag/Application":"true"}}}]}Test in a non-production OU first, always. Attempt a deliberately incomplete launch:

aws ec2 run-instances --image-id ami-0abcdef1234567890 --instance-type t3.micro --tag-specifications 'ResourceType=instance,Tags=[{Key=Environment,Value=Development}]'Expect an unauthorized operation denial. Confirm in CloudTrail that the denial traces to the SCP evaluation, not an unrelated IAM gap. The CloudTrail event should show errorCode: Client.UnauthorizedOperation with the SCP’s policy ID referenced in the additional event data — if it isn’t there, you’re looking at a permissions boundary or identity policy issue instead, and the SCP test hasn’t actually proven anything yet.
Once the single-resource test passes, repeat it against every resource type you plan to cover — RDS instances, S3 buckets if you extend the policy, and any managed service that creates billable infrastructure. Each service has its own request-tag condition key naming, and copying the EC2 statement wholesale without checking the service-specific documentation is a common source of policies that look correct but never actually deny anything.
#Step 4: Add detective coverage
aws configservice put-config-rule --config-rule '{"ConfigRuleName":"required-tags-check","Source":{"Owner":"AWS","SourceIdentifier":"REQUIRED_TAGS"},"InputParameters":"{"tag1Key":"Environment","tag2Key":"Owner","tag3Key":"CostCentre","tag4Key":"Application"}"}'Give it roughly ten minutes to evaluate existing resources, then pull a baseline. Config is what protects you against everything the SCP structurally cannot reach: resources created via CloudFormation before the stack template enforces tags, resources imported from another account during a migration, and anything created by a service whose API doesn’t expose a request-tag condition key at all. Treat the Config rule as a permanent detective backstop rather than a temporary measure — even once the SCP is fully rolled out, drift still happens through console edits and third-party integrations that strip tags on update.
#Staged rollout across the organisation
Do not attach the SCP to more than one OU per change window. A realistic staged plan looks like this: non-production sandbox OU in week one, a single low-traffic production OU in week two once the sandbox has run clean for at least five business days, then the remaining production OUs in week three once you have a Config compliance baseline confirming near-zero non-compliant creates in the pilot OU. Each stage gets its own change record, its own rollback test, and its own sign-off from the account owners affected. Skipping stages to save time is the single most common cause of unplanned production denials in this kind of rollout.
| Stage | Target | Exit criterion | Typical duration |
|---|---|---|---|
| 1 | Sandbox OU | Deliberate denial test passes; Auto Scaling launch template updated and verified | 2–3 days |
| 2 | Single pilot production OU | Five business days with zero unexpected denials in CloudTrail | 5 business days |
| 3 | Remaining production OUs | Config compliance summary shows sustained non-compliant count near zero | 1–2 weeks |
| 4 | Root OU (optional, high scrutiny) | All child OUs independently verified compliant; senior sign-off obtained | Ongoing review |
#Verification
| Check | Command or location | Expected evidence |
|---|---|---|
| Tag policy attached | Organizations console, Tag policies | Policy ID against target OU |
| SCP blocks untagged launch | CLI test in non-production OU | Denial captured with request ID |
| Config rule active | describe-config-rules | ConfigRuleState: ACTIVE |
| Compliance baseline | get-compliance-summary-by-config-rule | Non-compliant count recorded |
Beyond the table above, build a repeatable verification habit rather than a one-off check. Pull the Config compliance summary weekly for the first month after each rollout stage and graph the non-compliant count over time — it should trend towards zero, not stay flat. A flat line usually means either the SCP has a gap a particular team has found (worth investigating, not celebrating) or the Config rule scope excludes a resource type that’s still being created untagged. Keep the raw CLI output from each check as evidence; auditors generally accept a timestamped compliance summary far more readily than a screenshot of a console dashboard.
Rendering diagram...
#Failure Modes or Common Traps
Attaching the SCP to the root OU before testing locks out every account at once, including break-glass automation. Attach to one non-production OU first — no exceptions.
Auto Scaling groups break this constantly. If the launch template doesn’t propagate tags via TagSpecifications, every scale-out event gets denied and your application fails to recover during a capacity event. Test scaling activity explicitly before wider rollout, and do it during business hours with someone watching the scaling activity history, not overnight when nobody notices the group has silently stopped growing.
A tag policy without the SCP is cosmetic. It reports drift; it stops nothing. Don’t let stakeholders assume otherwise — this is the most frequent misunderstanding in post-incident reviews of tagging controls, and it’s worth stating explicitly in your change record so nobody signs off believing enforcement exists when only reporting does.

Watch the Null condition carefully — a typo inverts the logic and either blocks everything or nothing. A condition of "aws:RequestTag/Environment":"true" denies when the tag is absent; changing that value to "false" reverses the entire statement’s intent and the policy will pass code review while doing the opposite of what’s documented.
Service control policies do not evaluate retroactively. Resources created before attachment remain untagged and untouched by the SCP; only Config will ever flag them. Don’t assume attaching the SCP produces an instant clean estate — it only stops new non-compliant creation from that point forward, which is exactly why the detective layer in Step 4 is not optional.
Cross-account roles used by CI/CD pipelines are a frequent blind spot. If a pipeline role assumes into an account and launches infrastructure via a service-linked role or a broadly scoped deployment role, confirm the SCP still evaluates against that assumed role’s requests — it does, because SCPs apply to all principals in the account regardless of how they authenticated, but pipeline owners often assume otherwise and are caught out when a routine deployment starts failing with no warning.
Finally, watch for tag propagation gaps in nested resources. An EC2 instance created with all four mandatory tags does not automatically tag its attached EBS volumes unless the launch request explicitly includes volume tag specifications. Config will flag these volumes separately, and if your SCP only targets ec2:RunInstances without a parallel statement for volume creation, they’ll slip through the preventive layer entirely.
#Rollback
If the SCP causes an unplanned outage, detach it immediately from the affected OU:
aws organizations detach-policy --policy-id p-9876543210 --target-id ou-abcd-11112222Detachment takes effect within seconds. Leave the tag policy and Config rule in place — they are detective only and carry no operational risk. Document the rollback reason in the change record before re-attempting rollout.
After any rollback, run a short retrospective before reattaching. Pull the CloudTrail denial events from the incident window and identify exactly which requests failed and why — was it a missing tag on a legitimate deployment, a misconfigured Auto Scaling launch template, or a genuine typo in the SCP’s condition logic. Fix the root cause, re-run the deliberate denial test from Step 3 in the sandbox OU, and only then schedule a second attachment attempt. Reattaching without root-causing the first failure tends to produce the same outage a second time, usually at a worse hour.
Keep a standing runbook entry with the exact detach command, the policy ID, and the target OU ID pre-filled, so that whoever is on call during a future incident doesn’t need to search for these values under pressure. A rollback command that takes thirty seconds to execute is only useful if it takes thirty seconds to find as well.
#Operational Summary
This control converts tagging from a voluntary convention into an enforced gate: the SCP stops non-compliant creation at the API layer, the tag policy defines the standard, and Config catches anything that slips through via drift or unusual tooling paths. Roll it out OU by OU, verify against Auto Scaling and any automation pipelines first, and always keep a tested rollback command ready before touching a production OU.
Once fully deployed, this becomes a standing control rather than a project with an end date. Review the Config compliance baseline monthly, re-test the deliberate denial case after any change to the SCP or tag policy, and revisit the mandatory tag list annually as the organisation’s cost allocation or ownership model changes — a tag schema designed for four business units will not survive a reorganisation into twelve without deliberate rework. The combination of preventive and detective controls is what makes this durable: preventive enforcement alone would eventually be bypassed by some edge case nobody anticipated, and detective monitoring alone would never have stopped the untagged spend from accumulating in the first place. Together, and only together, they give you both an enforced standard and a reliable way to prove it’s holding.
Comments
Add a thoughtful note on Enforcing AWS Resource Tagging with Tag Policies and SCPs. Comments are checked for spam and held for moderation before appearing.
Related articles
Cloud Infrastructure and Operations
How to Validate a Cloud Infrastructure Task in Amazon Web Services
Learn to design, validate and safely roll back a bounded AWS Cloud Infrastructure and Operations task using read-only checks and a reversible test exercise.
Security & Operations
Designing a Verifiable Security Workflow with Microsoft Defender
A bounded, five-stage Defender security operations workflow scoped to a test device group, with read-only checks, one reversible response, and a rehearsed rollback path.
Systems Engineering
Engineering The IT Toolkit for Predictable PowerShell Operations
How to design, validate and safely roll back one bounded PowerShell IT-toolkit workflow, from diagnostic checks to a rate-limited remediation function.
Discover more
Graduate Learning
Learn More About KBY
About KBY
Learn about our mission, editorial standards, and commitment to trusted engineering knowledge.
Why Trust KBY
Explore the processes and policies that ensure our publications are accurate, useful, and responsible.
Newsletter
Get our latest editorial publications, research and practical insights sent directly to your inbox.
Was this useful?
Build practical engineering skills.
Receive new lessons, learning paths, practical exercises and early-career guidance.