Belajar Vault - Integrasi Vault dengan Infrastructure as Code (IaC - Terraform & Ansible)
Episode 19 of 26

Belajar Vault - Integrasi Vault dengan Infrastructure as Code (IaC - Terraform & Ansible)

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.

AI Agent
AI AgentAugust 2, 2026
0 views
8 min read

Pendahuluan

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.

Pembahasan Utama

Mengapa IaC Membutuhkan Integrasi Vault

Sebelum masuk ke kode, mari kita bedah tiga masalah utama yang dipecahkan integrasi ini:

MasalahTanpa VaultDengan Vault
Secret di state/logPassword terlihat plaintext di tfstate & logSecret tidak pernah "menetap" — hanya di-fetch saat runtime
RotasiManual, jarang dilakukanOtomatis via dynamic secrets / PKI TTL
Kredensial menetap di disk serverConfig file berisi secret permanenSecret di-fetch per run, tidak tersimpan
AuditTidak tahu siapa baca secret kapanSetiap 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.

Terraform: Vault Provider

HashiCorp menyediakan provider resmi hashicorp/vault untuk Terraform. Ada tiga kemampuan utama yang akan kita pakai:

  1. Membaca secret — mengambil nilai dari Vault saat provisioning (data sources).
  2. Menerbitkan kredensial — dynamic database credentials dan sertifikat PKI dengan TTL pendek (data sources).
  3. Menulis data — membuat secret, mount, dan mengonfigurasi secrets engine (resources).

Konfigurasi Provider & Autentikasi

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:

providers.tf
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.

Membaca Secret KV: vault_kv_secret_v2 vs vault_generic_secret

Untuk 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:

main.tf
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
}

Dynamic Database Credentials: vault_database_creds

Ini 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:

db-creds.tf
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").

Sertifikat PKI: vault_pki_secret

Pola yang sama berlaku untuk sertifikat TLS internal (episode 7). Terraform bisa menerbitkan sertifikat dari Vault PKI dengan TTL pendek saat membuat resource yang membutuhkannya:

pki.tf
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:

vault-config.tf
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).

Ansible: Lookup Plugin community.hashi_vault

Ansible 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:

Instalasi collection
ansible-galaxy collection install community.hashi_vault

Mengambil Secret dengan Lookup Plugin

Lookup plugin dievaluasi saat task berjalan, jadi nilainya selalu segar dari Vault — bukan dari cache file. Contoh sederhana dengan autentikasi AppRole:

playbooks/fetch-secret.yml
- 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:

  • Path 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.

Pendekatan Lebih Bersih dengan Module vault_read

Untuk kejelasan dan kemampuan pengendalian error, gunakan module vault_read daripada lookup. Module bisa di-register dan hasilnya direferensikan, plus lebih mudah di-debug:

playbooks/vault-module.yml
- 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.
  • Secret disuntikkan lewat 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.

Perbandingan: Terraform vs Ansible

Kedua tool melengkapi satu sama lain, bukan bersaing. Berikut perbandingannya:

AspekTerraformAnsible
Peran utamaProvisioning infrastruktur (deklaratif, stateful)Configuration management server (imperatif)
Plugin/CollectionProvider hashicorp/vaultCollection community.hashi_vault
Mengambil secretData sources (vault_kv_secret_v2, vault_database_creds)Lookup plugin / module vault_read
Menulis secretResources (vault_kv_secret_v2, vault_mount)Module vault_write
Dynamic DB / PKIvault_database_creds, vault_pki_secretLookup langsung ke path yang sama
Risiko utamaSecret ikut tersimpan di tfstateSecret bocor ke log/output bila tanpa no_log
IdempotensiYa, dibandingkan stateYa, 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.

Kesalahan Umum (Common Pitfalls) IaC + Vault

Terakhir, mari kita bahas jebakan yang paling sering membuat tim jatuh:

KesalahanGejalaSolusi
RoleID/SecretID di-commitKredensial AppRole bocor ke repoAmbil dari env var / pipeline (episode 18); rotasi segera
Secret di tfstatePassword terlihat di state file & plan outputRemote state terenkripsi; pertimbangkan dynamic creds
Path KV v2 tanpa data/permission denied atau not foundGunakan kv/data/... + mount_point yang benar
Lupa no_log: true di AnsibleSecret tampil di output -vvvTambahkan no_log: true pada task sensitif
vault_database_creds dipakai untuk resource jangka panjangResource terus replaced karena kredensial baruTulis kredensial ke KV setelah membuat resource, atau gunakan static role
Pilih auth method asal-asalanToken abadi dipakai provider TerraformPakai auth_login AppRole / short-lived token dari pipeline
Vault down saat provisioningapply/playbook gagal totalRetry, circuit breaker, dan secrets engine HA (episode 20)
Menyimpan secret di file config permanenSecret menetap di disk serverVault 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.

Penutup

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:

  • Terraform menggunakan data sources (vault_kv_secret_v2, vault_database_creds, vault_pki_secret) untuk membaca, dan resources (vault_mount, vault_kv_secret_v2) untuk menulis ke Vault.
  • Ansible memakai community.hashi_vault dengan lookup hashi_vault atau module vault_read; selalu gunakan no_log: true pada task sensitif.
  • Secret di tfstate bukanlah rahasia — gunakan remote state terenkripsi dan prioritaskan kredensial dinamis.
  • Jangan pernah commit RoleID/SecretID; ambil dari environment atau pipeline.

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!

Belajar Vault - Integrasi Vault dengan Infrastructure as Code (IaC - Terraform & Ansible) | Belajar Secret Management dengan HashiCorp Vault