How to Enforce Open Policy Agent (OPA) with Terraform

How to Enforce Open Policy Agent (OPA) with Terraform blog post

How to Enforce Open Policy Agent (OPA) with Terraform

Testing your infrastructure setups has always been a difficult task. How can you ensure that sensitive ports, like 22 for SSH, are not left open? How can you be confident that each resource has essential tags attached? Without any automated checks, these gaps can easily turn into security risks or can lead to some unexpected costs. This is where Open Policy Agent (OPA) steps in, helping you by enforcing policies directly within your IaC workflows, ensuring a more secure and compliant setup.

In this blog, we’ll explain what Open Policy Agent (OPA) is and how it works. We’ll also guide you through some practical examples to show how OPA can be implemented within your IaC setup.

What is Open Policy Agent (OPA)?

Open Policy Agent (OPA) is a policy engine that evaluates your infrastructure configurations against some defined policies and provides results based on what rules are broken and why.

It operates as “policy-as-code,” where policies are defined, versioned, and tested in the same way as code. This way, teams can write rules that automatically check configurations before any deployment, making sure that your infrastructure meets security, compliance, and cost guidelines.

By integrating OPA with IaC tools like Terraform or Pulumi, you can easily enforce policies directly within your infrastructure workflows and can catch any policy issues at an early stage before deploying resources to your infrastructure.

What is Rego?

Rego is a declarative policy language that is used in Open Policy Agent (OPA) define rules that evaluate whether your infrastructure configurations meet the specific standards that are set by your organization. With Rego, you can write policies that validate configurations based on a JSON format.

In simple terms, Rego policies act as rulebooks for your infrastructure. For example, you might write a Rego policy to check that every AWS S3 bucket in a configuration has encryption enabled. Rego evaluates each item in the JSON data, such as Terraform plan output, and flags issues on the basis of that terraform plan output.

Its flexibility allows for granular checks, such as making sure that SSH is only accessible from specific IP ranges. This approach to policy enforcement makes Rego a powerful tool for maintaining compliance and security standards across complex environments.

Here’s a simple Rego policy to make sure that any public EC2 instance in AWS has restricted SSH (port 22) access to some specific IPs only:

package aws.security
deny[msg] {
input.resource.type == "aws_instance"
input.resource.public == true
input.resource.open_ports[_] == 22
not input.resource.allowed_ips[_] == input.source_ip
msg := sprintf("Public EC2 instance %s has SSH port 22 open to all IPs.", [input.resource.id])
}

In this Rego code:

  • Package Declaration: package aws.security organizes the policy for AWS security checks.
  • Deny Rule: The rule deny[msg] checks specific conditions and, if met, produces a message explaining why the configuration fails.
  • Condition 1: Confirms the resource is an EC2 instance (type == "aws_instance").
  • Condition 2: Checks that the instance is public (public == true).
  • Condition 3: Ensures SSH port (22) is open (open_ports[_] == 22).
  • Condition 4: Validates SSH is only accessible to approved IPs (allowed_ips); if not, it triggers the rule.

In this way, Rego helps DevOps teams to define and enforce infrastructure policies that automatically validate resources before any deployment.

How does OPA work?

  1. OPA begins by receiving input data in a JSON format. This data includes the details of cloud resources like EC2 instances, S3 buckets, or databases and lists their configurations, such as IP restrictions, open ports, and encryption settings.
  2. Next, OPA applies policies written in Rego. These policies outline what’s allowed in the infrastructure, like ensuring all S3 buckets have encryption or restricting SSH access to public EC2 instances.
  3. OPA then processes this input by checking each configuration against the policies to see if they meet the required standards or not.
  4. Finally, OPA produces a result that shows whether the configuration passes or fails the policy checks. If there’s an issue, OPA provides feedback identifying which policies were broken and why. For example, if an EC2 instance has open SSH access, OPA flags this as a security risk and provides details on the issue.

Now, when you are using OPA with Terraform, the process involves evaluating the Terraform plan file, Rego policies, and any other values or parameters. Here’s how it works with Terraform:

  1. So similarly, OPA first receives the Terraform plan file as input in JSON format. This plan includes details about the resources Terraform is set to create, modify, or delete, as well as configurations like IP settings, open ports, and encryption requirements.
  2. Rego policies are then applied directly to this terraform plan. For example, they might require that all S3 buckets have encryption enabled or that public EC2 instances restrict SSH access.
  3. Additional values or parameters can also be provided, such as allowed IP ranges, cost limits, or any region-specific configuration. These values help tailor the policy checks based on specific organizational needs or environments.
  4. OPA then processes the input by checking each resource in the terraform plan against the Rego policies to see if they meet the set standards.

Finally, OPA generates a result that shows whether each resource in the terraform plan complies with the policies. If any policy is violated, like if an EC2 instance has SSH open to all IPs, OPA flags it, providing specific feedback on the resource ID and detailing the rule that was broken.

This integration with OPA makes sure that configurations follow security standards early in the deployment process. By catching non-compliant resources before they’re deployed, OPA helps maintain a secure infrastructure and also reduces the risk of security issues down the line.

Use Cases for OPA

Now that we know how OPA works, let’s look at where it can be used to manage and secure your cloud resources:

  • Enforcing specific provider versions: When you are working with infrastructures where thousands of cloud resources are managed across various accounts, regions, and environments, it’s important to keep your configurations consistent across all of them. However, people writing Terraform code might sometimes miss specifying a version or attaching required tags. Enforcing a specific AWS provider version, like 3.30, ensures stability across all IaC deployments. For example, version 3.30 might be chosen because it reliably supports resources like S3 buckets and EC2 instances or addresses specific production issues, such as unreliable load balancer performance. By locking all teams to this particular version, OPA makes sure that everyone uses the same tested version, reducing any unexpected issues from untested newer versions until they’re fully validated by the testing team.
  • Budget and Cost Management: You can also control your cloud costs with OPA just by setting the spending limits on the resources before deployment. For example, if your monthly budget for development instances is around $50, OPA can check each EC2 instance type against this limit. If a member tries to deploy an m5.large instance, which costs around $70 monthly, OPA will flag it and block the deployment. This will allow that member to switch to a smaller, budget-friendly option, like t3.micro, helping each member within your organization stay within set cost limits across multiple environments.
  • IAM Policy Validation: OPA also helps secure IAM policies by setting rules that limit permissions to only what’s needed. For example, OPA can block any IAM role with broad permissions like ”s3:*”, which allows full access to all the S3 actions out there. Instead, it enforces specific actions, such as ”s3:ListBucket”, so the roles only get essential permissions. This follows the principle of least privilege, reducing access to any sensitive resources and making sure that each role has only the permissions required to do its job.
  • Limiting Instance Types and Sizes: Within some infrastructure setups where multiple teams and different workloads are present, it’s important to control the types and sizes of instances used to keep costs down. OPA can enforce policies that limit specific environments, like development, to smaller, budget-friendly instance types, such as t2.micro or t3.small. This approach avoids using high-cost, high-power instances in areas where they aren’t necessary at all, helping keep your costs under control and also making sure that any larger, high-performance instances are saved for important tasks within production environments.

How to Implement OPA Policies with Terraform

In this section, we’ll use OPA with Terraform to enforce policies, where we’ll require S3 bucket encryption before allowing any deployment.

Step 1: Write a Rego Policy for Terraform Configuration

We will start by creating a simple Rego policy that makes sure that all the S3 buckets have encryption enabled on it. This policy checks if server_side_encryption_configuration is set for each S3 bucket. If encryption is missing, it flags the bucket as non-compliant.

Here is the rego code for the same:

package terraform.s3
import future.keywords.in
deny[message] {
some resource in input.resource_changes
resource.type == "aws_s3_bucket"
not resource.change.after.server_side_encryption_configuration
message = sprintf("S3 Bucket %s does not have encryption enabled.", [resource.change.after.bucket])
}

Step 2: Integrate OPA with Terraform

Next, we’ll integrate OPA into our Terraform’s workflow to evaluate the terraform plan output against our Rego policy which we have just created above before applying any changes to the infrastructure. This integration will allow us to enforce security policy with our IaC.

If you haven’t used OPA before, you’ll need to install it first. You can install it from here.

With OPA installed, let’s move forward by writing the Terraform configuration that defines our resources and then generating a Terraform plan file out of it:

resource "aws_s3_bucket" "infrasity_logs" {
bucket = "infrasity-logs-bucket"
versioning {
enabled = true
}
}

In this code, we’re setting up an S3 bucket. This configuration will be evaluated by our OPA policy to ensure it meets the encryption requirement for this S3 bucket.

Now, start by running terraform init to initialize the configuration:

Next, generate the terraform plan, which will be used by OPA for policy checks:

Terminal window
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json

The first command creates a binary plan file (tfplan.binary), and the second converts it into a JSON format (tfplan.json). This JSON file will be evaluated by OPA against our encryption policy to ensure compliance before running terraform apply.

Step 3: Enforce S3 encryption policy.

Now that we have generated the Terraform plan in a JSON format (tfplan.json), we can move forward with using OPA to validate it against our Rego policy.

Run the following command to evaluate the plan:

Terminal window
opa eval --input tfplan.json --format pretty --data s3_encryption_policy.rego "data.terraform.s3.deny"

This command will let OPA review each resource configuration in the plan. If the policy finds any S3 bucket without encryption enabled, it will display a message identifying the specific bucket that doesn’t meet the policy requirements:

By running this validation step before executing terraform apply, we can make sure that any configuration issues are flagged early, allowing us to address them before any deployment happens.

With OPA set up, we need to run these policy checks each time we run terraform apply. However, relying on team members to run OPA manually can lead to missed checks. Instead, a more controlled approach would make these checks a default part of the CI/CD pipeline; this is where Terrateam comes into play.

OPA with Terrateam

With Terrateam, you can make sure that every team member runs OPA checks within the CI/CD process. By integrating Terrateam with GitHub, OPA policies are enforced on every pull request, making sure that all the infrastructure changes are checked before deployment.

Now, as we’ve already implemented the S3 bucket encryption example above, Terrateam allows us to implement multiple checks without needing to install and run OPA before every terraform apply.

In this segment, we’ll use Terrateam with OPA to set up cost limits that will simply flag or block any resources that exceed the budget that is defined in the Rego. In our second example, we’ll enforce secure security group configurations with OPA, making sure that SSH access (port 22) isn’t open to all the IPs.

To set up Terrateam with your GitHub repository, follow the setup instructions provided in this link.

Setting Cost Limits with OPA and Terrateam

In this example, we’ll use OPA with Terrateam to set cost limits for your Terraform resources. This setup will automatically flag or block any resource that exceeds the cost threshold directly within the pull request itself.

Firstly, within your config.yml define a post-plan hook that uses Conftest to run OPA policy checks on the cost report:

hooks:
plan:
post:
- type: run
cmd: ['conftest', 'test', '-o', 'table', '$TERRATEAM_TMPDIR/infracost/infracost.json', '--policy', '$TERRATEAM_ROOT/policies/gcp/compute-instance.rego']
capture_output: true

Next, define cost limits in your rego file, which should be located under the policies directory. This rego file sets both hourly and monthly cost limits, flagging any project that exceeds these limits.

package main
max_hourly_cost = 0.01 # Set hourly cost limit
max_monthly_cost = 0.02 # Set monthly cost limit
deny[msg] {
project := input.projects[_]
hourly_cost := to_number(project.breakdown.totalHourlyCost)
monthly_cost := to_number(project.breakdown.totalMonthlyCost)
hourly_cost > max_hourly_cost
msg = sprintf("Project %v exceeds the hourly spending limit. Hourly cost: %v, Limit: %v", [project.name, hourly_cost, max_hourly_cost])
}
deny[msg] {
project := input.projects[_]
monthly_cost > max_monthly_cost
msg = sprintf("Project %v exceeds the monthly spending limit. Monthly cost: %v, Limit: %v", [project.name, monthly_cost, max_monthly_cost])
}

Now, to test this setup, create a new branch within your GitHub repository and add a main.tf file with your Terraform configuration. Then, create a pull request. Once the pull request is submitted, Terrateam will automatically trigger the OPA policy check.

If any resources exceed the set cost limits, you’ll see detailed feedback in the pull request, showing whether the configuration meets the defined cost policies or not. Now, every time a team member creates a pull request within this repository, Terrateam will automatically trigger OPA checks.

Enforcing Secure Security Group Settings with OPA and Terrateam

For this example, we’ll use OPA with Terrateam to enforce security rules on AWS security groups, making sure that SSH access (port 22) isn’t open to all IP addresses.

To implement this, simply replace the previous cost-checking Rego policy with the following code to block any unrestricted SSH access:

package main
deny[msg] {
resource := input.resources[_]
resource.type == "aws_security_group"
some ingress
ingress = resource.values.ingress[_]
ingress.from_port == 22
ingress.to_port == 22
ingress.protocol == "tcp"
some cidr
cidr = ingress.cidr_blocks[_]
cidr == "0.0.0.0/0"
msg = sprintf("Security Group %v has an ingress rule allowing unrestricted SSH access on port 22.", [resource.name])
}

Once this policy is in place, Terrateam will automatically enforce it on each pull request and if a team member attempts to create a security group with unrestricted SSH access, Terrateam will flag it in the PR, showing a failure message like:

This setup makes sure that your security groups remain secure by blocking configurations that open SSH to the internet. Terrateam takes care of running the check so that all team members can easily align with the security standards defined by their organization in the form of Rego.

With Terrateam, you can set up OPA to automatically run, making sure that policy checks are completed before terraform apply. This integration brings policy enforcement directly into your CI/CD workflow, making sure all infrastructure changes align with security and compliance standards before deployment.

Conclusion

By now, you should have a clear understanding of how OPA can help enforce security, compliance, and cost standards in your infrastructure. We’ve covered what OPA and Rego are, how to write Rego policies, and how to implement infrastructure testing with examples like setting cost limits and securing SSH access.

Infrastructure as Code. Optimized.

Ready to get started?

Experience powerful infrastructure orchestration, seamlessly integrated with GitHub.