Pulumi Policy as Code with CrossGuard

Guardrails belong in the deployment path, not in a wiki. Pulumi CrossGuard lets you express organisational rules — encryption, tagging, instance sizing, network exposure — as typed Python that runs on every pulumi preview and blocks non-compliant changes before they reach the cloud. This cluster, part of Pulumi patterns and provider management, covers how policy packs work and links to concrete policies you can adopt.

Problem Framing

Reviews catch some misconfigurations, but humans miss the fifth unencrypted bucket of the day. Policy as code makes the rule executable: it evaluates the resource graph Pulumi is about to apply and returns advisory warnings or hard failures. Because the policies are Python, they live in version control, get unit tested, and evolve with the same workflow as the infrastructure they govern — an extension of the security and compliance basics every team needs.

Where policy runs Where policy runs: pulumi preview then policy pack then evaluate resources then pass / fail pulumi preview policy pack evaluate resources pass / fail
A policy pack evaluates the planned resource graph during preview and can fail the run.

Prerequisites

Prerequisites Prerequisites: layered from Python interface / API down to Cloud runtime. Python interface / API Typed resource model Provider plugin State backend Cloud runtime
Prerequisites: the stack from the Python interface down to the cloud runtime.
  • Python 3.9+ with pulumi>=3.0 and pulumi-policy installed in a separate policy project
  • A stack you can preview, and permission to register the policy pack with --policy-pack
  • Agreement on which rules are advisory (warn) versus mandatory (fail), so the pack does not block every deploy on day one
# CLI: confirm the policy SDK is importable
python -c "import pulumi_policy; print('policy sdk ok')"

How Policy Packs Work

A policy pack is a small Python program that registers a PolicyPack with one or more ResourceValidationPolicy rules. Each rule receives a resource's type and properties and reports a violation string when the resource breaks the rule.

Policy pack structure Policy pack structure: layered from PolicyPack (name, level) down to EnforcementLevel: advisory / mandatory. PolicyPack (name, level) ResourceValidationPolicy rules validate(args, report) EnforcementLevel: advisory / mandatory
A pack bundles rules; each rule inspects a resource and may report a violation.
# __main__.py — a minimal policy pack requiring S3 encryption
# CLI: pulumi preview --policy-pack ./policy
from pulumi_policy import (
    PolicyPack, ResourceValidationPolicy, EnforcementLevel, ReportViolation)

def s3_encrypted(args, report: ReportViolation):
    if args.resource_type == "aws:s3/bucketV2:BucketV2":
        if not args.props.get("serverSideEncryptionConfiguration"):
            report("S3 buckets must define server-side encryption.")

PolicyPack(
    name="acme-baseline",
    enforcement_level=EnforcementLevel.MANDATORY,
    policies=[ResourceValidationPolicy(
        name="s3-encryption-required",
        description="All S3 buckets must be encrypted.",
        validate=s3_encrypted)])

Provider note: rules see the same property names Pulumi sends to the provider, so consult the resource schema when matching fields.

Step-by-Step: Enforcing a Baseline

Start advisory, watch what would fail, then promote stable rules to mandatory. Run the pack locally, then wire it into CI so no stack merges without passing.

Rollout path Rollout path: write rule then advisory then observe then mandatory then CI gate write rule advisory observe mandatory CI gate
Promote each rule from advisory to mandatory once it is stable, then enforce it in CI.
# CLI: evaluate a stack against the pack, failing on mandatory violations
pulumi preview --policy-pack ./policy --stack prod

Once green locally, register the pack in your pipeline so every preview enforces it. Group related rules (encryption, tagging, public-access) into one pack per baseline so consumers adopt them together.

Verification

Prove the pack both blocks bad resources and passes good ones — a rule that never fails is worthless, and one that always fails is unusable.

Verification Verification: Test → Program → Mock/Cloud. Test Program Mock/Cloud invoke declare resolve assert
Verification: the test drives the program and asserts on resolved values.
# CLI: a compliant stack passes; a deliberately bad one fails
pulumi preview --policy-pack ./policy   # expect: no violations

Add unit tests that call each validate function with crafted args and assert the report, exactly as you would unit test a Pulumi program.

Troubleshooting

Troubleshooting Troubleshooting: Where it breaks with 4 facets. Where it breaks resource_type watch this boundary args.resource_ watch this boundary HCL watch this boundary Pulumi watch this boundary
Troubleshooting: the boundaries where things break and what to check.

Rule never triggers — the resource_type string is wrong; print args.resource_type during a preview to get the exact token.

Property is always missing — you are matching the HCL/console name, not Pulumi's camelCase property; check the resource's input schema.

Pack blocks everything — a mandatory rule is too strict or has a bug; drop it to advisory, fix, and re-promote.

FAQ

Is CrossGuard AWS-only?

No. Policies match on resource type, so you can write rules for any Pulumi provider — AWS, GCP, Kubernetes, or a custom dynamic provider.

Can policies auto-remediate?

Resource validation policies report violations; they do not mutate resources. Remediation belongs in the program itself or a separate reconciliation job.

How does this relate to Checkov?

Checkov scans static configuration; CrossGuard evaluates Pulumi's actual planned graph at preview time, so it sees computed values Checkov cannot.