Izinkan infrastruktur kalian menerbitkan sendiri kredensial database dinamis dan sertifikat PKI saat provisioning — melalui Terraform Vault Provider untuk deklarasi infrastruktur dan lookup plugin community.hashi_vault untuk konfigurasi server dengan Ansible.

Setelah di episode 18 sebelumnya kita membahas bagaimana pipeline CI/CD meminjam kredensial sementara dari Vault alih-alih menyimpan API key permanen, pada episode kali ini kita akan menerapkan filosofi yang sama pada lapisan yang lebih fundamental: infrastruktur itu sendiri. Kita akan membahas integrasi Vault dengan dua tool IaC paling populer di industri — Terraform dan Ansible.
Kenapa topik ini begitu penting? Karena selama ini, ada ironi besar di dunia IaC: kita mengotomatisasi pembuatan server, jaringan, dan database, tapi kredensial yang dibutuhkan infrastruktur itu sendiri masih di-hardcode atau disimpan di file konfigurasi. Contoh nyata: sebuah module Terraform yang membuat database PostgreSQL lalu menyimpan password admin-nya sebagai variabel plaintext di repository — dan lebih parah lagi, password itu ikut tersimpan selamanya di terraform state file. Atau playbook Ansible yang menulis kredensial statis ke /etc/myapp/config.yml di ribuan server tanpa mekanisme rotasi.
Dengan integrasi Vault, pola yang benar berubah total: infrastruktur meminta kredensialnya kepada Vault pada saat provisioning, dan kredensial itu lahir dengan masa hidup terbatas. Database yang dibuat Terraform bisa menggunakan dynamic credentials yang hangus dalam hitungan jam. Sertifikat TLS internal untuk aplikasi bisa diterbitkan on-demand dengan TTL 24 jam dan dirotasi otomatis. Dan server yang diatur Ansible tidak pernah menyimpan secret di disk — ia membaca langsung dari Vault setiap kali playbook dijalankan. Mari kita bahas satu per satu.
Sebelum masuk ke kode, mari kita bedah tiga masalah utama yang dipecahkan integrasi ini:
| Masalah | Tanpa Vault | Dengan Vault |
|---|---|---|
| Secret di state/log | Password terlihat plaintext di tfstate & log | Secret tidak pernah "menetap" — hanya di-fetch saat runtime |
| Rotasi | Manual, jarang dilakukan | Otomatis via dynamic secrets / PKI TTL |
| Kredensial menetap di disk server | Config file berisi secret permanen | Secret di-fetch per run, tidak tersimpan |
| Audit | Tidak tahu siapa baca secret kapan | Setiap read tercatat di audit log Vault |
Intinya: Vault memutus siklus "secret yang menetap". Terraform dan Ansible tetap menjadi source of truth untuk infrastruktur, tapi Vault menjadi source of truth untuk kredensialnya.
HashiCorp menyediakan provider resmi hashicorp/vault untuk Terraform. Ada tiga kemampuan utama yang akan kita pakai:
Provider Vault perlu tahu alamat server dan cara login. Di environment produksi, jangan pernah menaruh token di kode — gunakan blok auth_login dengan AppRole, atau biarkan provider membaca VAULT_TOKEN dari environment:
provider "vault" {
address = var.vault_addr
auth_login {
path = "auth/approle/login"
params = {
role_id = var.vault_role_id
secret_id = var.vault_secret_id
}
}
}
variable "vault_addr" {
default = "https://vault.example.com:8200"
}
variable "vault_role_id" {}
variable "vault_secret_id" {}Nilai vault_role_id dan vault_secret_id sebaiknya di-pass lewat variabel environment (TF_VAR_vault_role_id) atau diambil dari pipeline CI/CD yang sudah kita bangun di episode 18 — bukan ditulis di terraform.tfvars.
Tip
Provider Vault membaca VAULT_ADDR dan VAULT_TOKEN secara otomatis dari environment bila tersedia. Untuk skenario bootstrap yang paling murni, berikan provider akses via VAULT_TOKEN dari pipeline (token itu sendiri short-lived), atau gunakan auth_login dengan AppRole bila pipeline kalian sudah memegang RoleID/SecretID.
vault_kv_secret_v2 vs vault_generic_secretUntuk membaca secret dari KV secrets engine, pilihan data source bergantung pada versi engine:
data "vault_kv_secret_v2" "app" {
mount = "kv"
name = "ci/app"
}
# Akses nilai: data.vault_kv_secret_v2.app.data["DB_PASSWORD"]vault_kv_secret_v2 adalah pilihan yang disarankan karena mendukung versioning, metadata, dan rollback — fitur yang kita bahas di episode 4. Contoh penggunaan nyata: mengisi password aplikasi ke resource database yang dibuat Terraform:
data "vault_kv_secret_v2" "app" {
mount = "kv"
name = "postgres/app"
}
resource "aws_db_instance" "app_db" {
identifier = "app-db"
engine = "postgres"
instance_class = "db.t3.micro"
username = data.vault_kv_secret_v2.app.data["DB_USERNAME"]
password = data.vault_kv_secret_v2.app.data["DB_PASSWORD"]
skip_final_snapshot = true
}vault_database_credsIni adalah fitur yang membuat integrasi Vault+Terraform sangat kuat. Alih-alih membaca password statis dari KV, kita bisa meminta Vault untuk membuat kredensial database baru yang hangus otomatis:
data "vault_database_creds" "app" {
backend = "database"
role = "deploy-reader"
}
# Username & password dinamis, valid selama TTL role
resource "aws_db_instance" "app_db" {
...
username = data.vault_database_creds.app.username
password = data.vault_database_creds.app.password
}Setiap kali terraform apply dijalankan, Vault membuat user baru di PostgreSQL/MySQL dengan privilege sesuai role (misalnya deploy-reader), lalu menghapusnya otomatis setelah lease berakhir. Ini paradigma dari episode 5 yang diterapkan ke IaC: kredensial yang bocor = kredensial yang sudah tidak berlaku.
Warning
Hati-hati: data "vault_database_creds" menghasilkan kredensial baru setiap kali Terraform membaca data source. Karena itu nilai yang direferensikan akan memaksa replacement resource yang bergantung padanya (misalnya aws_db_instance). Jika kalian butuh kredensial yang stabil untuk jangka panjang, gunakan KV secret yang diisi oleh proses rotasi terpisah — atau pastikan provider menulis kredensial ke Vault KV setelah membuat resource (pola yang kita bahas di bagian "Menulis Data").
vault_pki_secretPola yang sama berlaku untuk sertifikat TLS internal (episode 7). Terraform bisa menerbitkan sertifikat dari Vault PKI dengan TTL pendek saat membuat resource yang membutuhkannya:
data "vault_pki_secret" "app_cert" {
backend = "pki_int"
name = "internal-app"
common_name = "api.internal.example.com"
alt_names = ["api.internal.example.com"]
ttl = "24h"
}
resource "aws_lb_listener" "app" {
load_balancer_arn = aws_lb.app.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01"
certificate_arn = data.vault_pki_secret.app_cert.cert_chain
}Karena TTL-nya pendek, Terraform akan secara otomatis menerbitkan sertifikat baru saat data source diperbarui (misalnya setiap plan/apply ulang). Ini memaksa rotasi berkala tanpa manusia — justru menghilangkan masalah "sertifikat kedaluwarsa di malam hari" yang kita bahas di episode 7.
Terraform juga bisa menjadi manager untuk Vault itu sendiri — membuat mount, menulis KV secret, dan mengonfigurasi secrets engine. Contoh: membuat mount kv (KV v2) dan menulis secret bootstrap aplikasi:
resource "vault_mount" "kv" {
path = "kv"
type = "kv-v2"
description = "KV v2 untuk secret aplikasi"
}
resource "vault_kv_secret_v2" "bootstrap" {
mount = vault_mount.kv.path
name = "postgres/app"
data_json = jsonencode({
DB_USERNAME = "app_user"
DB_PASSWORD = random_password.app_password.result
})
}
resource "random_password" "app_password" {
length = 24
special = true
}Perhatikan pola menarik di atas: password dibuat oleh random_password resource, ditulis ke Vault, dan tidak pernah muncul sebagai literal di kode. Resource lain (misalnya EC2 user-data atau secret untuk pipeline) tinggal membaca dari Vault. Inilah chicken-and-egg bootstrap yang rapi: Vault dikonfigurasi dan diisi oleh Terraform, kemudian Terraform lain membaca dari Vault.
Note
Karena vault_kv_secret_v2 menyimpan nilai secret di state Terraform juga (demi idempotensi), jangan pernah menaruh data rahasia yang sangat sensitif lewat resource ini tanpa menyadari konsekuensinya. Untuk key material atau secret yang harus "tidak pernah dilihat Terraform", gunakan API langsung atau Response Wrapping (episode 13).
community.hashi_vaultAnsible menangani lapisan berbeda dari Terraform: Terraform membuat infrastruktur, Ansible mengonfigurasi server yang sudah ada. Untuk mengambil secret dari Vault saat playbook berjalan, kita menggunakan collection resmi community.hashi_vault, yang menyediakan lookup plugin hashi_vault dan sejumlah module (vault_read, vault_write, dan lain-lain).
Pertama, pastikan collection terpasang:
ansible-galaxy collection install community.hashi_vaultLookup plugin dievaluasi saat task berjalan, jadi nilainya selalu segar dari Vault — bukan dari cache file. Contoh sederhana dengan autentikasi AppRole:
- name: Ambil secret dari Vault
hosts: app-servers
vars:
vault_url: "https://vault.example.com:8200"
vault_role_id: "{{ lookup('env', 'VAULT_ROLE_ID') }}"
vault_secret_id: "{{ lookup('env', 'VAULT_SECRET_ID') }}"
tasks:
- name: Baca kredensial database dari Vault
ansible.builtin.set_fact:
db_password: "{{ lookup('community.hashi_vault.hashi_vault',
'kv/data/postgres/app',
url=vault_url,
auth_method='approle',
role_id=vault_role_id,
secret_id=vault_secret_id,
mount_point='kv')['data']['data']['DB_PASSWORD'] }}"
- name: Tulis konfigurasi aplikasi
ansible.builtin.template:
src: app.config.j2
dest: /etc/myapp/config.yaml
mode: "0600"Bedah bagian penting:
kv/data/postgres/app — seperti pada episode sebelumnya, KV v2 butuh segmen data/. Parameter mount_point='kv' memberi tahu plugin di mount mana path itu berada.['data']['data']['DB_PASSWORD'] — lookup mengembalikan response JSON penuh dari Vault. Untuk KV v2, respons berstruktur data.data.<field>; jangan lupa lapisan ganda ini.role_id dan secret_id diambil dari environment — bukan di-hardcode di playbook atau di grup inventory. Ini mencegah kredensial ikut tercommit ke repository Ansible.vault_readUntuk kejelasan dan kemampuan pengendalian error, gunakan module vault_read daripada lookup. Module bisa di-register dan hasilnya direferensikan, plus lebih mudah di-debug:
- name: Baca secret dari Vault (module)
community.hashi_vault.vault_read:
url: "https://vault.example.com:8200"
auth_method: approle
role_id: "{{ vault_role_id }}"
secret_id: "{{ vault_secret_id }}"
path: kv/data/postgres/app
register: vault_result
no_log: true
- name: Simpan nilai sebagai fact
ansible.builtin.set_fact:
db_username: "{{ vault_result.data.data.DB_USERNAME }}"
db_password: "{{ vault_result.data.data.DB_PASSWORD }}"
- name: Jalankan aplikasi dengan secret dari Vault
ansible.builtin.systemd:
name: myapp
state: restarted
environment:
DB_USERNAME: "{{ db_username }}"
DB_PASSWORD: "{{ db_password }}"Dua detail krusial di sini:
no_log: true pada task yang berinteraksi dengan secret — memastikan nilai rahasia tidak pernah muncul di output Ansible meskipun kalian menjalankan ansible-playbook -vvv.environment: pada task tertentu saja, tidak ditulis permanen ke file config — kecuali memang dibutuhkan oleh proses yang berjalan lama, dan itupun sebaiknya dengan permission yang ketat.Important
Jika aplikasi di server membutuhkan secret secara terus-menerus (bukan hanya saat provisioning), jangan menulis secret ke file konfigurasi permanen — pakailah Vault Agent yang kita bahas di episode 14 & 15 untuk merender template secret secara dinamis dan memperbarui file secara otomatis saat secret dirotasi. Ansible sebaiknya hanya dipakai untuk konfigurasi statis dan bootstrap.
Kedua tool melengkapi satu sama lain, bukan bersaing. Berikut perbandingannya:
| Aspek | Terraform | Ansible |
|---|---|---|
| Peran utama | Provisioning infrastruktur (deklaratif, stateful) | Configuration management server (imperatif) |
| Plugin/Collection | Provider hashicorp/vault | Collection community.hashi_vault |
| Mengambil secret | Data sources (vault_kv_secret_v2, vault_database_creds) | Lookup plugin / module vault_read |
| Menulis secret | Resources (vault_kv_secret_v2, vault_mount) | Module vault_write |
| Dynamic DB / PKI | vault_database_creds, vault_pki_secret | Lookup langsung ke path yang sama |
| Risiko utama | Secret ikut tersimpan di tfstate | Secret bocor ke log/output bila tanpa no_log |
| Idempotensi | Ya, dibandingkan state | Ya, per task |
Pola terbaik di produksi adalah kombinasi keduanya: Terraform membuat infrastruktur dan mengisi Vault (bootstrap), Ansible mengonfigurasi server dengan membaca dari Vault, dan Vault Agent menjaga secret tetap segar di masa hidup server.
Terakhir, mari kita bahas jebakan yang paling sering membuat tim jatuh:
| Kesalahan | Gejala | Solusi |
|---|---|---|
| RoleID/SecretID di-commit | Kredensial AppRole bocor ke repo | Ambil dari env var / pipeline (episode 18); rotasi segera |
| Secret di tfstate | Password terlihat di state file & plan output | Remote state terenkripsi; pertimbangkan dynamic creds |
Path KV v2 tanpa data/ | permission denied atau not found | Gunakan kv/data/... + mount_point yang benar |
Lupa no_log: true di Ansible | Secret tampil di output -vvv | Tambahkan no_log: true pada task sensitif |
vault_database_creds dipakai untuk resource jangka panjang | Resource terus replaced karena kredensial baru | Tulis kredensial ke KV setelah membuat resource, atau gunakan static role |
| Pilih auth method asal-asalan | Token abadi dipakai provider Terraform | Pakai auth_login AppRole / short-lived token dari pipeline |
| Vault down saat provisioning | apply/playbook gagal total | Retry, circuit breaker, dan secrets engine HA (episode 20) |
| Menyimpan secret di file config permanen | Secret menetap di disk server | Vault Agent + template sink (episode 14-15) |
Caution
Banyak yang mengira sensitive = true pada variabel/output Terraform menyembunyikan secret dari state — itu salah. Atribut tersebut hanya menyamarkan nilai di console output; state file tetap menyimpan nilai aslinya dalam bentuk terenkripsi-at-rest namun decryptable. Jadi jangan pernah mengandalkannya sebagai proteksi. Solusi nyata: remote state dengan enkripsi backend yang ketat, dan gunakan kredensial dinamis (dynamic) yang pendek umurnya sehingga apa pun yang bocor dari state menjadi tidak berguna.
Pada episode 19 ini kita telah membahas bagaimana Terraform dan Ansible — dua tulang punggung IaC — bisa berintegrasi dengan Vault untuk memutus siklus secret yang menetap. Dengan Terraform Vault Provider, infrastruktur bisa menerbitkan kredensial database dinamis dan sertifikat PKI on-demand saat provisioning, sekaligus menulis dan mengonfigurasi data di Vault secara deklaratif. Dengan Ansible, server yang sudah ada bisa membaca secret langsung dari Vault lewat lookup plugin atau module vault_read, tanpa pernah menyimpan kredensial di disk.
Poin kunci yang perlu kalian bawa pulang:
vault_kv_secret_v2, vault_database_creds, vault_pki_secret) untuk membaca, dan resources (vault_mount, vault_kv_secret_v2) untuk menulis ke Vault.community.hashi_vault dengan lookup hashi_vault atau module vault_read; selalu gunakan no_log: true pada task sensitif.tfstate bukanlah rahasia — gunakan remote state terenkripsi dan prioritaskan kredensial dinamis.Kita sudah membahas integrasi dengan aplikasi, Kubernetes, CI/CD, dan IaC. Di episode 20 selanjutnya, kita akan naik ke level arsitektur: Vault High Availability (HA) Cluster dengan Raft Integrated Storage — bagaimana membangun cluster 3-node atau 5-node yang tidak punya titik tunggal kegagalan, lengkap dengan vault operator raft join dan pemilihan leader. Pastikan tetap semangat!