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.
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
}
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."
}
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"
.
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
}
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."
}
Here, the deny
rule checks for aws_db_instance
resources where the storage_encrypted
attribute is explicitly set to false
.
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
}
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."
}
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
In this workflow:
- The code is checked out.
-
conftest
is downloaded and installed. -
terraform init
andterraform plan
are executed to generate a plan. -
terraform show -json
converts the binary plan into a JSON format that OPA can understand. -
conftest test tfplan.json --policy ./policies
runs the policies located in the./policies
directory against the generatedtfplan.json
. - 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.
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)