luvv to helpDiscover the Best Free Online Tools
Topic 7 of 8

Policy As Code Basics

Learn Policy As Code Basics for free with explanations, exercises, and a quick test (for Data Platform Engineer).

Published: January 11, 2026 | Updated: January 11, 2026

Why this matters

As a Data Platform Engineer, you provision and operate cloud resources that store sensitive data and run critical pipelines. Policy as Code (PaC) turns your security, compliance, and cost rules into automated checks that run on every change. This prevents risky configurations (like public buckets, weak IAM policies, or missing encryption) before they reach production.

  • Real tasks you will do: enforce encryption on data storage, block public exposure, require tagging for cost allocation, restrict IAM wildcards, and validate network egress rules.
  • Outcomes: fewer incidents, faster reviews, consistent guardrails across teams, and auditable compliance.

Concept explained simply

Policy as Code is just rules written in a programming language, evaluated against your infrastructure definitions (e.g., Terraform plan) or runtime resources (e.g., Kubernetes). If a rule is violated, the change is blocked or flagged.

  • Inputs: the thing to check (Terraform plan JSON, cloud resource JSON, Kubernetes admission request).
  • Policy: rules that must be true (e.g., "all buckets must be encrypted"), typically in languages like Rego (OPA) or Sentinel.
  • Decision: allow/deny or a list of violations.
  • Enforcement point: pre-commit, CI, pull request, or runtime admission controller.

Mental model

Think of PaC as unit tests for your infrastructure. The policy is the test file; the plan or resource is the subject under test. CI runs the tests on every change and fails fast when a rule breaks.

Core building blocks

  • Policy language: Rego (Open Policy Agent) is a common, vendor-neutral choice.
  • Decision API: evaluates input JSON against policies and returns allow/deny + reasons.
  • Policy bundles: collections of rules versioned with Git.
  • Enforcement modes: soft-fail (warn) for onboarding; hard-fail (block) for strict environments.

Worked examples

Example 1: S3 bucket must be private and encrypted

Show Rego policy
package infra.guardrails

deny[msg] {
  input.resource.type == "aws_s3_bucket"
  not input.resource.encryption.enabled
  msg := sprintf("Bucket %s must enable default encryption", [input.resource.name])
}

deny[msg] {
  input.resource.type == "aws_s3_bucket"
  input.resource.public_access == true
  msg := sprintf("Bucket %s must block public access", [input.resource.name])
}
Sample input
{
  "resource": {
    "type": "aws_s3_bucket",
    "name": "raw-data",
    "public_access": true,
    "encryption": {"enabled": false}
  }
}
Expected decision
{
  "deny": [
    "Bucket raw-data must enable default encryption",
    "Bucket raw-data must block public access"
  ]
}

Example 2: Require tags for cost and ownership

Show Rego policy
package infra.guardrails

required_tags := {"env", "owner"}

deny[msg] {
  r := input.resource
  t := required_tags[_]
  not r.tags[t]
  msg := sprintf("Resource %s missing tag: %s", [r.id, t])
}
Sample input
{
  "resource": {
    "id": "vpc-1234",
    "type": "aws_vpc",
    "tags": {"env": "prod"}
  }
}
Expected decision
{
  "deny": [
    "Resource vpc-1234 missing tag: owner"
  ]
}

Example 3: Forbid wildcard IAM policies

Show Rego policy
package infra.guardrails

deny[msg] {
  input.resource.type == "aws_iam_policy"
  stmt := input.resource.document.Statement[_]
  stmt.Effect == "Allow"
  stmt.Action[_] == "*"
  msg := sprintf("IAM policy %s allows wildcard Action", [input.resource.name])
}

deny[msg] {
  input.resource.type == "aws_iam_policy"
  stmt := input.resource.document.Statement[_]
  stmt.Effect == "Allow"
  (stmt.Resource == "*") or (is_array(stmt.Resource) and stmt.Resource[_] == "*")
  msg := sprintf("IAM policy %s allows wildcard Resource", [input.resource.name])
}
Sample input
{
  "resource": {
    "type": "aws_iam_policy",
    "name": "data-admin",
    "document": {
      "Statement": [
        {"Effect": "Allow", "Action": ["*"], "Resource": "*"}
      ]
    }
  }
}
Expected decision
{
  "deny": [
    "IAM policy data-admin allows wildcard Action",
    "IAM policy data-admin allows wildcard Resource"
  ]
}

How to wire this into your workflow

  1. Local: run policy checks on Terraform plan before pushing.
  2. CI: on pull request, generate plan JSON and evaluate policies; fail if any deny.
  3. Environments: start with warn-only in dev; enforce hard fail in prod.
  4. Runtime (optional): use an admission controller pattern for Kubernetes to validate manifests before they are admitted.
Minimal CI steps (conceptual)
# 1) Generate plan JSON
terraform init
terraform plan -out=tf.plan
terraform show -json tf.plan > plan.json

# 2) Evaluate policies (example with OPA)
opa eval -i plan.json -d policies 'data.infra.guardrails.deny'

Minimal starter policy set for data platforms

  • Data storage: encryption at rest, block public access, lifecycle rules for object retention.
  • Networking: no internet-facing subnets for data plane; egress via approved endpoints.
  • Identity: forbid wildcard IAM; require least-privilege patterns.
  • Tagging: env, owner, cost-center for every resource.
  • Backups: require automated backups/snapshots on stateful services.

Exercises

Complete the exercise below. The Quick Test at the end is available to everyone; only logged-in users get saved progress.

Exercise 1 — S3 guardrails policy (mirror of the exercise below)

Write a Rego policy that denies:

  • Buckets with public_access == true
  • Buckets without encryption.enabled == true
  • Buckets without versioning.enabled == true

Use the provided input and return clear messages including the bucket name.

Input to validate
{
  "resource": {
    "type": "aws_s3_bucket",
    "name": "raw-data",
    "public_access": true,
    "encryption": {"enabled": false},
    "versioning": {"enabled": false}
  }
}
Expected output
{
  "deny": [
    "Bucket raw-data must block public access",
    "Bucket raw-data must enable default encryption",
    "Bucket raw-data must enable versioning"
  ]
}
Hints
  • Create three deny rules; each checks one condition.
  • Use sprintf to include the bucket name in messages.
  • Check boolean fields carefully; missing fields should also trigger a deny.

Self-check checklist

  • Policy returns zero denies when all conditions are satisfied.
  • Messages are human-readable and actionable.
  • Each rule is focused (one responsibility per rule).
  • Edge cases covered: missing fields, nulls, unexpected types.

Common mistakes and how to catch them

  • Only checking one resource type: ensure policies generalize or clearly scope to target resources.
  • Weak messages: write messages that tell engineers exactly what to fix.
  • All-or-nothing rollout: start in warn-only mode to build trust, then enforce.
  • No tests for policies: add simple unit tests with positive and negative cases.
  • Drift between policy and architecture: review policies whenever platform patterns change.

Mini challenge

Extend the tagging policy to also require tag values to match a regex (e.g., env in {dev,stg,prod}). Add negative and positive test inputs and verify messages.

Who this is for

  • Data Platform Engineers building Terraform modules and platform guardrails.
  • DevOps/SREs collaborating on secure-by-default infrastructure for data workloads.

Prerequisites

  • Basic Terraform knowledge (resources, plan).
  • JSON familiarity.
  • Command-line basics.

Learning path

  • Start: understand inputs and decisions (this lesson).
  • Next: write small, targeted policies and add them to CI.
  • Scale: group policies by domain (identity, network, storage) and add tests.

Practical projects

  • Project 1: Policy bundle for data storage (encryption, public access, lifecycle).
  • Project 2: Identity bundle (no wildcards, restricted assume-role, MFA conditions).
  • Project 3: CI integration that fails PRs on policy violations and posts messages.

Next steps

  • Convert your top 3 platform guardrails into policies.
  • Run them in warn-only for a week, capture feedback, refine, then enforce.
  • Document how to fix each violation directly in the message text.

Practice Exercises

1 exercises to complete

Instructions

Write a Rego policy that denies any S3 bucket that:

  • Has public_access == true
  • Does not have encryption.enabled == true
  • Does not have versioning.enabled == true

Use this input:

{
  "resource": {
    "type": "aws_s3_bucket",
    "name": "raw-data",
    "public_access": true,
    "encryption": {"enabled": false},
    "versioning": {"enabled": false}
  }
}

Return clear messages including the bucket name.

Expected Output
{ "deny": [ "Bucket raw-data must block public access", "Bucket raw-data must enable default encryption", "Bucket raw-data must enable versioning" ] }

Policy As Code Basics — Quick Test

Test your knowledge with 8 questions. Pass with 70% or higher.

8 questions70% to pass

Have questions about Policy As Code Basics?

AI Assistant

Ask questions about this tool