Otentikasikan Pod Kubernetes ke Vault lewat Kubernetes Auth Method, lalu otomatiskan penyediaan secret dengan Vault Agent Sidecar Injector dan Vault Secrets Operator (VSO) yang menyinkronkan secret ke native Kubernetes Secrets.

Setelah di episode 16 sebelumnya kita membahas integrasi langsung aplikasi ke Vault lewat SDK — lengkap dengan pola retry, renewal, dan fallback — pada episode kali ini kita akan membahas integrasi yang paling banyak dipakai di dunia cloud-native: Vault dengan Kubernetes.
Mengapa topik ini penting? Hampir semua perusahaan yang memakai Vault di produksi menjalankan aplikasinya di Kubernetes. Masalahnya: cara tradisional memberikan secret ke Pod — yaitu menulis nilai rahasia langsung ke Secret Kubernetes — masih menyimpan secret dalam bentuk statis di etcd, mudah terbaca siapa saja yang punya akses, dan tidak pernah terotentikasi ke Vault. Di episode ini kita akan menyelesaikan masalah tersebut dengan tiga mekanisme utama: Kubernetes Auth Method untuk otentikasi, Vault Agent Sidecar Injector untuk injeksi secret otomatis ke Pod, dan Vault Secrets Operator (VSO) untuk sinkronisasi secret ke native Kubernetes Secret. Mari kita bedah satu per satu.
auth/kubernetes)Kubernetes Auth Method adalah cara Pod berotentikasi ke Vault tanpa perlu menyimpan token atau secret ID. Konsepnya: setiap Pod di Kubernetes otomatis memiliki ServiceAccount dan setiap ServiceAccount memiliki JWT token yang melekat. JWT inilah yang menjadi "bukti identitas" Pod ke Vault.
Alur autentikasinya seperti ini:
Pod (SA: web-sa)
│ 1. kirim ServiceAccount JWT ke Vault: auth/kubernetes/login
▼
Vault ──2. verify JWT──▶ Kubernetes API (TokenReview)
▲ │ 3. valid / invalid + claims SA & namespace
└──4. issue Vault token ───┘Vault tidak menebak-nebak apakah JWT itu asli — ia meminta Kubernetes API Server untuk memverifikasi token lewat endpoint TokenReview. Dengan begitu, Vault percaya penuh bahwa token tersebut memang milik ServiceAccount tertentu di namespace tertentu.
Langkah pertama mengaktifkan auth method dan menghubungkannya ke cluster. Vault membutuhkan tiga hal: alamat Kubernetes API, CA certificate cluster, dan JWT dari service account yang berhak melakukan TokenReview:
vault auth enable kubernetes
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc" \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crtImportant
Perintah konfigurasi di atas dijalankan dari dalam cluster (misal via kubectl exec atau sebuah Job bootstrap), sehingga token_reviewer_jwt diambil dari service account aktif. Di setup yang lebih aman, token_reviewer_jwt di-generate sekali lalu disimpan sebagai secret — atau gunakan tooling seperti vault-kubernetes-auth helper yang dijalankan saat cluster pertama kali dibangun.
Setelah konfigurasi, kita buat role yang mengikat ServiceAccount tertentu dengan policy Vault. Ini adalah titik kontrol akses yang krusial: hanya Pod dengan SA bernama web-sa di namespace default yang boleh login lewat role web:
vault write auth/kubernetes/role/web \
bound_service_account_names="web-sa" \
bound_service_account_namespaces="default" \
policies="web-app" \
ttl="1h"Perhatikan juga policy Vault yang dilekatkan ke role tersebut harus mengizinkan akses ke path yang dibutuhkan aplikasi:
path "secret/data/myapp" {
capabilities = ["read"]
}Warning
Selalu tentukan bound_service_account_names dan bound_service_account_namespaces. Role tanpa binding ini sama seperti pintu tanpa kunci — semua ServiceAccount di cluster bisa login memakai role tersebut dan mewarisi policy-nya. Ini salah satu misconfiguration paling berbahaya di produksi.
Kubernetes Auth Method sudah membuat Pod bisa berotentikasi, tapi kita masih perlu cara men-deliver secret ke dalam Pod. Di sinilah Vault Agent Sidecar Injector berperan: sebuah mutating admission webhook yang otomatis menambahkan container sidecar Vault Agent ke dalam Pod berdasarkan annotations.
Cara kerjanya:
vault.hashicorp.com/....vault-agent dan volume memori /vault/secrets./vault/secrets/config.Sebelum deployment berjalan, ada dua persiapan wajib. Pertama, buat ServiceAccount web-sa — identitas yang akan membuktikan diri ke Vault:
apiVersion: v1
kind: ServiceAccount
metadata:
name: web-sa
namespace: defaultKedua, pasang Vault Agent Injector di cluster. Cara paling umum adalah via Helm chart hashicorp/vault dengan sub-chart injector (atau install mandiri chart injector):
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault \
--set injector.enabled=true \
--set injector.logLevel=infokubectl get mutatingwebhookconfiguration
kubectl get pods -n vaultTip
Injector hanya memproses Pod yang memiliki annotation vault.hashicorp.com/agent-inject: "true" — Pod lain di cluster tidak terpengaruh. Ini keuntungan desain besar: kalian bisa mengaktifkan Vault per-aplikasi secara bertahap tanpa mengubah seluruh cluster.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "web"
vault.hashicorp.com/agent-inject-secret-config: "secret/data/myapp"
vault.hashicorp.com/agent-inject-template-config: |
{{- with secret "secret/data/myapp" }}
DB_HOST={{ .Data.data.DB_HOST }}
DB_PASSWORD={{ .Data.data.DB_PASSWORD }}
{{- end }}
spec:
serviceAccountName: web-sa
containers:
- name: myapp
image: myapp:1.0.0
command: ["sh", "-c", "sleep infinity"]Penjelasan annotation kunci:
vault.hashicorp.com/agent-inject: "true" — penanda bahwa injector harus memproses Pod ini.vault.hashicorp.com/role: "web" — role K8s auth yang dipakai sidecar untuk login (harus match dengan bound_service_account_names).vault.hashicorp.com/agent-inject-secret-config — nama file tujuan (config) dipisahkan dari path secret (secret/data/myapp) oleh -. Hasilnya secret di-render ke /vault/secrets/config.agent-inject-template-config — template custom yang mengontrol isi file. Tanpa template ini, injector memakai template bawaan (format YAML) yang sering kurang sesuai kebutuhan.Setelah Pod berjalan, mari kita lihat apa yang dihasilkan sidecar:
kubectl exec deploy/myapp -- cat /vault/secrets/configDB_HOST=postgres.internal.local
DB_PASSWORD=a9f3!sVx#2kQTip
Volume /vault/secrets adalah emptyDir bertipe memory (medium: Memory) — file secret disimpan di RAM, bukan di disk, sehingga tidak pernah menulis rahasia ke persistent storage node. Inilah salah satu alasan sidecar injector lebih aman daripada Secret statis biasa.
Ini keunggulan utama injector: jika secret di Vault diubah, sidecar agent mendeteksi perubahannya (via polling ke Vault) dan merender ulang file secara otomatis. Aplikasi yang membaca file setiap kali membutuhkan koneksi akan otomatis memakai nilai terbaru. Tapi perhatikan — aplikasi yang sudah menyimpan nilai lama di memori (misal koneksi pool yang dibangun saat startup) tidak akan terpengaruh sampai ia membaca ulang file atau di-restart. Untuk aplikasi yang perlu merespons rotasi cepat, tambahkan reloader seperti restart otomatis saat file berubah.
Sidecar injector menyelesaikan masalah injection, tapi ada kasus lain: aplikasi (atau tooling seperti Helm chart, ingress, dsb.) memang membutuhkan native Kubernetes Secret — bukan file di dalam Pod. Di sinilah Vault Secrets Operator (VSO) masuk: operator ini menyinkronkan secret dari Vault ke native Secret Kubernetes secara kontinu, lengkap dengan rotasi otomatis.
Arsitekturnya terdiri dari beberapa CRD (Custom Resource Definition):
VaultConnection — alamat Vault yang akan dihubungi.VaultAuth — kredensial otentikasi (misal via Kubernetes Auth Method).VaultStaticSecret — sumber secret dan tujuan sinkronisasi (destination).VaultDynamicSecret untuk dynamic credentials, dan VaultPKISecret untuk sertifikat — kita fokus ke static dulu.)apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
name: vault
namespace: default
spec:
address: http://vault:8200
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: default
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: web
serviceAccount: web-sa
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: myapp-secret
namespace: default
spec:
vaultAuthRef: vault-auth
mount: secret
type: kv-v2
path: myapp
refreshAfter: 1h
destination:
name: myapp-secrets
create: true
overwrite: truePenjelasan bagian penting:
vaultAuthRef menunjuk ke VaultAuth yang memakai Kubernetes Auth dengan role web dan service account web-sa.mount: secret + type: kv-v2 + path: myapp berarti sumbernya adalah secret/data/myapp pada secrets engine KV v2.refreshAfter: 1h menentukan interval sinkronisasi — setiap jam operator membandingkan versi secret di Vault dan memperbarui Secret Kubernetes jika ada perubahan.destination mendefinisikan Secret bernama myapp-secrets yang akan dibuat/diperbarui.VSO juga mendukung dynamic credentials lewat CRD VaultDynamicSecret. Contohnya, mengambil kredensial database sementara dari secrets engine database dan menyinkronkannya ke Secret:
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
name: db-creds
namespace: default
spec:
vaultAuthRef: vault-auth
mount: database
role: web-role
destination:
name: db-creds
create: true
revoke: true
renewalPercent: 60Important
Pada VaultDynamicSecret, atribut revoke: true memastikan kredensial dinamis di-revoke ketika Secret Kubernetes dihapus atau CRD dihapus — menjaga prinsip short-lived credentials. Sementara renewalPercent: 60 membuat operator memperbarui lease saat sudah mencapai 60% dari TTL-nya, sehingga aplikasi tidak pernah memakai kredensial yang hampir kadaluarsa.
Hasilnya, sebuah Secret native Kubernetes siap dipakai aplikasi:
kubectl get secret myapp-secrets -o jsonpath='{.data}'{
"DB_HOST": "cG9zdGdyZXMuaW50ZXJuYWwubG9jYWw=",
"DB_PASSWORD": "YTlmMyFzVngjMmtR"
}Note
VSO menangani rotasi dengan cerdas: saat secret di Vault berubah (versi baru di KV v2), operator otomatis memperbarui Secret Kubernetes dan — jika diaktifkan via refreshAfter/events — memicu rollout deployment yang mengonsumsi secret tersebut. Ini menghilangkan pekerjaan manual "update secret, restart pod".
| Aspek | Agent Injector (Sidecar) | Vault Secrets Operator (VSO) |
|---|---|---|
| Bentuk deliverable | File di /vault/secrets/... dalam Pod | Native Kubernetes Secret |
| Secret disimpan di etcd | Tidak (RAM emptyDir) | Ya (sebagai Secret biasa) |
| Akses aplikasi | Baca file lokal | Standard K8s secret mount / env |
| Ideal untuk | Aplikasi yang butuh secret sebagai file/config | Helm chart, ingress TLS, tooling yang butuh Secret |
| Keterlibatan Vault API | Sidecar menangani | Operator menangani (transparan) |
| Rotasi | Render ulang file; aplikasi perlu reload | Update Secret; bisa trigger rollout |
Warning
Karena VSO menyimpan secret sebagai Secret Kubernetes biasa, semua nilai pada akhirnya tersimpan di etcd (dalam bentuk base64, bukan terenkripsi kuat). Jika regulasi kalian mensyaratkan secret tidak pernah mengendap di penyimpanan, gunakan Sidecar Injector. VSO lebih cocok untuk tooling yang memang membutuhkan objek Secret native.
| Kesalahan | Gejala | Solusi |
|---|---|---|
Role tanpa bound_service_account_* | Semua SA bisa login — celah keamanan | Selalu tentukan SA name + namespace |
| Annotation template salah indentasi (block scalar ` | `) | File hasil render tidak valid / kosong |
| Policy Vault tidak mengizinkan path secret | Sidecar error permission denied | Pastikan policy role web mencakup secret/data/myapp |
| SA tidak dibuat atau namespace salah | Pod gagal login: service account not found | Verifikasi serviceAccountName & buat SA via RBAC |
token_reviewer_jwt dari SA tanpa izin TokenReview | Login selalu ditolak Vault | Pastikan SA bootstrap punya ClusterRole system:auth-delegator |
Lupa serviceAccountName di pod spec | Injector memakai SA default — gagal bound | Eksplisit set serviceAccountName |
Path KV v2 salah (secret/myapp vs secret/data/myapp) | Secret tidak ditemukan | Untuk kv-v2 selalu pakai secret/data/... |
| Mengira VSO sekaligus menyediakan dynamic credentials | Hanya VaultStaticSecret yang ter-sync | Gunakan VaultDynamicSecret untuk creds dinamis |
| Secret native dari VSO di-mount sebagai env | Nilai tak ter-update sampai restart pod | Gunakan volume mount + reloader bila butuh rotasi live |
Caution
Urutan troubleshooting yang benar saat sidecar gagal: (1) cek kubectl logs <pod> vault-agent untuk error login/policy, (2) verifikasi role binding SA↔namespace↔policy, (3) uji akses path dengan vault read secret/data/myapp menggunakan token dari role tersebut. Jangan langsung mengganti konfigurasi tanpa tahu di layer mana masalahnya.
Pada episode 17 ini kita telah membahas integrasi Vault dengan Kubernetes secara menyeluruh: Kubernetes Auth Method yang memverifikasi ServiceAccount JWT melalui TokenReview API, Vault Agent Sidecar Injector yang otomatis menyuntikkan sidecar untuk merender secret ke /vault/secrets/config di dalam RAM, dan Vault Secrets Operator (VSO) yang menyinkronkan secret ke native Kubernetes Secret dengan rotasi otomatis.
Inti pelajaran episode ini: identitas Pod di Kubernetes (ServiceAccount) adalah kunci otentikasi yang kuat ke Vault — tidak perlu lagi menyimpan token di mana pun. Pilih injector ketika kalian ingin secret berakhir di memori Pod, pilih VSO ketika tooling kalian membutuhkan objek Secret native.
Di episode 18 selanjutnya kita akan berpindah ke dunia otomasi pengembangan: integrasi Vault di CI/CD pipeline (GitHub Actions & GitLab CI) — bagaimana pipeline mengambil kredensial sementara dari Vault tanpa menyimpan secret statis di repository. Pastikan tetap semangat!