DEV Community

vAIber
vAIber

Posted on

Shifting Left with Policy-as-Code: Enforcing Infrastructure Security in Your DevSecOps Pipeline using OPA and Rego

Shifting Left with Policy-as-Code: Enforcing Infrastructure Security in Your DevSecOps Pipeline using OPA and Rego

The rapid adoption of Infrastructure as Code (IaC) has revolutionized how organizations provision and manage their cloud environments. However, this agility often comes with the inherent risk of introducing security vulnerabilities if proper checks are not integrated early in the development lifecycle. Traditionally, security reviews occurred much later, leading to costly remediation and deployment delays. This is where the concept of "shift-left" security, empowered by Policy-as-Code (PaC), becomes indispensable for any modern DevSecOps pipeline.

PaC is the practice of managing and automating policies through code, treating them like any other software artifact. It enables security and compliance checks to be executed before infrastructure is deployed, catching misconfigurations and policy violations at the earliest possible stage. This proactive approach significantly reduces the attack surface, streamlines compliance, and fosters a culture of shared security responsibility between development and operations teams. By embedding security policies directly into the CI/CD pipeline, organizations can ensure consistent enforcement across all environments, accelerating secure deployments.

A conceptual diagram illustrating the

Understanding OPA and Rego

At the heart of many Policy-as-Code implementations lies the Open Policy Agent (OPA). OPA is an open-source, general-purpose policy engine that enables unified, context-aware policy enforcement across the entire stack. It decouples policy decision-making from policy enforcement, allowing you to define policies once and apply them consistently across microservices, Kubernetes, CI/CD pipelines, API gateways, and more.

OPA evaluates policies written in Rego, a high-level declarative query language. Rego is designed for expressing policies over structured data (like JSON or YAML). It allows you to write rules that define what is permitted or denied based on input data, which in our case, will be the planned infrastructure changes from IaC tools like Terraform. Rego's strength lies in its ability to query complex nested data structures and define logical conditions for policy violations.

Practical Rego Policies for Common IaC Security Issues

Let's explore how to write practical Rego policies to address common infrastructure security misconfigurations using Terraform as our IaC example. OPA can consume the JSON output of terraform plan to evaluate proposed changes against defined policies.

Example 1: Preventing Public S3 Buckets

Public S3 buckets are a notorious source of data breaches. Our policy will ensure no S3 bucket is configured with public ACLs.

Insecure Terraform:

resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-insecure-bucket"
  acl    = "public-read" # Policy should flag this
}
Enter fullscreen mode Exit fullscreen mode

Rego Policy Example (s3_policy.rego):

package main

deny[msg] {
  input.resource_changes[_].type == "aws_s3_bucket"
  input.resource_changes[_].change.after.acl == "public-read"
  msg := "S3 buckets should not have public ACLs."
}

deny[msg] {
  input.resource_changes[_].type == "aws_s3_bucket"
  input.resource_changes[_].change.after.acl == "public-read-write"
  msg := "S3 buckets should not have public ACLs."
}
Enter fullscreen mode Exit fullscreen mode

This Rego policy defines two deny rules. Each rule triggers a violation (deny[msg]) if an S3 bucket resource (aws_s3_bucket) is being created or modified (input.resource_changes[_]) such that its acl attribute is set to either "public-read" or "public-read-write".

A visual representation of an S3 bucket with a large red 'X' over it and a padlock icon that is unlocked and broken, symbolizing an insecure public S3 bucket. Text around it reads 'Public Access Denied by Policy' or 'No Public ACLs'.

Example 2: Enforcing Encryption for RDS Databases

Encrypting data at rest is a fundamental security requirement. This policy ensures all new RDS instances have storage encryption enabled.

Insecure Terraform:

resource "aws_db_instance" "default" {
  engine                 = "mysql"
  engine_version         = "5.7"
  allocated_storage      = 20
  storage_encrypted      = false # Policy should flag this
}
Enter fullscreen mode Exit fullscreen mode

Rego Policy Example:

package main

deny[msg] {
  input.resource_changes[_].type == "aws_db_instance"
  input.resource_changes[_].change.after.storage_encrypted == false
  msg := "RDS instances must have storage encryption enabled."
}
Enter fullscreen mode Exit fullscreen mode

Here, the deny rule checks for aws_db_instance resources where the storage_encrypted attribute is explicitly set to false.

A stylized depiction of a database server with a shield icon and a strong, locked padlock, representing an encrypted RDS instance. Green checkmarks surround it, signifying compliance and security.

Example 3: Disallowing Insecure EC2 Security Group Rules (e.g., SSH from 0.0.0.0/0)

Exposing services like SSH to the entire internet (0.0.0.0/0) is a major security risk. This policy prevents such broad access.

Insecure Terraform:

resource "aws_security_group_rule" "allow_ssh_all" {
  type        = "ingress"
  from_port   = 22
  to_port     = 22
  protocol    = "tcp"
  cidr_blocks = ["0.0.0.0/0"] # Policy should flag this
  security_group_id = aws_security_group.web.id
}
Enter fullscreen mode Exit fullscreen mode

Rego Policy Example:

package main

deny[msg] {
  input.resource_changes[_].type == "aws_security_group_rule"
  input.resource_changes[_].change.after.type == "ingress"
  input.resource_changes[_].change.after.from_port == 22
  input.resource_changes[_].change.after.to_port == 22
  input.resource_changes[_].change.after.protocol == "tcp"
  contains(input.resource_changes[_].change.after.cidr_blocks, "0.0.0.0/0")
  msg := "Security group rules should not allow SSH from 0.0.0.0/0."
}
Enter fullscreen mode Exit fullscreen mode

This more complex policy combines several conditions: it checks for an ingress security group rule, targeting port 22 (SSH), and verifies if the cidr_blocks list contains 0.0.0.0/0.

Integrating OPA into Your CI/CD Pipeline

The true power of Policy-as-Code is realized when integrated directly into your CI/CD pipeline. conftest is a utility that helps you write tests against structured configuration data using OPA. It's an excellent tool for integrating Rego policies into your automated workflows.

Here's an example of how to integrate conftest into a GitHub Actions workflow to scan Terraform plans:

name: IaC Security Scan with OPA/Conftest

on: [push, pull_request]

jobs:
  scan-terraform:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

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

      - name: Init Terraform
        run: terraform init

      - name: Plan Terraform
        run: terraform plan -out tfplan.binary

      - name: Convert Terraform Plan to JSON
        run: terraform show -json tfplan.binary > tfplan.json

      - name: Run Conftest against Terraform Plan
        run: conftest test tfplan.json --policy ./policies # Assuming policies are in ./policies directory

      - name: Fail on Conftest violations
        run: |
          if [ $(conftest test tfplan.json --policy ./policies | grep "FAIL" | wc -l) -gt 0 ]; then
            echo "Conftest found policy violations!"
            exit 1
          fi
Enter fullscreen mode Exit fullscreen mode

In this workflow:

  1. The code is checked out.
  2. conftest is downloaded and installed.
  3. terraform init and terraform plan are executed to generate a plan.
  4. terraform show -json converts the binary plan into a JSON format that OPA can understand.
  5. conftest test tfplan.json --policy ./policies runs the policies located in the ./policies directory against the generated tfplan.json.
  6. A final step checks the conftest output for any "FAIL" messages and causes the workflow to fail if violations are found, preventing the deployment of insecure infrastructure.

This integration ensures that every proposed infrastructure change is automatically vetted against your security and compliance policies before it can be deployed, providing immediate feedback to developers and preventing insecure configurations from reaching production. For a deeper dive into integrating security tools, refer to this DevSecOps Integration Guide.

A diagram showing a CI/CD pipeline with a specific stage highlighted for

Advanced Topics

While the examples above cover basic use cases, OPA and Rego offer advanced capabilities for more complex scenarios:

  • Custom Data Sources: OPA can load external data (e.g., from a CMDB, vulnerability scanner, or asset inventory) to enrich policy decisions. This allows for policies that react to the current state of your environment or external threat intelligence.
  • Policy Versioning: Treating policies as code means they can be versioned, reviewed, and managed in a Git repository, just like application code. This provides an audit trail and facilitates collaborative policy development.
  • Integrating with GitOps Workflows: In a GitOps model, infrastructure changes are applied by an automated operator that observes a Git repository. OPA can be integrated into this flow to ensure that only policy-compliant changes are reconciled and applied to the cluster.

Benefits and Best Practices

Adopting Policy-as-Code with OPA and Rego offers significant benefits:

  • Consistency: Enforces security and compliance policies uniformly across all environments, regardless of the team or project.
  • Speed: Automates security checks, eliminating manual reviews and accelerating development cycles.
  • Early Detection: Catches misconfigurations at the design or commit phase, significantly reducing the cost and effort of remediation.
  • Compliance: Automates the verification of regulatory and internal compliance standards, providing an auditable trail.
  • Scalability: Policies can be scaled to manage a growing number of infrastructure resources and teams without increasing manual overhead.

Best Practices:

  • Start Small: Begin with a few critical policies and gradually expand your policy library.
  • Version Control Policies: Store your Rego policies in a dedicated Git repository.
  • Test Your Policies: Write unit tests for your Rego policies to ensure they behave as expected.
  • Educate Your Teams: Provide training to developers and operations staff on writing and understanding Rego policies.
  • Iterate and Refine: Policies are living documents; continuously review and update them as your infrastructure and security posture evolve.

By embracing Policy-as-Code with OPA and Rego, organizations can effectively shift left their infrastructure security, embedding automated guardrails directly into their DevSecOps pipelines. This proactive approach not only enhances security posture but also fosters a more agile, compliant, and efficient development environment.

Top comments (0)