Skip to main content
Technology & EngineeringDevsecops Pipeline143 lines

Security as Code

Encode security policy as version-controlled, testable artifacts that

Quick Summary18 lines
Security policy that lives in a wiki gets ignored. Security policy that lives as code in the build pipeline gets enforced. The shift from policy-as-document to policy-as-code is the central move in modern DevSecOps.

## Key Points

- **Open Policy Agent (OPA)**. General-purpose policy engine using the Rego language. Integrates with Kubernetes (Gatekeeper, Kyverno), Terraform, CI pipelines, and many SaaS products via webhook.
- **Cedar**. Authorization-focused, simpler grammar than Rego, designed by AWS. Native to AWS Verified Permissions but usable elsewhere.
- **In CI** — pre-merge: SAST policies, IaC policies, secrets policies, dependency policies. The PR check fails if a policy is violated.
- **At deploy time** — in your CI/CD: artifact-signing policy, image-scanning policy, deployment policy. Deploy is rejected if violated.
- **At runtime** — in Kubernetes (admission controller), in service mesh (Envoy filter), in API gateway (request validation). The request is denied at the moment of execution.
- **In audit** — read-only checks against running infrastructure. They don't block; they alert when a violation is discovered post-fact.
- **Documented in the policy or in a metadata system.** The exception is data, not code.
- **Time-bounded.** Exceptions expire. The team renews them or fixes the underlying issue.
- **Reviewed.** Exceptions are reviewed by the security team. A pile of standing exceptions is a signal the policy might be wrong.
- **Logged.** When the exception is invoked, the system logs it. The audit trail is preserved.
- Number of policy violations caught per week.
- Mean time from violation introduction to detection.
skilldb get devsecops-pipeline-skills/Security as CodeFull skill: 143 lines
Paste into your CLAUDE.md or agent config

Security policy that lives in a wiki gets ignored. Security policy that lives as code in the build pipeline gets enforced. The shift from policy-as-document to policy-as-code is the central move in modern DevSecOps.

The principle is the same as infrastructure-as-code: make the rules executable. Pull request introduces an over-permissive IAM role? The IaC scanner blocks it. Engineer tries to deploy a container running as root? The admission controller rejects it. New service requests an unencrypted connection? The mesh proxy refuses.

Policy Engines

Pick a policy engine. Two that are battle-tested:

  • Open Policy Agent (OPA). General-purpose policy engine using the Rego language. Integrates with Kubernetes (Gatekeeper, Kyverno), Terraform, CI pipelines, and many SaaS products via webhook.
  • Cedar. Authorization-focused, simpler grammar than Rego, designed by AWS. Native to AWS Verified Permissions but usable elsewhere.

Both express policies as small declarative documents that take an input (a request, a resource, a config) and return an allow/deny decision plus a reason. Both can run inline in the request path or in a CI gate.

For most organizations, OPA is the default. Larger ecosystem, more mature tooling, more existing policies to draw from.

Where Policies Live

The policy repo is its own thing. Versioned, reviewed, tested. Engineers don't modify policies casually; they treat them like production code.

Structure:

policies/
  kubernetes/
    no-privileged-containers.rego
    resource-limits-required.rego
  terraform/
    no-public-s3-buckets.rego
    encryption-required.rego
  ci/
    no-secrets-in-code.rego
    sast-must-pass.rego
  api/
    auth-required.rego
    rate-limit-required.rego
tests/
  ...mirror structure with test fixtures

Each policy is a small file with a single rule. Each policy has tests — input fixtures that should be allowed, input fixtures that should be denied. The test suite runs on every change to the policy repo.

This structure allows the security team to evolve policies confidently. Tests catch regressions; reviewed PRs document the rationale; the policies themselves become the spec for "what is allowed."

Enforcement Points

Policy gets enforced wherever the relevant decision is made:

  • In CI — pre-merge: SAST policies, IaC policies, secrets policies, dependency policies. The PR check fails if a policy is violated.
  • At deploy time — in your CI/CD: artifact-signing policy, image-scanning policy, deployment policy. Deploy is rejected if violated.
  • At runtime — in Kubernetes (admission controller), in service mesh (Envoy filter), in API gateway (request validation). The request is denied at the moment of execution.
  • In audit — read-only checks against running infrastructure. They don't block; they alert when a violation is discovered post-fact.

Each enforcement point has a different cost. CI is cheap (block before merge); admission controller is moderate (block before deploy); runtime is most expensive (the failure mode is a denied production request).

Choose enforcement points based on the policy's criticality. "Containers must not run as root" can be runtime-enforced; the cost of blocking a deploy is acceptable. "All resources must have a cost-center tag" can be audit-enforced; periodic noncompliance reports are sufficient.

Policy Authoring

Write policies that are precise, testable, and explainable.

Precise: the policy should match exactly what's prohibited, not a heuristic.

# good
deny[msg] {
  input.kind == "Pod"
  some i
  input.spec.containers[i].securityContext.runAsUser == 0
  msg := sprintf("Container %q runs as UID 0", [input.spec.containers[i].name])
}

# bad — overly broad
deny[msg] {
  input.kind == "Pod"
  not input.metadata.labels.approved
  msg := "Pod not approved"
}

Testable: every policy comes with allow/deny test cases. The tests live with the policy.

Explainable: the deny message includes what was denied and why, in language an engineer can act on. "Container X runs as UID 0" is actionable; "Policy violation" is not.

Exception Handling

Every policy needs an exception process. Some legitimate workloads will violate a policy; the team needs a way to mark them acknowledged and proceed.

Exception design:

  • Documented in the policy or in a metadata system. The exception is data, not code.
  • Time-bounded. Exceptions expire. The team renews them or fixes the underlying issue.
  • Reviewed. Exceptions are reviewed by the security team. A pile of standing exceptions is a signal the policy might be wrong.
  • Logged. When the exception is invoked, the system logs it. The audit trail is preserved.

Common pattern: a policy admits an exempt annotation referencing a ticket. The annotation must be present, the ticket must be open, and the ticket must have approval. The annotation expires after a configured window.

Policy Telemetry

Track:

  • Number of policy violations caught per week.
  • Mean time from violation introduction to detection.
  • Number of standing exceptions.
  • Number of policies enforced.
  • Coverage: what percentage of services have policies that gate them.

Trends matter more than levels. Increasing violations might mean engineers are pushing limits, or it might mean the policy set has grown. Increasing exceptions might mean the policies have gotten too strict.

Cultural Shift

Policy-as-code is partly a cultural shift. The team's relationship to security changes. Security is no longer an end-of-cycle review; it's a set of guardrails that engineers operate within. The security team's role shifts from gatekeeper to author of the guardrails.

This shift is sometimes resisted. Engineers used to working around security may find guardrails frustrating. Security teams used to reviewing every change may feel their role is diminished. Both reactions are normal; both are addressed by being explicit about the new model.

The new model:

  • Engineers are responsible for satisfying the policies, not for asking permission.
  • Security is responsible for the policies — they own the spec; they own the false-positive rate; they own the exception process.
  • When a policy is wrong (too strict, too lenient, too noisy), the response is to fix the policy, not to disable it.

Anti-Patterns

Policy in a PDF. No automated enforcement. Engineers don't read it; auditors find out at audit time. Convert to code.

Single giant policy file. All rules in one document. Hard to test, hard to evolve. Decompose into per-rule files.

Policy without exceptions. Engineers can't ship around legitimate edge cases. They disable the engine or work around it. Build an exception process.

Untested policies. A policy update breaks unrelated workloads; nobody noticed. Tests prevent this.

Runtime enforcement only. Policies enforced only at runtime block deploys at the worst time — after the engineer has shipped. Catch them earlier in the pipeline.

Audit-only. Policies only check, never block. Engineers ignore the alerts. For critical policies, enforce; for advisory ones, audit; mix carefully.

Install this skill directly: skilldb add devsecops-pipeline-skills

Get CLI access →