intermediate 20 min read cicd-integration Updated: 2024-06-24

Policy Checks in GitHub Actions

A practical guide to integrating automated policy validation for Terraform, Kubernetes, and more into your GitHub Actions workflows.

📋 Prerequisites

  • A GitHub account and a repository where you can enable Actions.
  • Basic understanding of GitHub Actions workflow syntax (YAML).
  • Familiarity with an Infrastructure as Code (IaC) tool like Terraform.
  • Basic knowledge of Policy-as-Code concepts.

🏷️ Topics Covered

github actions policy validationopa conftest github actionsterraform policy ci cd githubpolicy as code github workflowsautomated policy testing githubdevsecops github actions

Why Check Policies in CI/CD?

Integrating policy checks into your CI/CD pipeline embodies the "shift left" philosophy. Instead of discovering security misconfigurations, compliance violations, or operational risks in production, you catch them automatically during development. This approach transforms governance from a manual gate into an automated, integral part of the development lifecycle.

The key benefits include:

  • Immediate Feedback: Developers are alerted to issues within their pull requests, allowing them to fix problems while the context is still fresh.
  • Reduced Risk: Automating checks ensures that common misconfigurations, security vulnerabilities, and non-compliant resources never reach production environments.
  • Increased Velocity: By removing manual review bottlenecks, teams can deploy faster and more confidently, knowing that foundational checks are handled automatically.
  • Cost Optimization: Policies can enforce cost-saving measures, such as requiring specific instance types, setting TTL tags on resources, or preventing the deployment of overly expensive infrastructure.

Core Workflow: Triggering on Pull Requests

The most effective place to run policy checks is on pull requests. This ensures that no non-compliant code is ever merged into your main branch, protecting your production environment's integrity. A basic workflow file (e.g., .github/workflows/policy-check.yml) starts with a trigger that activates on pull requests targeting the main branch.

.github/workflows/policy-check.yml

name: Policy and Security Checks

on:
  pull_request:
    branches: [ "main" ]
    # The 'paths' filter ensures this workflow only runs
    # when relevant files are changed, saving CI minutes.
    paths:
      - '**.tf'
      - '**.k8s.yaml'
      - 'policies/**'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      # ... Policy check steps like Conftest or Checkov will go here ...

Example 1: Custom Terraform Policies with OPA Conftest

This pattern is ideal for enforcing your organization's custom governance rules that might not be covered by standard security scanners. The workflow generates a Terraform plan, converts it to JSON, and then uses OPA Conftest to validate it against your custom Rego policies.

Sample Policy: policies/tags.rego

First, define a simple Rego policy that requires all resources to have an owner tag. This policy iterates through all resource changes in the Terraform plan. If it finds a resource that does not have an owner tag defined, it generates a failure message.

package terraform

# The 'deny' rule will contain a set of failure messages.
# The rule is satisfied if the logic inside the curly braces is true.
deny[msg] {
    # Assign the list of resource changes from the input plan to a variable.
    resource := input.resource_changes[_]
    # Check if the 'owner' tag is NOT present in the resource's 'after' state.
    not resource.change.after.tags.owner
    # If the check passes, create a descriptive error message.
    msg := sprintf("Resource '%s' is missing the required 'owner' tag.", [resource.address])
}

GitHub Actions Workflow for Conftest

Next, create the workflow file to execute the check. This workflow automates the process of generating a Terraform plan and testing it against the Rego policy.

name: Terraform Conftest Policy Check

on:
  pull_request:
    paths:
      - '**.tf'
      - 'policies/**.rego'

jobs:
  terraform-conftest:
    runs-on: ubuntu-latest
    steps:
      # 1. Check out the repository code to the runner.
      - name: Checkout code
        uses: actions/checkout@v4

      # 2. Install and configure Terraform CLI.
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      # 3. Download and install the Conftest CLI.
      - name: Setup Conftest
        uses: open-policy-agent/setup-conftest@v2

      # 4. Initialize Terraform to download provider plugins.
      - name: Terraform Init
        run: terraform init

      # 5. Generate a plan file and convert it to structured JSON
      #    so it can be processed by Conftest.
      - name: Terraform Plan and Convert to JSON
        run: |
          terraform plan -out=plan.tfplan
          terraform show -json plan.tfplan > plan.json
      
      # 6. Run Conftest to test the JSON plan against all policies
      #    in the 'policies' directory. The job will fail if any 'deny'
      #    rule is triggered.
      - name: Run Conftest
        run: conftest test --policy policies/ plan.json

Example Failure Output

If a Terraform resource is missing the owner tag, Conftest will fail the step with a clear error message, causing the workflow to fail:

FAIL - plan.json - terraform.deny: Resource 'aws_s3_bucket.data' is missing the required 'owner' tag.

1 test, 0 passed, 1 failure
Error: Process completed with exit code 1.

Example 2: IaC Security Scanning with Checkov

Checkov is excellent for broad security scanning without writing custom policies. It checks your code against a massive, managed library of policies for known misconfigurations based on industry best practices like the CIS Benchmarks.

GitHub Actions Workflow for Checkov

Using the official Checkov action from the GitHub Marketplace is simple. This workflow scans a directory of Terraform code and uploads the results to GitHub's Security tab for easy review.

name: IaC Security Scan with Checkov

on:
  pull_request:
    paths:
      - 'infra/**'

jobs:
  checkov-scan:
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write # Required to upload SARIF results
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run Checkov
        id: checkov
        uses: bridgecrewio/checkov-action@v12
        with:
          # The directory containing your Terraform files.
          directory: ./infra
          # Specify the IaC framework being scanned.
          framework: terraform
          # Don't fail the build for LOW or MEDIUM severity issues,
          # but fail for HIGH or CRITICAL ones.
          soft_fail_on: HIGH
          # Output results in SARIF format, an industry standard for static analysis.
          output_format: sarif
          output_file_path: results.sarif

      # This step uploads the SARIF file generated by Checkov.
      - name: Upload SARIF file
        uses: github/codeql-action/upload-sarif@v3
        # Use 'if: always()' to ensure results are uploaded even if
        # the Checkov step fails the build.
        if: always() 
        with:
          sarif_file: results.sarif

Viewing Results in GitHub

By outputting to SARIF and using the upload-sarif action, findings will appear directly in your repository's Security > Code scanning tab. This provides a rich, searchable interface for developers to view, prioritize, and remediate security issues directly within GitHub.

Choosing the Right Tool: Conftest vs. Checkov

Both Conftest and Checkov are powerful tools, but they serve different primary purposes. Understanding their strengths will help you build a comprehensive policy enforcement strategy.

  • Use OPA Conftest for Custom Logic. Conftest is the perfect choice when you need to enforce rules that are unique to your organization. Examples include enforcing a specific tagging strategy, naming conventions, or restricting infrastructure to certain regions or instance types. It gives you maximum flexibility but requires you to write and maintain your own policies in Rego.
  • Use Checkov for Broad Security Coverage. Checkov is designed for security and compliance. Its vast library of built-in checks can immediately identify thousands of common misconfigurations across various frameworks (Terraform, CloudFormation, Kubernetes, etc.). It's the fastest way to align with security benchmarks like CIS, HIPAA, or PCI-DSS without writing a single policy yourself.

Better Together: These tools are not mutually exclusive. A best-practice strategy often involves using both: run Checkov for baseline security and compliance, and layer Conftest on top to enforce your organization's specific operational and governance rules.

Best Practices for CI/CD Policy Checks

💡 Key Takeaways

  • Trigger on Pull Requests: The most effective time to check policies is during pull request validation, providing immediate feedback before flawed code is merged.
  • Fail the Build Strategically: For critical security and compliance policies, always configure workflows to fail the build on violations. For less critical issues, like style warnings, use flags like soft_fail_on to log warnings without blocking merges.
  • Provide Clear Feedback: Your policy violation messages must be clear and actionable. A good message explains what is wrong, why it's wrong, and how to fix it, enabling developers to resolve issues without needing help.
  • Use Encrypted Secrets: Never hardcode credentials. Use GitHub Encrypted Secrets for storing cloud provider credentials, API keys, and other sensitive data securely in your workflows. Reference them using ${{ secrets.YOUR_SECRET }}.
  • Start Small and Iterate: Begin with a few high-impact policy checks. Consider running your workflow in an "audit-only" mode (where builds don't fail) at first. This allows teams to see violations and get comfortable with the process before you start enforcing blocking failures.
  • Cache Dependencies: Use actions like actions/cache to cache tool binaries (like Terraform providers) between workflow runs. This can significantly speed up your CI/CD jobs and reduce redundant downloads.