A

Blog Post

Workload Identity di Kubernetes dengan SPIFFE dan SPIRE untuk mTLS Internal

Panduan teknis untuk menerapkan workload identity berbasis SPIFFE dan SPIRE di Kubernetes agar service-to-service trust tidak lagi bergantung pada shared secret.

5 min read
Workload Identity di Kubernetes dengan SPIFFE dan SPIRE untuk mTLS Internal

TL;DR

  • Workload identity memindahkan trust dari static secret ke identity yang diterbitkan dinamis untuk tiap workload.
  • SPIFFE mendefinisikan identity format, sedangkan SPIRE menjadi control plane yang menerbitkan SVID untuk workload.
  • Implementasi yang sehat butuh selector yang disiplin, distribusi SVID yang aman, dan verifikasi identity pada jalur komunikasi service-to-service.

Pendahuluan

Banyak cluster Kubernetes masih mengandalkan shared secret, token statis, atau mTLS sertifikat yang dikelola manual untuk komunikasi internal. Pola ini cepat terasa tidak sehat ketika jumlah service bertambah. Secret tersebar di banyak namespace, rotasi menjadi menyakitkan, dan sulit membuktikan service mana sebenarnya berbicara dengan service lain. Inilah konteks di mana workload identity menjadi jauh lebih menarik daripada sekadar “sertifikat internal”.

SPIFFE dan SPIRE memberi jalur yang lebih rapi. SPIFFE mendefinisikan identitas workload dalam bentuk URI yang konsisten. SPIRE bertugas menerbitkan dan mengelola identitas itu berdasarkan selector yang berasal dari platform. Di Kubernetes, selector bisa didasarkan pada namespace, service account, atau label workload. Hasil akhirnya adalah service memiliki identity yang lahir dari runtime environment, bukan dari file secret yang disalin ke mana-mana.

Tutorial ini fokus pada baseline mTLS internal menggunakan SPIRE di Kubernetes. Targetnya bukan service mesh penuh, melainkan fondasi identity yang bisa dipakai untuk service-to-service trust dan integrasi berikutnya.

Problem di Production

Rahasia terbesar di banyak environment bukan serangan canggih, tetapi akumulasi credential yang tidak pernah benar-benar hilang. Service account token digunakan di luar konteksnya, sertifikat dipakai lebih lama dari yang seharusnya, dan secret yang sama tersebar ke banyak pipeline. Begitu satu namespace kompromi, blast radius membesar karena trust model didasarkan pada “siapa yang punya file ini”, bukan “workload apa yang sedang hidup di sini”.

Masalah kedua adalah auditability. Saat incident terjadi, tim ingin tahu siapa yang memanggil apa. Jika semua service memakai secret bersama atau internal CA generik, jawaban itu sering terlalu kabur.

Prerequisites

  • Cluster Kubernetes 1.29+.
  • kubectl, helm, dan akses admin.
  • Namespace khusus identitas, misalnya spire-system.
  • Node Linux yang bisa menjalankan daemon SPIRE agent.
  • Dua sample namespace untuk uji, misalnya identity-demo dan identity-client.
  • Basic familiarity dengan TLS dan sertifikat X.509.

Architecture / Context

Arsitektur yang dipakai terdiri dari SPIRE server, SPIRE agent di tiap node, dan workload yang menerima SVID lewat CSI volume. SPIRE server menyimpan trust domain dan registration entry. SPIRE agent berjalan dekat node dan melakukan attestation terhadap workload berdasarkan selector Kubernetes. Ketika pod yang cocok muncul, agent menyajikan X.509 SVID yang bisa dipakai service untuk mTLS.

Trust domain dalam tutorial ini menggunakan example.org. Dalam praktik production, pilih trust domain yang konsisten dan tidak ambigu, karena identity ini akan hidup lama dalam integrasi platform.

Concept diagram

Step-by-Step Implementation

Step 1: Buat namespace dan install SPIRE

kubectl create namespace spire-system
helm repo add spiffe https://spiffe.github.io/helm-charts-hardened/
helm repo update

Buat values sederhana:

global:
  spire:
    trustDomain: example.org

spire-server:
  enabled: true

spire-agent:
  enabled: true

spire-csi-driver:
  enabled: true

Install:

helm upgrade --install spire spiffe/spire \
  -n spire-system \
  -f spire-values.yaml

Step 2: Pastikan server dan agent sehat

kubectl -n spire-system get pods

Minimal harus ada SPIRE server, SPIRE agent DaemonSet, dan CSI driver.

Step 3: Buat namespace demo dan service account

kubectl create namespace identity-demo
kubectl create namespace identity-client
kubectl -n identity-demo create serviceaccount mtls-server
kubectl -n identity-client create serviceaccount mtls-client

Step 4: Buat registration entry untuk server dan client

Cari pod SPIRE server:

SPIRE_SERVER_POD=$(kubectl -n spire-system get pod -l app.kubernetes.io/name=spire-server -o jsonpath='{.items[0].metadata.name}')

Tambahkan entry:

kubectl -n spire-system exec "$SPIRE_SERVER_POD" -- /opt/spire/bin/spire-server entry create \
  -spiffeID spiffe://example.org/ns/identity-demo/sa/mtls-server \
  -selector k8s:ns:identity-demo \
  -selector k8s:sa:mtls-server

kubectl -n spire-system exec "$SPIRE_SERVER_POD" -- /opt/spire/bin/spire-server entry create \
  -spiffeID spiffe://example.org/ns/identity-client/sa/mtls-client \
  -selector k8s:ns:identity-client \
  -selector k8s:sa:mtls-client

Selector harus cukup ketat. Jangan gunakan selector yang terlalu umum hanya demi mempercepat uji coba.

Step 5: Deploy server yang menerima SVID

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mtls-server
  namespace: identity-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mtls-server
  template:
    metadata:
      labels:
        app: mtls-server
    spec:
      serviceAccountName: mtls-server
      containers:
        - name: server
          image: cgr.dev/chainguard/nginx:latest
          command: ["/bin/sh","-c"]
          args:
            - |
              ls -lah /spiffe
              sleep 3600
          volumeMounts:
            - name: spiffe-workload-api
              mountPath: /spiffe
              readOnly: true
      volumes:
        - name: spiffe-workload-api
          csi:
            driver: csi.spiffe.io
            readOnly: true

Apply:

kubectl apply -f server.yaml

Untuk aplikasi nyata, SVID dipakai oleh Envoy, gRPC, atau library TLS yang bisa memuat cert dan key dari path volume atau Workload API.

Step 6: Deploy client workload

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mtls-client
  namespace: identity-client
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mtls-client
  template:
    metadata:
      labels:
        app: mtls-client
    spec:
      serviceAccountName: mtls-client
      containers:
        - name: client
          image: cgr.dev/chainguard/wolfi-base
          command: ["/bin/sh","-c"]
          args:
            - "sleep 3600"
          volumeMounts:
            - name: spiffe-workload-api
              mountPath: /spiffe
              readOnly: true
      volumes:
        - name: spiffe-workload-api
          csi:
            driver: csi.spiffe.io
            readOnly: true

Step 7: Verifikasi SVID muncul di pod

kubectl -n identity-demo exec deploy/mtls-server -- ls -lah /spiffe
kubectl -n identity-client exec deploy/mtls-client -- ls -lah /spiffe

Jika aplikasi Anda sudah siap memakai X.509 SVID, mount path ini bisa langsung diintegrasikan ke TLS config.

Step 8: Tampilkan registration entry dan trust bundle

kubectl -n spire-system exec "$SPIRE_SERVER_POD" -- /opt/spire/bin/spire-server entry show
kubectl -n spire-system exec "$SPIRE_SERVER_POD" -- /opt/spire/bin/spire-server bundle show

Step 9: Integrasikan ke proxy atau app runtime

Contoh jika Envoy dipakai untuk mTLS internal:

common_tls_context:
  tls_certificate_sds_secret_configs:
    - name: "spiffe://example.org/ns/identity-demo/sa/mtls-server"
  validation_context_sds_secret_config:
    name: "ROOTCA"

Intinya bukan pada Envoy semata, tetapi pada verifikasi identity SPIFFE URI di sisi penerima.

Architecture flow

Verification

Cek semua komponen SPIRE:

kubectl -n spire-system get pods
kubectl -n spire-system get ds

Lihat apakah entry sudah benar:

kubectl -n spire-system exec "$SPIRE_SERVER_POD" -- /opt/spire/bin/spire-server entry show

Validasi pod menerima volume SPIFFE:

kubectl -n identity-demo exec deploy/mtls-server -- ls -lah /spiffe
kubectl -n identity-client exec deploy/mtls-client -- ls -lah /spiffe

Hasil yang benar:

  • SPIRE server dan agent sehat
  • entry registration cocok dengan namespace dan service account
  • pod target menerima material identity yang sesuai

Troubleshooting

Jika volume CSI tidak muncul, cek CSI driver:

kubectl -n spire-system get pods
kubectl -n spire-system logs ds/spire-spire-agent

Jika SVID tidak diterbitkan, kemungkinan selector salah:

kubectl -n spire-system exec "$SPIRE_SERVER_POD" -- /opt/spire/bin/spire-server entry show
kubectl -n identity-demo get sa

Jika pod gagal start karena mount, cek apakah CSI driver terpasang di semua node yang menampung workload. Masalah lain yang sering muncul adalah trust domain salah sejak awal atau aplikasi tidak benar-benar membaca certificate path yang diberikan.

Production Notes

Workload identity sebaiknya diperkenalkan bertahap. Mulai dari service internal yang paling sensitif atau paling sering menjadi dependency lintas namespace. Tambahkan audit trail untuk registration entry dan perubahan trust domain. Rotasi trust bundle harus diuji seperti rotasi credential lain. Jika identity dipakai untuk authorization juga, pisahkan policy authz dari issuance identity agar debugging lebih masuk akal.

Untuk skala besar, pikirkan ownership entry lifecycle. Tim platform tidak seharusnya menjadi bottleneck untuk tiap service baru. Buat pola onboarding yang terdokumentasi dan selector policy yang konsisten.

Trade-offs

SPIFFE/SPIRE memberi identity yang jauh lebih sehat daripada shared secret, tetapi operational model-nya lebih kompleks. Tim perlu memahami trust domain, entry selector, dan distribusi certificate. Jika aplikasi tidak siap memakai mTLS atau Workload API, adopsi awal bisa terasa berat. Namun trade-off ini biasanya sepadan pada lingkungan dengan banyak service dan kebutuhan isolasi yang nyata.

Failure Modes

  • Selector terlalu longgar sehingga workload yang salah mendapat identity.
  • Selector terlalu sempit sehingga pod tidak pernah menerima SVID.
  • Trust domain berubah tanpa rencana migrasi sehingga integrasi lama rusak.
  • Tim memakai SPIFFE identity tetapi tetap menyimpan shared secret yang sama di mana-mana, sehingga manfaatnya turun drastis.

Best Practices

  • Gunakan namespace dan service account sebagai selector minimum.
  • Dokumentasikan trust domain sejak awal.
  • Mulai dari use case service-to-service yang jelas, bukan mengubah semua service sekaligus.
  • Audit registration entry secara berkala.
  • Pastikan aplikasi atau proxy benar-benar memverifikasi SPIFFE URI peer, bukan hanya menerima sertifikat apa pun dari bundle internal.

Kesalahan Umum

  • memperlakukan SPIRE seperti secret injector biasa
  • memakai selector terlalu generik
  • tidak menyiapkan pola rotasi dan audit entry
  • mengaktifkan identity tetapi tidak mengubah policy trust di aplikasi

Kesimpulan

SPIFFE dan SPIRE memberi fondasi workload identity yang jauh lebih matang untuk Kubernetes dibanding token statis dan shared secret yang tersebar. Begitu identity diterbitkan berdasarkan runtime selector yang jelas, mTLS internal menjadi lebih mudah diaudit, diputar, dan dipahami sebagai sistem kepercayaan yang nyata, bukan sekadar kumpulan file cert.

Kalau artikel ini membantu, kamu bisa support eksperimen berikutnya.

Apresiasi di Trakteer

Keep Reading

Related posts