Implementing Compliance as Code#

Compliance as code encodes compliance requirements as machine-readable policies evaluated automatically, continuously, and with every change. Instead of quarterly spreadsheet audits, a policy like “all S3 buckets must have encryption enabled” becomes a check that runs in CI, blocks non-compliant Terraform plans, and scans running infrastructure hourly. Evidence generation is automatic. Drift is detected immediately.

Step 1: Map Compliance Controls to Technical Policies#

Translate your compliance framework’s controls into specific, testable technical requirements. This mapping bridges auditor language and infrastructure code.

SOC2 Trust Service Criteria#

SOC2 Control Technical Requirement Enforcement Point
CC6.1 Logical Access RBAC with least privilege, no cluster-admin for workloads Kubernetes admission, IAM policy
CC6.3 Role-Based Access Service accounts per workload, no shared credentials Kubernetes admission
CC6.6 System Boundaries Default-deny network policies in all namespaces Kubernetes admission
CC6.8 Prevent Unauthorized Software Only signed images from approved registries Kubernetes admission
CC7.1 Monitoring Audit logging enabled, logs shipped to central SIEM Infrastructure as code, runtime check
CC7.2 Anomaly Detection Alerting on privilege escalation, unusual API access Runtime monitoring
CC8.1 Change Management All changes via GitOps, no manual kubectl apply Kubernetes admission, CI gate

PCI-DSS v4.0#

PCI-DSS Requirement Technical Requirement Enforcement Point
1.2.1 Network segmentation Network policies isolating cardholder data environment Kubernetes admission
2.2.1 Secure configurations CIS benchmark pass on all nodes and containers Infrastructure scan, admission
6.3.2 Vulnerability management No critical/high CVEs in deployed images CI gate, admission
7.2.1 Access control RBAC policies, no default service account usage Kubernetes admission
8.3.1 MFA OIDC with MFA for cluster access Identity provider configuration
10.2.1 Audit trails API server audit logs with 90-day retention Infrastructure as code
11.3.1 Vulnerability scanning Weekly automated scanning of running workloads Scheduled job

HIPAA Security Rule#

HIPAA Standard Technical Requirement Enforcement Point
164.312(a)(1) Access Control RBAC, namespace isolation for PHI workloads Kubernetes admission
164.312(a)(2)(iv) Encryption Encryption at rest and in transit (mTLS) Infrastructure as code, admission
164.312(b) Audit Controls Complete audit trail of PHI access API audit logs, application logs
164.312(e)(1) Transmission Security TLS 1.2+ on all endpoints, mTLS between services Admission, network policy

Step 2: Select and Deploy a Policy Engine#

Three tools cover most use cases. They are not mutually exclusive – use Checkov in CI and Kyverno or Gatekeeper at admission.

Checkov: Static Infrastructure Scanning#

Checkov scans Terraform, Kubernetes manifests, Helm charts, and Dockerfiles before they are applied:

# Scan Terraform files
checkov -d ./terraform/ --framework terraform

# Scan Kubernetes manifests
checkov -d ./k8s-manifests/ --framework kubernetes

# Scan Helm charts (renders templates first)
checkov -d ./helm-chart/ --framework helm

# Output in JUnit format for CI integration
checkov -d ./terraform/ -o junitxml > checkov-results.xml

Checkov includes over 2,500 built-in policies mapped to CIS benchmarks, SOC2, PCI-DSS, and HIPAA. Run only the policies relevant to your compliance scope:

# Run by compliance framework
checkov -d ./terraform/ --compliance-framework soc2

# Run specific checks
checkov -d ./terraform/ --check CKV_AWS_18,CKV_AWS_19,CKV_AWS_21

OPA Gatekeeper: Kubernetes Admission Control#

Gatekeeper evaluates policies at the Kubernetes API admission point. Non-compliant resources are rejected before they are created.

Install with Helm (helm install gatekeeper gatekeeper/gatekeeper -n gatekeeper-system --create-namespace). Define constraint templates (policy logic in Rego) and apply constraints using them:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: require-compliance-labels
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
  parameters:
    labels:
      - "app.kubernetes.io/name"
      - "compliance/data-classification"
      - "compliance/owner"

Kyverno: Kubernetes-Native Policies#

Kyverno uses Kubernetes YAML natively – no Rego required. For teams that already manage Kubernetes resources in YAML, the learning curve is lower.

Install with Helm (helm install kyverno kyverno/kyverno -n kyverno --create-namespace). Enforce that containers must not run as root:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-non-root
  annotations:
    policies.kyverno.io/category: Pod Security
    policies.kyverno.io/severity: high
    policies.kyverno.io/subject: Pod
    policies.kyverno.io/description: >-
      Containers must run as non-root. Maps to SOC2 CC6.1,
      PCI-DSS 2.2.1, HIPAA 164.312(a)(1).
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: check-containers
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Containers must not run as root (runAsNonRoot must be true)"
        pattern:
          spec:
            containers:
              - securityContext:
                  runAsNonRoot: true

Kyverno can also mutate resources (inject labels, set defaults) and generate resources (create network policies when a namespace is created).

Conftest: Pre-Deployment Policy Testing#

Conftest uses OPA/Rego to test structured data files (YAML, JSON, HCL, Dockerfile) in CI before admission. Useful when you want OPA’s policy language for pre-deployment checks:

conftest test deployment.yaml -p ./policies/
terraform show -json plan.tfplan | conftest test - -p ./policies/terraform/

Step 3: Integrate Policies into CI/CD#

Policies must run automatically. Manual compliance checks are compliance theater.

# GitHub Actions: compliance gates
name: Compliance Checks
on:
  pull_request:
    branches: [main]

jobs:
  checkov-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: bridgecrewio/checkov-action@v12
        with:
          directory: ./terraform
          framework: terraform
          soft_fail: false

  kyverno-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Test policies against manifests
        run: kyverno apply ./policies/ --resource ./k8s-manifests/

  conftest-terraform:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: |
          terraform -chdir=terraform init -backend=false
          terraform -chdir=terraform plan -out=plan.tfplan
          terraform -chdir=terraform show -json plan.tfplan > plan.json
          conftest test plan.json -p ./policies/terraform/

Block merges if any compliance check fails.

Step 4: Generate Audit Trails#

Auditors need evidence. Compliance as code generates evidence automatically, but you must collect and retain it.

Policy Decision Logs#

Gatekeeper logs every admission decision (allow/deny) including the resource, the policy evaluated, and the result. Configure audit logging:

# Gatekeeper audit configuration
helm upgrade gatekeeper gatekeeper/gatekeeper \
  --namespace gatekeeper-system \
  --set audit.logLevel=INFO \
  --set audit.constraintViolationsLimit=100

Kyverno generates Policy Reports as Kubernetes resources (kubectl get policyreport -n production). These list every resource evaluated, pass/fail status, and which policy was applied. Export them to your log aggregation system for long-term retention.

CI Pipeline Records and State Snapshots#

Every CI run that executes compliance checks is an audit artifact. Retain CI logs, test results (JUnit XML), and the specific policy versions evaluated. Store in tamper-evident storage (S3 with object lock or write-once retention policies).

Periodically capture the compliance state of running infrastructure:

#!/bin/bash
DATE=$(date +%Y-%m-%d)
kubescape scan framework cis-v1.23-t1.0.1 -f json -o "audit/k8s-cis-$DATE.json"
kube-bench run --json > "audit/kube-bench-$DATE.json"
kubectl get clusterpolicyreport -o json > "audit/policy-report-$DATE.json"
checkov -d ./terraform/ --framework terraform -o json > "audit/checkov-$DATE.json"
aws s3 cp audit/ s3://compliance-audit-bucket/$DATE/ --recursive

Step 5: Establish Continuous Compliance Monitoring#

Compliance at deployment is necessary but not sufficient. Configuration drifts, new CVEs emerge, and certificates expire. Continuous monitoring catches what CI cannot.

Scheduled Compliance Scans#

Run full compliance scans on a schedule against live infrastructure using Kubernetes CronJobs. Schedule kube-bench, kubescape, and Checkov scans daily and store results as audit evidence.

Gatekeeper Audit and Drift Detection#

Gatekeeper’s audit controller periodically evaluates all existing resources against constraints, catching resources created before a policy existed:

kubectl get constraints -o json | jq '.items[].status.totalViolations'

For infrastructure drift, run terraform plan -detailed-exitcode on a schedule (exit code 2 means drift). Alert when actual state diverges from declared state.

Alerting on Compliance Violations#

Route violations to your alerting system with severity-based response: Critical (privilege escalation, encryption disabled) – page on-call immediately. High (missing network policies, RBAC drift) – alert within 1 hour, create incident ticket. Medium (missing labels, benchmark failures) – daily alert, backlog ticket with SLA.

Organize policies by framework (policies/soc2/, policies/pci-dss/, policies/hipaa/, policies/cis/) with annotations mapping each to its compliance control.

Common Mistakes#

  1. Writing policies without mapping to controls first. You end up with gaps where requirements have no policy.
  2. Enforcing everything immediately. Start in audit/warn mode. Switch to enforce after confirming no false positives.
  3. Not retaining audit evidence. Compliance checks whose results are not stored provide no audit value.
  4. Relying solely on admission control. Admission only applies to new resources. Pre-existing non-compliant resources require audit-mode scanning.
  5. Treating compliance as code as one-time setup. Review and update policies quarterly, aligned with your audit cycle.