Skip to main content

08 - Policy as Code - Enforcement of Security Policies in Deployment

OPA/Rego, Kyverno (Kubernetes), Sentinel (Terraform), policy examples (image registries, resource limits), testing policies, organizational rollout.

What do you think about this article?

At a Glance

OPA/Rego, Kyverno (Kubernetes), Sentinel (Terraform), policy examples (image registries, resource limits), testing policies, organizational rollout.

Policy as Code: Automating Security Rules

Policy as Code (PaC) is the practice of defining security and compliance rules as versionable, testable, and automatically enforceable code. Instead of documenting policies in PDFs that nobody reads, PaC transforms them into executable code that is automatically applied in pipelines and infrastructure.

With PaC, rules like "every container must run as non-root" or "S3 buckets must not be public" become automated checks that block non-compliant deploys. This eliminates human error and ensures continuous compliance.

What You'll Learn

  • Policy as Code principles and advantages over traditional policies
  • OPA (Open Policy Agent) and the Rego language
  • Kyverno for Kubernetes policy enforcement
  • HashiCorp's Sentinel for Terraform
  • Conftest for configuration validation
  • Testing and deploying policies

OPA: Open Policy Agent

OPA (Open Policy Agent) is the most widely used policy engine in the cloud-native ecosystem. Developed by the CNCF, OPA uses the declarative language Rego to define policies that can be applied to any type of structured data: API requests, Kubernetes configurations, Terraform configurations, CI/CD pipelines.

Writing Policies in Rego


# policy/kubernetes/pod-security.rego
package kubernetes.admission

# Deny containers running as root
deny[msg] {
  input.request.kind.kind == "Pod"
  container := input.request.object.spec.containers[_]
  not container.securityContext.runAsNonRoot
  msg := sprintf(
    "Container '%s' must have runAsNonRoot: true",
    [container.name]
  )
}

# Deny images with :latest tag
deny[msg] {
  input.request.kind.kind == "Pod"
  container := input.request.object.spec.containers[_]
  endswith(container.image, ":latest")
  msg := sprintf(
    "Container '%s' uses :latest. Specify an explicit tag.",
    [container.name]
  )
}

# Require resource limits
deny[msg] {
  input.request.kind.kind == "Pod"
  container := input.request.object.spec.containers[_]
  not container.resources.limits
  msg := sprintf(
    "Container '%s' must define resource limits (CPU and memory)",
    [container.name]
  )
}
      

Testing OPA Policies


# policy/kubernetes/pod-security_test.rego
package kubernetes.admission

test_deny_root_container {
  count(deny) > 0 with input as {
    "request": {
      "kind": {"kind": "Pod"},
      "object": {
        "spec": {
          "containers": [{
            "name": "app",
            "image": "myapp:v1",
            "securityContext": {}
          }]
        }
      }
    }
  }
}

test_allow_nonroot_container {
  count(deny) == 0 with input as {
    "request": {
      "kind": {"kind": "Pod"},
      "object": {
        "spec": {
          "containers": [{
            "name": "app",
            "image": "myapp:v1",
            "securityContext": {"runAsNonRoot": true},
            "resources": {"limits": {"cpu": "500m", "memory": "256Mi"}}
          }]
        }
      }
    }
  }
}
      

Kyverno: Kubernetes-Native Policy

Kyverno is a policy engine designed specifically for Kubernetes. Unlike OPA which uses Rego, Kyverno defines policies as native Kubernetes resources in YAML, making adoption simpler for teams already familiar with Kubernetes.


# kyverno-policies/require-nonroot.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-run-as-nonroot
  annotations:
    policies.kyverno.io/title: Require runAsNonRoot
    policies.kyverno.io/category: Pod Security
    policies.kyverno.io/severity: high
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: check-containers
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: >-
          Container must run as non-root.
          Set securityContext.runAsNonRoot to true.
        pattern:
          spec:
            containers:
              - securityContext:
                  runAsNonRoot: true

---
# Require approved registries
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-image-registries
spec:
  validationFailureAction: Enforce
  rules:
    - name: validate-registries
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Images must come from approved registries."
        pattern:
          spec:
            containers:
              - image: "ghcr.io/myorg/* | registry.internal.company.com/*"
      

Conftest: Validating Configurations

Conftest uses OPA/Rego to validate any type of configuration file: Dockerfile, Kubernetes manifests, Terraform files, CI/CD pipelines. It's particularly useful as a pipeline step to validate configurations before deployment.


# Install Conftest
brew install conftest

# Validate a Dockerfile
conftest test Dockerfile --policy policy/docker/

# Validate Kubernetes manifests
conftest test k8s/ --policy policy/kubernetes/

# Validate with structured output
conftest test --output json terraform/ --policy policy/terraform/
      

# policy/docker/dockerfile.rego
package docker

# Deny Dockerfile without USER
deny[msg] {
  input[i].Cmd == "from"
  not any_user_after(i)
  msg := "Dockerfile must include the USER instruction to avoid running as root"
}

any_user_after(from_idx) {
  input[j].Cmd == "user"
  j > from_idx
}

# Deny base images with :latest
deny[msg] {
  input[i].Cmd == "from"
  val := input[i].Value[0]
  endswith(val, ":latest")
  msg := sprintf("Avoid :latest in FROM instruction. Use a specific tag: %s", [val])
}
      

Sentinel: Terraform Policy

Sentinel is HashiCorp's policy framework, natively integrated into Terraform Cloud and Enterprise. It allows defining policies that validate Terraform plans before application, preventing the creation of non-compliant resources.

Policy Engines Comparison

Feature OPA/Rego Kyverno Sentinel
Scope Universal Kubernetes HashiCorp ecosystem
Language Rego (declarative) Native K8s YAML Sentinel (imperative)
Learning curve Medium-high Low Medium
License Open source (Apache 2.0) Open source (Apache 2.0) Commercial (Terraform Cloud)

Policy in the CI/CD Pipeline


# .github/workflows/policy-check.yml
name: Policy Validation
on:
  pull_request:

jobs:
  validate-policies:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Conftest
        run: |
          wget -q https://github.com/open-policy-agent/conftest/releases/download/v0.46.0/conftest_0.46.0_Linux_x86_64.tar.gz
          tar xzf conftest_0.46.0_Linux_x86_64.tar.gz
          sudo mv conftest /usr/local/bin/

      - name: Test OPA policies
        run: opa test policy/ -v

      - name: Validate Dockerfiles
        run: conftest test Dockerfile --policy policy/docker/

      - name: Validate K8s manifests
        run: conftest test k8s/ --policy policy/kubernetes/

      - name: Validate Terraform
        run: conftest test terraform/ --policy policy/terraform/
      

Policy as Code Best Practices

  1. Version policies: treat policies as code, with reviews, tests, and releases
  2. Test policies: write unit tests for every policy to avoid regressions
  3. Gradual rollout: start in audit mode (warning) before switching to enforce (blocking)
  4. Document: every policy must have a clear description of why it exists
  5. Quick feedback: integrate validation in PRs for immediate feedback
  6. Managed exceptions: provide a process for temporary exceptions with expiration

Conclusions

Policy as Code transforms security rules from static documents into executable, automated code. With OPA, Kyverno, and Conftest, organizations can ensure continuous compliance without slowing down the development process.

In the next article, we'll explore CI/CD Security Pipeline, analyzing how to protect the build and deploy process itself: pipeline hardening, OIDC, least privilege, and signed commits.

Frequently asked questions

What is Policy as Code (PaC)?

PaC is the practice of defining security and compliance rules as versionable, testable, and automatically enforceable code.

How does Policy as Code eliminate human error?

It eliminates human error by transforming rules into automated checks that block non-compliant deploys.

What is Rego, and how is it used in OPA?

Rego is a declarative language used to define policies in OPA, which can be applied to any type of structured data.

How does Conftest use OPA/Rego to validate configurations?

Conftest uses OPA/Rego to validate any type of configuration file.

Sources and further reading

Try These Tools

Free developer tools related to this topic.

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects