Enforce Tagging Policies with Pulumi CrossGuard
Cost allocation and ownership fall apart without consistent tags. This guide, part of Pulumi policy as code under Pulumi patterns and provider management, writes a CrossGuard policy that requires every taggable resource to carry a mandatory set of tags before a deploy is allowed.
Context
Finance wants every resource tagged with owner, env, and cost-center. Enforcing that with a CrossGuard policy means untagged resources fail preview, so the tag set is guaranteed rather than hoped for.
Prerequisites
pulumi-policyinstalled and a working policy pack skeleton- A list of resource types that support tags in your stacks
- Agreement on the exact required tag keys
# CLI: confirm the pack runs before adding the tag rule
pulumi preview --policy-pack ./policy
Implementation
Match any resource whose props include a tags map and report the missing keys. Keeping the required set in one constant makes the rule easy to audit.
# policy/tags.py — require a fixed tag set on taggable resources
# CLI: pulumi preview --policy-pack ./policy
from pulumi_policy import ResourceValidationPolicy, ReportViolation
REQUIRED = {"owner", "env", "cost-center"}
def require_tags(args, report: ReportViolation) -> None:
props = args.props
if "tags" not in props: # resource is not taggable — skip
return
present = set((props.get("tags") or {}).keys())
missing = REQUIRED - present
if missing:
report(f"Missing required tags: {', '.join(sorted(missing))}.")
tag_policy = ResourceValidationPolicy(
name="required-tags",
description="Taggable resources must carry owner, env, cost-center.",
validate=require_tags)
Verification
Deploy-preview a stack with a deliberately untagged bucket and confirm it fails listing the missing keys, then add the tags and confirm a clean run.
# CLI: expect a violation naming the missing tags
pulumi preview --policy-pack ./policy --stack dev
Gotchas & Edge Cases
Default tags hide gaps. If you set provider-level defaultTags, resources may inherit them and the rule sees them as present — decide whether that satisfies the policy.
Non-taggable resources. Skipping resources without a tags key avoids false positives, but some resources tag via a sub-block; handle those explicitly.
Case sensitivity. Owner and owner are different keys; normalise before comparing if your org is inconsistent.
FAQ
Can I require tag values, not just keys?
Yes — extend the rule to validate the value (e.g. env in a known set) and report when it is missing or invalid.
Does this cover existing resources?
Policies evaluate what a preview plans, so pre-existing untagged resources surface the next time they are updated. Pair with a one-off audit for full coverage.
Where should the required set live?
In one constant in the pack, ideally sourced from the same place your finance team maintains it, so the rule and the policy of record never diverge.
Related
- Writing Pulumi CrossGuard Policies in Python — the foundational policy walkthrough this builds on.
- Pulumi Policy as Code with CrossGuard — the parent cluster on policy packs.
- Scanning Python IaC with Checkov — complementary static scanning of configuration.