Understanding DevOps and Cloud Maturity Models: A Guide to Elevating Your IT Strategy
In today’s fast-paced technological landscape, DevOps and Cloud practices are integral to accelerating software delivery and optimizing cloud resources. But as
In the world of infrastructure as code (IaC), maintaining compliance and security can be challenging. This is where Open Policy Agent (OPA) combined with Terraform comes into play. By integrating OPA policies with Terraform, you can enforce compliance and security best practices automatically. In this article, we'll explore how to use OPA with Terraform, and we'll provide examples to demonstrate common use cases.
Open Policy Agent (OPA) is an open-source policy engine that allows you to define and enforce policies across various systems. It uses a high-level declarative language called Rego to write policies. OPA can be integrated with various tools and services, including Kubernetes, microservices, CI/CD pipelines, and, as we'll see, Terraform.
Terraform is an open-source tool by HashiCorp that allows you to define and manage your infrastructure as code. With Terraform, you can describe your infrastructure using a high-level configuration language, and then use that configuration to create, update, and version your infrastructure.
Integrating OPA with Terraform allows you to enforce policies on your infrastructure code before it gets deployed. This ensures that your infrastructure adheres to compliance, security, and operational best practices, reducing the risk of misconfigurations and policy violations.
OPA policies are written in Rego
, a high-level declarative language. Rego policies are organized into packages, and each package can contain multiple rules. Here's an example of a simple OPA policy that enforces a minimum Terraform version:
1package terraform.module
2
3import input as tfplan
4import rego.v1
5
6minimum_terraform := "1.6.0"
7
8deny contains msg if {
9 v := tfplan.terraform_version
10 semver.compare(v, minimum_terraform) < 0
11
12 msg := sprintf("terraform version must be at least %v - %v is not supported", [minimum_terraform, v])
13}
To verify our OPA policy, we can use the OPA CLI to check the policy file:
1$ opa check policies/minimal_terraform_version.rego
To integrate OPA with Terraform, we need to generate a Terraform plan and convert it to JSON format. We can then evaluate the OPA policy against the JSON plan. Here's an example using Terraform to create an S3 bucket with bucket ownership controls and an ACL:
1resource "aws_s3_bucket" "example" {
2 bucket = "my-tf-example-bucket"
3}
4
5resource "aws_s3_bucket_ownership_controls" "example" {
6 bucket = aws_s3_bucket.example.id
7 rule {
8 object_ownership = "BucketOwnerPreferred"
9 }
10}
11
12resource "aws_s3_bucket_acl" "example" {
13 depends_on = [aws_s3_bucket_ownership_controls.example]
14
15 bucket = aws_s3_bucket.example.id
16 acl = "public-read"
17}
1$ terraform plan -out tfplan
1$ terraform show -json tfplan > tfplan.json
1$ opa eval --data policies/minimal_terraform_version.rego --input tfplan.json "data.terraform.module.deny"
2
3{
4 "result": [
5 {
6 "expressions": [
7 {
8 "value": [],
9 "text": "data.terraform.module.deny",
10 "location": {
11 "row": 1,
12 "col": 1
13 }
14 }
15 ]
16 }
17 ]
18}
You can also use OPA policies to verify S3 bucket policies. Here's an example policy that enforces that S3 buckets have specific ACLs:
1package terraform.module
2
3import input as tfplan
4import rego.v1
5
6allowed_s3_acls := ["private", "authenticated-read"]
7
8deny contains msg if {
9 r := tfplan.resource_changes[_]
10 r.type == "aws_s3_bucket_acl"
11 not r.change.after.acl in allowed_s3_acls
12
13 msg := sprintf("%v - s3 buckets must have acls that are in %v", [r.address, allowed_s3_acls])
14}
To evaluate the policy to the same plan we had created before, you just need to run the following command:
1$ opa eval --data policies/s3_bucket_acl.rego --input tfplan.json "data.terraform.module.deny"
2{
3 "result": [
4 {
5 "expressions": [
6 {
7 "value": [
8 "aws_s3_bucket_acl.example - s3 buckets must have acls that are in [\"private\", \"authenticated-read\"]"
9 ],
10 "text": "data.terraform.module.deny",
11 "location": {
12 "row": 1,
13 "col": 1
14 }
15 }
16 ]
17 }
18 ]
19}
Let's enforce a policy to ensure that no security group allows SSH access from anywhere.
1package terraform.module
2
3import rego.v1
4import input as tfplan
5
6disallowed_cidrs := ["0.0.0.0/0", "::/0"]
7
8deny contains msg if {
9 r := tfplan.resource_changes[_]
10 r.type in ["aws_security_group_rule", "aws_security_group"]
11 valid_port(r.change.after.ingress[_])
12 invalid_cidr(r.change.after.ingress[_])
13
14 msg := sprintf("%v has 0.0.0.0/0 as allowed ingress for SSH / Port 22", [r.address])
15}
16
17valid_port(ingress) if ingress.from_port == 22
18valid_port(ingress) if ingress.to_port == 22
19
20invalid_cidr(ingress) if ingress.cidr_blocks[_] in disallowed_cidrs
21invalid_cidr(ingress) if ingress.ipv6_cidr_blocks[_] in disallowed_cidrs
Here’s a sample Terraform configuration for an AWS security group:
1resource "aws_security_group" "example" {
2 name = "example-sg"
3 description = "Beispiel Sicherheitsgruppe"
4
5 ingress {
6 from_port = 22
7 to_port = 22
8 protocol = "tcp"
9 cidr_blocks = ["0.0.0.0/0"]
10 }
11}
12
13resource "aws_security_group_rule" "example" {
14 type = "ingress"
15 from_port = 22
16 to_port = 22
17 protocol = "tcp"
18 ipv6_cidr_blocks = [ "::/0" ]
19 security_group_id = aws_security_group.example.id
20}
1$ terraform plan -out=tfplan
2$ terraform show -json tfplan > tfplan.json
1$ opa eval --data policies/security_group_ssh.rego --input tfplan.json "data.terraform.module.deny"
2
3{
4 "result": [
5 {
6 "expressions": [
7 {
8 "value": [
9 "aws_security_group.example has 0.0.0.0/0 as allowed ingress for SSH / Port 22"
10 ],
11 "text": "data.terraform.module.deny",
12 "location": {
13 "row": 1,
14 "col": 1
15 }
16 }
17 ]
18 }
19 ]
20}
In the previous examples, we evaluated individual policies against the Terraform plan. However, you can also bundle multiple policies together and evaluate them all at once. Here's how you can do that:
1$ terraform plan -out=tfplan
2$ terraform show -json tfplan > tfplan.json
3$ opa exec --decision terraform/module/deny --bundle policies/ tfplan.json
4
5{
6 "result": [
7 {
8 "path": "tfplan.json",
9 "result": [
10 "aws_s3_bucket_acl.example - s3 buckets must have acls that are in [\"private\", \"authenticated-read\"]",
11 "aws_security_group.example has 0.0.0.0/0 as allowed ingress for SSH / Port 22"
12 ]
13 }
14 ]
15}
Integrating OPA with Terraform provides a powerful mechanism to enforce compliance, security, and operational best practices in your infrastructure as code. By defining policies in OPA and validating Terraform configurations against these policies, you can ensure that your infrastructure remains secure and compliant.
With the examples provided, you can start creating your own policies to meet your organization's requirements. Happy coding!
You are interested in our courses or you simply have a question that needs answering? You can contact us at anytime! We will do our best to answer all your questions.
Contact us