Hentikan terraform apply sebelum merusak produksi! Pelajari konsep Policy as Code dengan Open Policy Agent (Rego) dan HashiCorp Sentinel untuk menegakkan aturan keamanan, tags, dan biaya secara otomatis.

Setelah di episode 15 sebelumnya kita membahas secret management dan keamanan state file — bagaimana memastikan rahasia tidak bocor melalui state, dan bagaimana mengambil secret dari Vault, AWS Secrets Manager, maupun SOPS — pada episode kali ini kita akan membahas pertahanan berikutnya: Policy as Code (PaC) menggunakan Open Policy Agent (OPA) dan HashiCorp Sentinel.
Ini cerita yang sering terjadi di dunia nyata: seorang engineer baru diminta menambahkan security group rule untuk membuka port SSH dari rumahnya. Tanpa sadar, ia mengetik 0.0.0.0/0 sebagai CIDR — artinya port 22 terbuka untuk seluruh internet. Code review pun terlewat karena perubahan terlihat "kecil dan polos". Beberapa jam kemudian, instance produksi masuk ke daftar bitcoin miner. Skenario sebaliknya juga umum: ada engineer yang iseng membuat S3 bucket publik hanya untuk "test bentar", atau mem-bootstrap EC2 tanpa tags sehingga tim cost bingung mengatur anggaran.
Manusia pasti bisa melakukan kesalahan, dan code review manual tidak akan pernah menangkap semuanya — apalagi saat pull request berisi ratusan baris perubahan infrastruktur. Solusinya adalah mengotomatiskan aturan itu sendiri, menjadikannya bagian dari pipeline, dan membuatnya dapat di-review seperti kode. Itulah esensi dari Policy as Code.
Semakin besar infrastruktur, semakin mustahil men-review setiap baris HCL secara manual. Masalahnya bukan hanya soal kelelahan manusia, tapi juga:
terraform apply — misalnya kombinasi dua resource yang membuat akses publik.Note
Posisi PaC dalam pipeline: ia bukan pengganti linter (tflint) atau scanner keamanan statis (Checkov/Trivy). Scanner statis membaca kode HCL untuk mencari pola miskonfigurasi. PaC membaca hasil terraform plan — kondisi nyata yang akan diterapkan — sehingga bisa menangkap pelanggaran yang hanya muncul saat runtime.
| Aspek | Static Scanning (Checkov/Trivy) | Policy as Code (OPA/Sentinel) |
|---|---|---|
| Input | Kode HCL / file IaC | Output terraform plan (JSON) |
| Kapan dijalankan | Awal pipeline (lint/gate) | Setelah plan, sebelum apply |
| Kemampuan | Mendeteksi pola misconfig statis | Mengevaluasi kondisi nyata yang akan diterapkan |
| Contoh | "Ada acl = public-read di kode" | "Plan ini akan membuka port 22 ke 0.0.0.0/0" |
| Sifat hasil | Pass/Fail per file | Pass/Fail per rencana eksekusi |
Bayangkan PaC seperti pos pemeriksaan keamanan bandara yang diletakkan persis sebelum gerbang keberangkatan. Penumpang (plan) boleh antre, tapi sebelum naik pesawat (apply), mereka harus melewati pemeriksaan (policy engine). Jika ada barang terlarang (pelanggaran), penumpang itu dihentikan.
Alur kerja PaC bersama Terraform:
terraform plan -out=tfplan — hitung rencana perubahan.terraform show -json tfplan > tfplan.json — ekspor rencana menjadi JSON yang bisa dibaca mesin.tfplan.json terhadap kumpulan aturan.Kunci dari alur ini adalah terraform show -json: semua rencana perubahan — resource apa yang akan dibuat/diubah/dihapus, atribut apa yang akan diterapkan — diubah menjadi data terstruktur yang bisa diperiksa oleh mesin policy.
terraform plan -out=tfplan # 1. Rencana
terraform show -json tfplan > tfplan.json # 2. Ekspor ke JSON
opa eval ... # 3. Evaluasi policy
terraform apply tfplan # 4. Baru boleh di siniDua tools yang paling populer untuk PaC di ekosistem Terraform adalah:
Open Policy Agent (OPA) — engine policy open-source yang didanai CNCF, menggunakan bahasa Rego. OPA bersifat generic: ia bisa mengevaluasi policy untuk Kubernetes, Envoy, HTTP API, maupun Terraform. Karena open-source, ia mudah diintegrasikan ke pipeline CI/CD kalian sendiri.
HashiCorp Sentinel — engine policy proprietary milik HashiCorp yang terintegrasi natively ke Terraform Cloud / HCP Terraform dan Vault Enterprise. Bahasa Sentinel sengaja dibuat mudah dibaca (mirip HCL) dan menggunakan konsep rule dengan tingkatan enforce (hard-mandatory vs soft-mandatory).
| Aspek | OPA / Rego | HashiCorp Sentinel |
|---|---|---|
| Lisensi | Open source (CNCF) | Proprietary (HashiCorp) |
| Bahasa policy | Rego (declarative, berbasis set) | Sentinel (mirip HCL) |
| Cakupan | Multi-platform: K8s, Terraform, HTTP, Envoy | Ekosistem HashiCorp (Terraform, Vault, Consul) |
| Integrasi Terraform | External — eval plan JSON via CLI / CI | Native di Terraform Cloud / HCP |
| Cara menegakkan | Skrip gate / platform pendukung (Spacelift, etc.) | Policy set + enforcement level |
| Biaya | Gratis | Enterprise |
OPA cukup didistribusikan sebagai satu binary statis. Mari pasang:
# macOS (Homebrew)
brew install opa
# Linux (binary statis)
curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static
chmod +x opa
sudo mv opa /usr/local/bin/
# Verifikasi
opa versionOutputnya akan menunjukkan versi dan komitmen build:
Version: 1.x.x
Build Commit: 3f4f1b2
Build Timestamp: 2026-07-15T00:00:00Z
Go Version: go1.23.4
Platform: linux/amd64Kita mulai dengan aturan paling umum: tidak boleh ada S3 bucket publik. Policy ditulis dalam bahasa Rego. Setiap file policy mendeklarasikan package (namespace) dan aturan deny — jika ada satu pesan pun di dalam deny, berarti plan melanggar policy.
package terraform
import rego.v1
PUBLIC_ACLS := {"public-read", "public-read-write", "authenticated-read"}
deny contains msg if {
some rc in input.resource_changes
rc.type == "aws_s3_bucket"
rc.change.actions[_] != "delete"
rc.change.after.acl in PUBLIC_ACLS
msg := sprintf("%s: S3 bucket publik dilarang oleh policy organisasi.", [rc.address])
}Mari bedah satu per satu:
package terraform — namespace policy, sehingga aturan bisa dipanggil lewat data.terraform.deny.import rego.v1 — gaya Rego modern (keyword if, contains, in).PUBLIC_ACLS — set nilai ACL yang dianggap berbahaya.some rc in input.resource_changes — iterasi seluruh resource dalam plan.rc.change.actions[_] != "delete" — abaikan resource yang sedang dihapus (yang penting hanya yang dibuat/diubah).rc.change.after.acl in PUBLIC_ACLS — cek apakah ACL bucket setelah diterapkan termasuk publik.msg := ... — pesan pelanggaran yang akan muncul sebagai output.Struktur input.resource_changes ini persis berasal dari terraform show -json, jadi policy langsung mencerminkan apa yang akan dilakukan Terraform.
Sekarang kita satukan semuanya. Bangkitkan plan, ekspor ke JSON, lalu evaluasi dengan OPA:
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
opa eval \
--format pretty \
--data policy \
--input tfplan.json \
"data.terraform.deny"Jika ada pelanggaran, hasilnya berupa daftar pesan:
[
"aws_s3_bucket.assets: S3 bucket publik dilarang oleh policy organisasi.",
"aws_security_group_rule.ssh_world: SSH (22) terbuka ke 0.0.0.0/0 — DILARANG",
"aws_instance.web: wajib memiliki tags (hilang: CostCenter, Team)"
]Bila tidak ada pelanggaran, hasilnya [] — plan aman untuk diterapkan.
Di pipeline, hasil evaluasi ini diubah menjadi gate: lulus jika deny kosong, gagal jika ada satu pun pelanggaran. Berikut contoh skrip gate sederhana:
#!/usr/bin/env bash
set -euo pipefail
opa eval \
--format json \
--data policy \
--input tfplan.json \
"data.terraform.deny" > /tmp/deny.json
violations=$(jq '.result[0] | length' /tmp/deny.json)
if [ "$violations" -gt 0 ]; then
echo "::error::Infrastruktur MELANGGAR policy:"
jq -r '.result[0][]' /tmp/deny.json
exit 1
fi
echo "Policy gate: LULUS — plan aman untuk di-apply"Important
Ingat aturan dari episode 13: terraform apply tidak boleh berjalan dari laptop developer lokal di produksi. PaC berfungsi maksimal ketika ia menjadi tahap wajib di pipeline CI/CD — tepat setelah plan, dan hanya lulus bila policy gate lolos sebelum ada mekanisme manual approval.
Aturan kedua yang sangat berguna untuk kontrol biaya: setiap VM wajib memiliki tags standar (Environment, CostCenter, Team). Tanpa tags, beban biaya cloud tidak bisa dialokasikan ke tim masing-masing.
package terraform
import rego.v1
REQUIRED_TAGS := {"Environment", "CostCenter", "Team"}
deny contains msg if {
some rc in input.resource_changes
rc.type in {"aws_instance", "aws_eks_node_group", "aws_lb"}
rc.change.actions[_] != "delete"
missing_tags := REQUIRED_TAGS - object.keys(rc.change.after.tags)
count(missing_tags) > 0
msg := sprintf(
"%s: wajib memiliki tags %v (hilang: %v)",
[rc.address, REQUIRED_TAGS, missing_tags],
)
}Logika utamanya ada di baris missing_tags := REQUIRED_TAGS - object.keys(rc.change.after.tags). Kita menghitung selisih antara tags yang diwajibkan dan tags yang dimiliki resource; jika ada sisa, maka ada yang hilang dan pelanggaran terjadi.
Aturan ketiga menargetkan salah satu penyebab serangan paling umum: SSH port 22 terbuka ke 0.0.0.0/0.
package terraform
import rego.v1
deny contains msg if {
some rc in input.resource_changes
rc.type == "aws_security_group_rule"
rc.change.actions[_] != "delete"
rc.change.after.from_port == 22
rc.change.after.cidr_blocks[_] == "0.0.0.0/0"
msg := sprintf("%s: SSH (port 22) terbuka ke 0.0.0.0/0 — DILARANG", [rc.address])
}Ekspresi rc.change.after.cidr_blocks[_] == "0.0.0.0/0" memeriksa apakah daftar CIDR pada security group rule berisi 0.0.0.0/0. Jika ya, aturan langsung menolak plan tersebut.
Tip
Saat mengembangkan policy, gunakan opa test untuk menulis unit test dengan fixture JSON. Buat file _test.rego yang berisi kasus input bermasalah dan input bersih, lalu jalankan opa test policy/. Ini memastikan policy kalian sendiri tidak punya bug — karena policy yang salah konfigurasi bisa membuat semua plan "lolos" tanpa disadari.
Di ekosistem HashiCorp (Terraform Cloud / HCP Terraform), policy ditulis dalam bahasa Sentinel. Konsepnya: kita mendefinisikan rule yang bernilai true (lulus) atau false (gagal). Sentinel mengimpor data plan via tfplan/v2, dan Terraform Cloud menjalankannya di setiap plan.
Policy Sentinel untuk aturan yang sama (melarang S3 bucket publik):
import "tfplan/v2" as tfplan
# Nilai ACL yang melanggar kebijakan
public_acls = ["public-read", "public-read-write", "authenticated-read"]
# Ambil semua S3 bucket yang akan dibuat/diubah
s3_buckets = filter tfplan.resource_changes as _, rc {
rc.mode is "managed" and
rc.type is "aws_s3_bucket" and
rc.change.actions contains "create"
}
# Rule utama: tidak boleh ada bucket dengan ACL publik
main = rule {
all s3_buckets as _, rc {
rc.change.after.acl not in public_acls
}
}Perhatikan kemiripan strukturnya dengan Rego: sama-sama mengiterasi resource_changes, sama-sama memfilter berdasarkan type dan action. Bedanya, Sentinel menggunakan rule/all/filter yang lebih dekat ke gaya bahasa pemrograman umum.
Di Terraform Cloud, policy diorganisasikan dalam Policy Set yang bisa dikaitkan ke workspace atau seluruh organisasi. Setiap policy memiliki enforcement level:
| Enforcement Level | Perilaku |
|---|---|
soft-mandatory | Plan berjalan, tapi ditandai sebagai pelanggaran |
hard-mandatory | Plan dihentikan sampai pelanggaran diperbaiki |
advisory | Hanya sebagai informasi, tidak menghalangi apa pun |
Warning
Untuk aturan keamanan, gunakan selalu hard-mandatory. soft-mandatory hanya cocok untuk aturan yang bersifat imbauan (misal standar penamaan). Aturan seperti "dilarang bucket publik" atau "dilarang SSH ke 0.0.0.0/0" harus memaksa plan berhenti, bukan sekadar memberi peringatan.
package terraform
import rego.v1
PUBLIC_ACLS := {"public-read", "public-read-write", "authenticated-read"}
deny contains msg if {
some rc in input.resource_changes
rc.type == "aws_s3_bucket"
rc.change.actions[_] != "delete"
rc.change.after.acl in PUBLIC_ACLS
msg := sprintf("%s: S3 bucket publik dilarang.", [rc.address])
}Selain pipeline CI/CD sendiri, platform managed IaC yang kita bahas di episode 14 sudah menyediakan tempat untuk menegakkan PaC tanpa mengelola infrastruktur evaluasi:
| Platform | Engine Policy | Cara Kerja |
|---|---|---|
| Terraform Cloud / HCP | Sentinel | Policy Set + enforcement level (hard-mandatory, soft-mandatory) |
| Spacelift | OPA (Rego) | Policy Pack yang bisa dipasang per stack atau global |
| env0 | OPA / Rego | Policy as code di level organization |
| CI/CD mandiri (GitHub Actions, GitLab CI) | OPA CLI | Script gate setelah terraform show -json |
Pola yang sama berlaku di semua platform: plan dihasilkan → diekspor → dievaluasi → diblokir bila melanggar. Yang membedakan hanyalah tempat policy tinggal dan bagaimana hasilnya dilaporkan.
| Kesalahan | Gejala | Solusi |
|---|---|---|
| Mengevaluasi kode HCL, bukan plan JSON | Policy lolos padahal plan melanggar | Selalu eval hasil terraform show -json tfplan |
Tidak memfilter action delete | Resource yang dihapus ikut dinilai / salah diblokir | Tambahkan rc.change.actions[_] != "delete" |
deny kosong karena kesalahan input | Semua plan lolos diam-diam | Tulis fixture + opa test untuk tiap policy |
Semua rule dibuat soft-mandatory | Aturan keamanan diabaikan, apply tetap jalan | Set hard-mandatory untuk rule keamanan & cost |
| Policy global untuk semua environment | Rule dev menghambat prod atau sebaliknya | Pisahkan policy set per environment |
Satu policy tidak punya sprintf/pesan jelas | Engineer bingung kenapa plan ditolak | Sertakan rc.address dalam pesan pelanggaran |
| Tidak menandai variabel policy | Nilai threshold tersebar di dalam rule | Gunakan konstanta/parameters policy |
Pada episode 16 ini kita telah belajar bahwa code review manual tidak cukup untuk mengamankan infrastruktur yang semakin besar, sehingga dibutuhkan guard rail otomatis berupa Policy as Code. Kita memahami alur dasarnya: terraform plan → terraform show -json → evaluasi policy → apply hanya jika lolos. Kita juga sudah mempraktikkan tiga aturan penting dalam bahasa Rego (OPA): melarang S3 bucket publik, mewajibkan tags standar, dan melarang SSH port 22 terbuka ke 0.0.0.0/0. Terakhir, kita melihat bagaimana Sentinel bekerja natively di Terraform Cloud dengan enforcement level hard-mandatory/soft-mandatory.
Poin kunci yang perlu kalian bawa pulang:
hard-mandatory), bukan sekadar imbauan.opa test agar tidak ada celah yang lolos diam-diam.Dengan PaC terpasang, seluruh perubahan infrastruktur kini melewati pemeriksaan otomatis sebelum menyentuh produksi. Pertahanan sudah berlapis: secret aman, state aman, dan rencana perubahan terverifikasi.
Sekarang waktunya merangkai semua komponen yang sudah kita pelajari menjadi satu kesatuan utuh. Di episode 17 selanjutnya kita akan membahas Provisioning Complex Cloud Infrastructure Stack — studi kasus membangun stack multi-tier lengkap dari nol: VPC multi-AZ, cluster Kubernetes (EKS), database PostgreSQL (RDS), dan object storage terenkripsi. Pastikan tetap semangat!