Terraform_policy_as_code
Terraform Policy as Code
Section titled “Terraform Policy as Code”Overview
Section titled “Overview”Policy as Code (PaC) enables you to define, implement, and enforce policies programmatically. This ensures infrastructure complies with organizational standards, security requirements, and best practices before deployment.
Why Policy as Code?
Section titled “Why Policy as Code?”- Prevent misconfigurations: Catch issues before production
- Enforce standards: Consistent policy enforcement
- Audit trails: Track policy changes in version control
- Self-service: Enable safe self-service provisioning
- Compliance: Meet regulatory requirements (SOC2, PCI-DSS, HIPAA)
Policy as Code Workflow
Section titled “Policy as Code Workflow”┌─────────────────────────────────────────────────────────────────┐│ Policy as Code Workflow ││ ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ Write │───▶│ Test │───▶│ Enforce │ ││ │ Policies │ │ Policies │ │ in CI/CD │ ││ └─────────────┘ └─────────────┘ └─────────────┘ ││ │ │ ││ ▼ ▼ ││ ┌─────────────┐ ┌─────────────┐ ││ │ Version │ │ Approve/ │ ││ │ Control │ │ Reject │ ││ └─────────────┘ └─────────────┘ │└─────────────────────────────────────────────────────────────────┘Open Policy Agent (OPA)
Section titled “Open Policy Agent (OPA)”OPA is an open-source policy engine:
# Install OPAbrew install opa
# Check versionopa versionRego Policy Language
Section titled “Rego Policy Language”# Example: Deny public S3 bucketspackage terraform
deny[msg] { input.resource_type == "aws_s3_bucket" input.config.acl == "public-read" msg = "S3 bucket must not be public"}
# With conditionsdeny[msg] { input.resource_type == "aws_instance" not input.config.vpc_security_group_ids msg = "EC2 instance must be in a VPC with security groups"}
# Allow with specific conditionsallow[msg] { input.resource_type == "aws_iam_user" input.config.name == "admin" input.provider == "aws" msg = "IAM user creation is allowed only for admin"}OPA Test
Section titled “OPA Test”package terraform
test_deny_public_s3 { deny with input as { "resource_type": "aws_s3_bucket", "config": {"acl": "public-read"} }}
test_allow_private_s3 { not deny with input as { "resource_type": "aws_s3_bucket", "config": {"acl": "private"} }}Run tests:
opa test policy.rego -vSentinel (Terraform Cloud/Enterprise)
Section titled “Sentinel (Terraform Cloud/Enterprise)”Sentinel is HashiCorp’s policy as code framework:
# Example: Restrict instance typesimport "tfplan/v2" as tfplan
# Get all EC2 instancesinstances = tfplan.resource_changes[type is "aws_instance"]
# Define allowed instance typesallowed_types = [ "t3.micro", "t3.small", "t3.medium",]
# Validation rulevalidate_instance_type = func() { all instances as i { i.change.after.instance_type in allowed_types }}
# Main rulemain = rule { validate_instance_type()}Sentinel Examples
Section titled “Sentinel Examples”# Restrict regionsimport "tfplan/v2" as tfplan
allowed_regions = [ "us-east-1", "us-west-2", "eu-west-1",]
main = rule { all tfplan.resource_changes as rc { all rc.change.after as key, value { key != "region" or value in allowed_regions } }}# Require tagsimport "tfplan/v2" as tfplan
required_tags = [ "Environment", "CostCenter", "Owner",]
main = rule { all tfplan.resource_changes as rc { rc.type in ["aws_instance", "aws_s3_bucket", "aws_rds_instance"] implies all required_tags as tag { rc.change.after.tags contains tag } }}Conftest
Section titled “Conftest”Policy testing for configuration files:
package main
deny[msg] { input.kind == "Deployment" not input.spec.replicas msg = "Deployment must specify replicas"}
deny[msg] { input.kind == "Deployment" input.spec.replicas == 0 msg = "Deployment replicas cannot be 0"}# Install conftestbrew install conftest
# Test Terraform planconftest test plan.out -p policy/rego/
# Test Kubernetes manifestsconftest test deployment.yaml -p policy/rego/Terraform Guardrails
Section titled “Terraform Guardrails”AWS Guardrails for Terraform:
# Install cfn-guardpip install cfn-guard
# Create rulescfn-guard rulegen --template type=terraform > rules.guard# AWS SCP Example{ "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Action": [ "ec2:*" ], "Resource": "*", "Condition": { "StringNotEquals": { "aws:RequestedRegion": ["us-east-1", "us-west-2"] } } } ]}Integrating with CI/CD
Section titled “Integrating with CI/CD”GitHub Actions
Section titled “GitHub Actions”name: Policy Check
on: [pull_request]
jobs: policy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3
- name: Run OPA run: | opa eval --fail-defined -d policy.rego "data.terraform.deny" \ -f json \ --fail-defined \ terraform_plan.json > result.json
- name: Check Results run: | if [ -s result.json ]; then echo "Policy violations found:" cat result.json exit 1 fiTerraform Cloud/Enterprise
Section titled “Terraform Cloud/Enterprise”policy_set "aws-policies" { source = "./policies" enforce { mode = "hard-mandatory" }}Pre-Commit Hooks
Section titled “Pre-Commit Hooks”repos: - repo: https://github.com/open-policy-agent/conftest rev: v0.25.0 hooks: - id: conftest args: ["test", "--all", "--data", "tests/"]Common Policies
Section titled “Common Policies”Security Policies
Section titled “Security Policies”# Security: Require encryptionpackage terraform
deny[msg] { input.resource_type == "aws_s3_bucket" not input.config.server_side_encryption_configuration msg = "S3 bucket must have encryption enabled"}
deny[msg] { input.resource_type == "aws_db_instance" not input.config.storage_encrypted msg = "RDS instance must be encrypted"}
# Require secure protocolsdeny[msg] { input.resource_type == "aws_alb" not input.config.ssl_policy msg = "ALB must use secure SSL policy"}Cost Policies
Section titled “Cost Policies”# Cost: Restrict expensive instance typespackage terraform
deny[msg] { input.resource_type == "aws_instance" input.config.instance_type in ["m5.4xlarge", "r5.4xlarge", "c5.4xlarge"] msg = "Large instance types require approval"}
# Cost: Limit storage sizedeny[msg] { input.resource_type == "aws_db_instance" input.config.allocated_storage > 1000 msg = "Database storage over 1TB requires approval"}Tagging Policies
Section titled “Tagging Policies”# Tags: Required tagspackage terraform
required_tags = ["Environment", "CostCenter", "Owner"]
deny[msg] { input.resource_type == "aws_instance" missing_tags := required_tags - keys(input.config.tags) count(missing_tags) > 0 msg = sprintf("Missing required tags: %v", [missing_tags])}Best Practices
Section titled “Best Practices”- Start with basics: Begin with simple policies
- Version control: Store policies in Git
- Test thoroughly: Test policies before enforcement
- Gradual enforcement: Start with warnings, then hard enforcement
- Document policies: Explain why each policy exists
- Regular review: Update policies as requirements change
- False positives: Allow for exceptions process
Summary
Section titled “Summary”Policy as Code is essential for:
- Security: Enforce security standards automatically
- Compliance: Meet regulatory requirements
- Cost control: Prevent expensive resources
- Governance: Consistent policy enforcement
Key tools:
- OPA: Open-source policy engine with Rego
- Sentinel: HashiCorp’s policy framework
- Conftest: Configuration policy testing
- Guardrails: Cloud-specific policy enforcement