WAF++ WAF++
Back to WAF++ Homepage

Best Practice: Policy-as-Code

Context

Security policies as PDF documents in SharePoint are declarative, not enforceable. They describe the target state but never verify the actual state. Policy-as-Code solves this problem: policies are formulated as versioned, executable code artifacts – and integrated into CI/CD pipelines, deployment gates, and continuous monitoring systems.

The result: compliance is not claimed but proven.

Typical problems without Policy-as-Code:

  • Security reviews take place in manual review rounds – does not scale

  • Configuration drift between compliance audit and the next change

  • Every developer interprets security policies differently

  • Audit preparation means weeks of manual work

  • New resources are not automatically subject to the same security standard

  • WAF-SEC-090 – Policy-as-Code & Compliance Automation

  • WAF-SEC-080 – Security Monitoring & Threat Detection

What Policy-as-Code Means

Policy-as-Code (PaC) is the principle of formulating security and compliance requirements in machine-executable form. The core characteristics:

  • Versioned: Policies are managed in Git – changes are traceable and peer-reviewed like any other code

  • Executable: Policies can be automatically executed against infrastructure, Kubernetes manifests, or Terraform plans

  • Idempotent: The same policy always produces the same result on the same inputs

  • Documented through execution: The policy test is simultaneously the documentation of the requirement

Tooling Comparison

Tool Strengths Weaknesses Recommended Use

wafpass (WAF++)

Native for WAF++ controls; pillar-based filter; direct YAML control integration; check Terraform plan + live state

WAF++-specific; no Kubernetes manifests

Primary tool for all WAF-SEC controls

OPA / Conftest

Highly flexible; Rego language for complex rules; Kubernetes + Terraform + Docker; large community; Gatekeeper for K8s admission control

Rego learning curve; no native WAF++ reference

Kubernetes admission control; custom business rules

HashiCorp Sentinel

Deeply integrated in Terraform Enterprise; policy sets across multiple workspaces; enforcement levels (advisory, soft-mandatory, hard-mandatory)

Only Terraform Enterprise / HCP Terraform; proprietary

Terraform Enterprise environments

AWS Config Rules

Native AWS integration; managed rules without code; continuous monitoring; Config Aggregator for multi-account

AWS-only; Lambda overhead for custom rules; no Terraform plan integration

Continuous live state monitoring; AWS-specific compliance

Checkov

Open source; broad Terraform coverage; CI integration; SARIF output

No WAF++ reference; many false positives; less flexible than OPA

CI pipeline as supplement to wafpass

WAF++ wafpass in CI/CD

wafpass is the native policy-as-code tool of the WAF++ framework. It evaluates Terraform plans and state against WAF-SEC (and other pillar) controls.

Basic Usage

# Check all security controls
wafpass check --pillar security

# Check only critical controls (CI gate: no deployment on failure)
wafpass check --pillar security --severity critical --fail-on critical

# Check specific controls
wafpass check --controls WAF-SEC-010,WAF-SEC-020,WAF-SEC-030

# Check Terraform plan (pre-deployment)
terraform plan -out=plan.tfplan
wafpass check --plan plan.tfplan --pillar security

# Output report as JSON
wafpass check --pillar security --output json > security-report.json

# Output report as HTML for management
wafpass check --pillar security --output html > security-report.html

wafpass in GitHub Actions

name: Security Policy Check

on:
  pull_request:
    paths: ['terraform/**', '*.tf']
  push:
    branches: [main]

jobs:
  wafpass-security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Terraform Init
        run: terraform init
        working-directory: terraform/

      - name: Terraform Plan
        run: terraform plan -out=plan.tfplan
        working-directory: terraform/
        env:
          AWS_ROLE_ARN: ${{ secrets.AWS_OIDC_ROLE }}

      - name: wafpass – Security Pillar Check
        run: |
          wafpass check \
            --plan terraform/plan.tfplan \
            --pillar security \
            --fail-on critical \
            --output sarif \
            > wafpass-results.sarif

      - name: Upload wafpass Results
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: wafpass-results.sarif
          category: wafpass-security

The --fail-on critical flag ensures that critical control violations block the deployment process, while High/Medium/Low findings are visible as warnings in the SARIF report.

OPA for Kubernetes: Gatekeeper

Open Policy Agent (OPA) with Gatekeeper implements a Kubernetes admission controller – every resource is checked against policies before being created:

Installing Gatekeeper

helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm install gatekeeper/gatekeeper \
  --name-template=gatekeeper \
  --namespace gatekeeper-system \
  --create-namespace \
  --set replicas=3 \
  --set auditInterval=30

ConstraintTemplate: No Privileged Containers

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8snoprivilegedcontainer
spec:
  crd:
    spec:
      names:
        kind: K8sNoPrivilegedContainer
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8snoprivilegedcontainer

        violation[{"msg": msg}] {
          c := input.review.object.spec.containers[_]
          c.securityContext.privileged == true
          msg := sprintf(
            "Container '%v' must run as non-privileged (WAF-SEC-120)",
            [c.name]
          )
        }

        violation[{"msg": msg}] {
          c := input.review.object.spec.initContainers[_]
          c.securityContext.privileged == true
          msg := sprintf(
            "Init container '%v' must run as non-privileged (WAF-SEC-120)",
            [c.name]
          )
        }
# Constraint: activating the template rule for the production namespace
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoPrivilegedContainer
metadata:
  name: no-privileged-containers
spec:
  enforcementAction: deny  # deny blocks; warn only warns
  match:
    namespaces: ["production", "staging"]

Conftest for Terraform Plans

Conftest uses OPA/Rego policies for Terraform plans:

# policies/security/no_public_s3.rego
package terraform.security

import rego.v1

deny contains msg if {
  resource := input.resource_changes[_]
  resource.type == "aws_s3_bucket"
  resource.change.after.acl == "public-read"
  msg := sprintf(
    "S3 bucket '%v' must not be set to public-read (WAF-SEC-130)",
    [resource.address]
  )
}

deny contains msg if {
  resource := input.resource_changes[_]
  resource.type == "aws_s3_bucket_public_access_block"
  resource.change.after.block_public_acls == false
  msg := sprintf(
    "S3 bucket '%v' must have Block Public ACLs enabled (WAF-SEC-130)",
    [resource.address]
  )
}
terraform show -json plan.tfplan | conftest test - --policy policies/security/

AWS Config: Continuous Live Monitoring

AWS Config monitors the current state of the infrastructure and reports deviations:

resource "aws_config_configuration_recorder" "main" {
  name     = "main-recorder"
  role_arn = aws_iam_role.config.arn

  recording_group {
    all_supported                 = true
    include_global_resource_types = true  # IAM, CloudFront, etc.
  }
}

resource "aws_config_delivery_channel" "main" {
  name           = "main-channel"
  s3_bucket_name = aws_s3_bucket.config_logs.bucket

  snapshot_delivery_properties {
    delivery_frequency = "TwentyFour_Hours"
  }

  depends_on = [aws_config_configuration_recorder.main]
}

resource "aws_config_configuration_recorder_status" "main" {
  name       = aws_config_configuration_recorder.main.name
  is_enabled = true

  depends_on = [aws_config_delivery_channel.main]
}

# Managed rule: MFA for root account
resource "aws_config_rule" "root_mfa" {
  name = "root-account-mfa-enabled"

  source {
    owner             = "AWS"
    source_identifier = "ROOT_ACCOUNT_MFA_ENABLED"
  }

  depends_on = [aws_config_configuration_recorder_status.main]
}

# Managed rule: VPC flow logs enabled
resource "aws_config_rule" "vpc_flow_logs" {
  name = "vpc-flow-logs-enabled"

  source {
    owner             = "AWS"
    source_identifier = "VPC_FLOW_LOGS_ENABLED"
  }
}

# Managed rule: CloudTrail enabled
resource "aws_config_rule" "cloudtrail_enabled" {
  name = "cloud-trail-enabled"

  source {
    owner             = "AWS"
    source_identifier = "CLOUD_TRAIL_ENABLED"
  }
}

# Custom rule via Lambda: WAF-SEC-specific check
resource "aws_config_rule" "custom_encryption_check" {
  name = "custom-waf-sec-030-encryption-check"

  source {
    owner = "CUSTOM_LAMBDA"
    source_identifier = aws_lambda_function.config_encryption_check.arn

    source_detail {
      message_type = "ConfigurationItemChangeNotification"
      event_source = "aws.config"
    }
  }

  scope {
    compliance_resource_types = ["AWS::RDS::DBInstance", "AWS::S3::Bucket"]
  }
}

Policy Versioning

Policies evolve – new requirements emerge, old ones become stricter. Without a versioning strategy, this leads to breaking changes:

Semantic Versioning for Policies

# wafpass-policies/security/WAF-SEC-010.policy.yml
version: "2.1.0"
# 2.x.x = Breaking change (new mandatory assertion)
# x.2.x = Non-breaking enhancement (new optional check)
# x.x.1 = Bugfix (corrected assertion)

effective_date: "2025-01-01"
deprecation_warning: null
breaking_changes_from_previous:
  - "Minimum password length increased from 12 to 14 characters (BSI C5:2020 IAM-01 update)"

Graceful Migration: Warn Mode Before Enforce Mode

New policies should initially run in warn mode:

# Phase 1 (month 1): warn only
enforcement_level: warn

# Phase 2 (month 2): non-compliance in reports
enforcement_level: report

# Phase 3 (month 3): CI gate blocks on new deployments
enforcement_level: fail-on-new

# Phase 4 (month 4): CI gate blocks all deployments
enforcement_level: fail

Monitoring and Measurement

  • Config Compliance Dashboard: Percentage of compliant resources per rule

  • Config Aggregator: Multi-account compliance overview for the entire organization

  • Security Hub Findings: Config findings aggregated with other security sources

  • Policy Coverage Metric: Share of resources covered by at least one policy

  • Mean Time to Remediation (MTTR): How long does it take to fix a Config finding?

  • wafpass check --pillar security --controls WAF-SEC-090 --output json | jq '.summary'