Kubernetes Security: RBAC, Network Policies, and Pod Hardening
Cerita gue mengamankan cluster Kubernetes yang tadinya dibiarkan terbuka — dari RBAC yang berantakan, network policy yang nggak ada, sampai pod yang jalan dengan privilege berlebihan. Semua langkahnya praktis dan bisa langsung lo terapin di cluster sendiri.
Lo tahu nggak, momen paling deg-degan dalam karir gue sebagai engineer infrastruktur itu bukan pas server production down. Bukan. Momen paling menyeramkan justru waktu gue buka dashboard Kubernetes di environment staging dan nyadar bahwa semua user di cluster itu punya akses yang sama dengan admin. Semua. Termasuk service account yang dipakai aplikasi internal yang cuma butuh baca config.
Waktu itu gue cuma bisa mikir: "Ini cluster, bukan playground."
Kubernetes itu sebenernya tools yang luar biasa. Dia ngasih lo abstraksi yang rapi buat manage container di banyak node. Tapi justru karena dia powerful, dia juga jadi salah satu target paling menarik buat attacker. Dan masalahnya, kebanyakan orang yang baru migrate ke Kubernetes bawa mindset lama: "yang penting aplikasi jalan dulu, security nyusul." Gue juga dulu kayak gitu. Sampai akhirnya ada satu insiden yang bikin gue kapok dan mulai belajar dari dasar.
Cerita ini bukan tutorial yang muluk-muluk. Ini cerita perjalanan gue — apa yang gue temuin, apa yang gue rusak (ya, gue sempat bikin cluster down gara-gara salah config network policy), dan apa yang akhirnya gue terapin biar cluster gue layak disebut aman. Kalau lo lagi mulai serius sama Kubernetes security, semoga cerita gue bisa nyelametin lo dari sakit kepala yang gue alamin.
Babak Satu: Ketika Semua Orang Punya Akses Admin
Jadi begini ceritanya. Klien gue punya cluster Kubernetes yang dipake buat ngejalanin sekitar lima belas microservice. Aplikasi internal, kata mereka. Nggak ada data sensitif, kata mereka. Tapi pas gue mulai audit, gue nemuin hal yang bikin alis gue naik.
Pertama, hampir semua service account di namespace production itu attach ke role cluster-admin. Nggak cuma satu atau dua — hampir semua. Kenapa? Karena tim developer-nya dulu, waktu pertama kali setup cluster, pake kubeadm, terus biar gampang, mereka assign cluster-admin ke semua yang kerasa perlu akses. "Nanti aja dirapiin," kata mereka. "Nanti" itu nggak pernah dateng.
Kedua, ada satu service account yang dipake aplikasi frontend — aplikasi yang nyambung ke internet, yang bisa diakses publik — dan service account itu punya akses ke semua secret di seluruh cluster. Bayangin. Aplikasi yang theoretically bisa kena RCE kalau ada vulnerability di web framework-nya, punya kunci ke semua secret. Secret database production, secret API key payment gateway, semuanya.
Gue nggak perlu nunggu attacker buat tau ini bahaya. Cukup pikirkan: kalau attacker berhasil nembus aplikasi frontend itu, langkah berikutnya adalah enumerate service account token yang ada di dalam pod. Token itu otomatis di-mount di /var/run/secrets/kubernetes.io/serviceaccount/token. Attacker tinggal pakai token itu buat akses Kubernetes API. Dan karena token itu punya akses cluster-admin, game over. Attacker nggak cuma nguasai satu pod — dia nguasai seluruh cluster. Pod, secret, node, semuanya.
Ini yang namanya privilege escalation, versi Kubernetes. Dan ini jauh lebih umum daripada yang orang bayangin.
Kenapa RBAC Itu Bukan Opsional
RBAC — Role-Based Access Control — itu mekanisme utama Kubernetes buat ngatur siapa bisa ngapain. Konsepnya sederhana: lo bikin Role (atau ClusterRole), lo bikin RoleBinding (atau ClusterRoleBinding) yang ngehubungin Role itu ke user, group, atau service account.
Masalahnya, orang sering malas mikir dan ujung-ujungnya pilih jalan pintas. Dan jalan pintas paling umum adalah pakai cluster-admin buat semuanya. Gue paham banget kenapa orang ngelakuin ini: nge-debug aplikasi di Kubernetes itu menyebalkan, dan kalau lo punya akses penuh, lo bisa ngapain aja buat ngebantu developer. Tapi konsekuensinya gede banget.
Pertama, blast radius-nya. Kalau satu account ke-compromise, attacker bisa ngapain aja di seluruh cluster. Kedua, audit trail-nya jadi meaningless. Kalau semua orang bisa delete pod, hapus namespace, atau baca secret, gimana lo mau tau siapa yang ngapain? Ketiga, ini melanggar prinsip least privilege yang sebenernya udah jadi standar industri.
Gue pernah denger argumen: "Ah, ini cluster internal, siapa sih yang mau nyerang?" Argumen itu naif. Attacker nggak peduli cluster lo internal atau nggak. Yang mereka peduliin adalah: ada nggak jalan masuk? Dan service account yang over-privileged itu justru jalan masuk yang paling enak.
Langkah Praktis: RBAC yang Bener
Oke, cukup teorinya. Ini yang gue lakuin buat benerin RBAC di cluster klien gue.
1. Inventory Dulu
Pertama, gue bikin list semua service account, role, dan binding yang ada. Perintahnya gampang:
bashkubectl get serviceaccounts -A kubectl get roles -A kubectl get rolebindings -A kubectl get clusterroles kubectl get clusterrolebindings
Dari situ gue bisa liat pola-nya. Dan polanya gue udah ceritain: semua cluster-admin, nggak ada granularity.
2. Bikin Role Spesifik per Aplikasi
Terus gue bikin role yang sesuai kebutuhan tiap aplikasi. Contohnya, aplikasi yang cuma butuh baca config dari ConfigMap, gue kasih role kayak gini:
yamlapiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: production name: config-reader rules: - apiGroups: [""] resources: ["configmaps", "secrets"] verbs: ["get", "list", "watch"]
Catet: role ini cuma berlaku di namespace production, dan cuma buat baca ConfigMap dan Secret. Nggak bisa hapus, nggak bisa edit, nggak bisa akses namespace lain.
3. Bind ke Service Account, Bukan ke Orang
Terus gue bind role itu ke service account yang dipake aplikasi:
bashkubectl create rolebinding config-reader-binding \ --role=config-reader \ --serviceaccount=production:frontend-app \ --namespace=production
Ini penting: service account itu identitas yang dipake pod, jadi ngatur akses lewat service account itu jauh lebih clean daripada ngatur per-user. Pod yang jalan dengan service account frontend-app otomatis punya permission yang sesuai.
4. Verifikasi dengan kubectl auth can-i
Sebelum gue push perubahan ke production, gue tes dulu. Command kubectl auth can-i itu lifesaver:
bashkubectl auth can-i --list --as=system:serviceaccount:production:frontend-app
Ini nunjukin semua permission yang dimiliki service account itu. Kalau gue liat ada yang nggak seharusnya, gue bisa benerin sebelum jadi masalah.
5. Jangan Lupa Service Account Default
Satu hal yang sering kelewat: pod yang jalan tanpa specify serviceAccountName itu otomatis pake service account default di namespace-nya. Dan kalau service account default itu nggak diatur, dia punya permission yang terbatas — tapi tetap ada token yang ke-mount. Best practice-nya: set automountServiceAccountToken: false di service account yang nggak butuh akses API, biar token nggak ke-mount ke pod.
yamlapiVersion: v1 kind: ServiceAccount metadata: name: default namespace: production automountServiceAccountToken: false
Ini langkah kecil tapi dampaknya gede. Pod yang nggak butuh akses Kubernetes API jadi nggak punya token yang bisa dicuri.
Babak Dua: Network Policy — Tembok yang Nggak Pernah Dibangun
Setelah RBAC beres, gue nemuin masalah berikutnya: cluster itu jalan dengan network policy nol. Nol. Semua pod bisa komunikasi sama semua pod. Nggak ada pembatas sama sekali.
Buat yang belum familiar: Kubernetes secara default itu flat network. Semua pod bisa ngomong satu sama lain, lintas namespace juga bisa. Ini bikin debugging gampang, tapi juga bikin lateral movement gampang banget buat attacker. Kalau attacker udah masuk ke satu pod, dia bisa scan seluruh cluster dan nyoba connect ke pod lain.
Gue inget persis waktu gue bilang ke klien: "Cluster lo ini kayak apartemen tanpa sekat. Semua kamar nyambung ke semua kamar. Kalau ada maling masuk ke satu kamar, dia bisa jalan-jalan ke semua kamar lain tanpa halangan."
Mereka cuma bisa garuk kepala.
Cara Kerja Network Policy
NetworkPolicy itu resource Kubernetes yang ngatur traffic masuk (ingress) dan keluar (egress) antar pod. Konsepnya: lo define pod selector, terus lo tentuin dari mana aja traffic boleh masuk dan ke mana traffic boleh keluar.
Contoh simpel — ini policy yang ngebatesin traffic ke pod frontend cuma dari ingress controller:
yamlapiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: frontend-allow-ingress-only namespace: production spec: podSelector: matchLabels: app: frontend policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: name: ingress-nginx ports: - protocol: TCP port: 8080
Policy ini bilang: pod yang label-nya app: frontend cuma boleh nerima traffic dari pod yang ada di namespace dengan label name: ingress-nginx, dan cuma di port 8080. Semua traffic lain ke pod frontend diblokir.
Pitfall yang Bikin Cluster Gue Down
Sekarang bagian yang memalukan. Waktu gue terapin network policy pertama di production, gue nggak nyadar kalau policy yang gue tulis ternyata nge-block traffic DNS. Semua pod di namespace production butuh resolve DNS biar bisa connect ke service lain. Dan karena gue nggak nulis rule egress yang ngizinin traffic ke kube-dns (yang ada di namespace kube-system), semua pod di namespace itu tiba-tiba nggak bisa resolve hostname.
Hasilnya? Semua microservice yang saling connect mulai gagal. Health check mulai merah. Monitoring mulai bunyi. Dan gue — dengan panik — harus rollback network policy itu secepat mungkin.
Pelajaran yang gue dapet dengan cara paling sakit:
- 1Selalu tambahin rule DNS. Ini yang paling sering bikin masalah. Kalau lo bikin NetworkPolicy yang nge-block egress, jangan lupa izinin traffic ke kube-dns di port 53 (TCP dan UDP). Atau simpen DNS resolver di luar cluster kalau lo emang sengaja mau blokir DNS internal.
- 1Terapin bertahap. Jangan langsung push policy ke semua namespace production. Mulai dari namespace yang paling nggak penting, atau environment staging, terus amati dulu.
- 1Gunakan default deny dulu di environment yang aman. Konsep default deny itu: semua traffic diblokir kecuali yang eksplisit diizinin. Ini pendekatan paling aman, tapi paling susah diterapin di cluster yang udah jalan. Mulai dari namespace baru, biarin policy-nya mateng, terus baru terapin ke namespace lain.
- 1Tools bisa bantu. Ada tools kayak Cilium yang punya network policy yang lebih ekspresif, plus observability yang lebih bagus. Tapi buat mulai, NetworkPolicy bawaan Kubernetes udah cukup.
Setelah gue benerin dengan nambahin rule DNS, cluster gue stabil lagi. Dan kali ini, gue punya policy yang beneran ngebatesin traffic antar pod. Nggak ada lagi apartemen tanpa sekat.
Babak Tiga: Pod Security — Jangan Biarin Pod Lo Jadi Superuser
Masalah ketiga yang gue temuin: banyak pod yang jalan sebagai root, dengan privilege penuh, dan hostPath mounts yang nggak perlu.
Ini kombinasi yang bahaya. Pod yang jalan sebagai root di dalam container — ya, secara default container itu jalan sebagai root di dalam namespace-nya sendiri — tapi kalau container itu bisa break out, dia bisa akses host. Dan kalau ada hostPath mount yang nunjuk ke direktori sensitif host, attacker bisa baca file host langsung dari dalam pod.
Pod Security Standards
Kubernetes punya Pod Security Standards yang dibagi jadi tiga level: privileged, baseline, restricted. Level restricted itu paling ketat: pod harus jalan sebagai non-root, nggak boleh pake privileged mode, nggak boleh pake hostPath, dll.
Cara terapinnya bisa pake namespace label:
bashkubectl label namespace production pod-security.kubernetes.io/enforce=restricted
Dengan label ini, semua pod yang di-deploy ke namespace production harus comply sama level restricted. Kalau nggak, pod-nya nggak bakal bisa di-deploy.
Tapi hati-hati: kalau lo udah punya workload yang jalan sebagai root, ini bakal nge-block mereka. Jadi gue saranin: mulai dari level baseline dulu, terus pelan-pelan naik ke restricted. Atau terapin ke namespace baru dulu.
Contoh Config yang Bener
Ini contoh security context yang gue terapin di deployment:
yamlsecurityContext: runAsNonRoot: true runAsUser: 1000 seccompProfile: type: RuntimeDefault capabilities: drop: ["ALL"] allowPrivilegeEscalation: false
Mari kita bedah:
runAsNonRoot: true— pod nggak boleh jalan sebagai rootrunAsUser: 1000— pake UID 1000 (user non-root)seccompProfile: RuntimeDefault— batasi system calls yang bisa dipake containercapabilities.drop: ["ALL"]— buang semua Linux capabilities, cuma yang dibutuhin aja yang di-add balikallowPrivilegeEscalation: false— proses di dalam container nggak bisa escalate privilege
Ini bukan cuma formalitas. Ini yang bikin container susah banget buat break out ke host. Bahkan kalau ada vulnerability di aplikasi, attacker bakal kesulitan buat dapet akses yang lebih tinggi.
Image Scanning
Satu hal lagi yang gue tambahin: image scanning. Gue integrasiin Trivy ke pipeline CI. Setiap kali ada image baru di-build, Trivy scan dulu. Kalau ada vulnerability critical, build gagal. Ini murah, gampang, dan ngeblock banyak masalah sebelum masuk ke cluster.
bashtrivy image --severity CRITICAL,HIGH --exit-code 1 myapp:latest
Command ini scan image myapp:latest, dan kalau ada vulnerability critical atau high, exit code-nya 1, yang bikin pipeline gagal. Simpel tapi efektif.
Gue juga terapin image pull policy Always dan pin image dengan digest (bukan cuma tag), biar image yang jalan di production selalu yang udah di-scan dan di-approve.
Babak Empat: Audit dan Monitoring
Setelah RBAC, network policy, dan pod security beres, gue nambahin layer terakhir: observability dan audit.
Audit Logs
Kubernetes API server bisa ngeluarin audit logs yang nyatet semua request ke API. Ini penting buat ngejawab pertanyaan: "Siapa yang ngapain, kapan, dan apa hasilnya?"
Gue aktifin audit logging dan simpan log-nya ke external storage (S3 bucket di cloud provider). Konfigurasi audit policy-nya bisa disetel biar nggak semua event ke-log — biar volume log-nya nggak gila-gilaan. Yang penting di-log: semua request yang nulis (create, update, delete), semua akses ke secret, semua request dari user yang bukan human.
Kubernetes Events
kubectl get events itu sumber informasi yang sering dilupain. Event kayak "Failed to pull image", "BackOff", "Killing" itu bisa jadi early warning. Gue set alerting ke Slack buat event tertentu, biar tim bisa tau lebih awal kalau ada yang aneh.
Runtime Security
Kalau lo mau naik level, ada tools kayak Falco yang monitor system calls di level runtime. Falco bisa deteksi hal-hal kayak: container spawn shell interaktif, container baca file sensitif host, atau container nyoba mount sesuatu yang aneh. Ini layer pertahanan yang bagus, terutama buat deteksi dini kalau ada yang nyoba break out.
Gue nggak bilang semua orang harus pasang semua ini besok pagi. Tapi mulailah dari yang paling dasar: audit log aktif, event monitoring, dan alerting. Itu udah ngebantu banget.
Pelajaran yang Gue Bawa Pulang
Kalau gue rangkum semua yang gue alami, ada beberapa pelajaran besar:
Pertama, least privilege itu bukan saran, tapi keharusan. Service account yang over-privileged itu kayak kunci master yang digantung di pintu depan. Iya, gampang diakses, tapi juga gampang dicuri. Setiap workload harus punya akses seminimal mungkin.
Kedua, default deny itu teman lo. Semua yang nggak eksplisit diizinin, blokir. Ini berlaku di network policy, di RBAC, di pod security. Memang ribet di awal, tapi jauh lebih aman daripada model allow-all yang bikin lo nggak tau apa yang sebenernya boleh dan nggak boleh.
Ketiga, security di Kubernetes itu lapisan. Nggak ada satu config ajaib yang bikin cluster aman. RBAC ngatur akses, network policy ngatur traffic, pod security ngatur privilege, audit log ngatur visibilitas. Semuanya harus jalan bareng.
Keempat, perubahan security harus bertahap. Gue belajar ini dengan cara yang menyakitkan — cluster sempat down gara-gara network policy. Jangan ulangi kesalahan gue. Terapin di staging, amati, terus pindah ke production. Rollback plan harus selalu ada.
Kelima, dokumentasikan. Setiap keputusan security yang lo buat, tulis. Kenapa service account ini dikasih akses ini? Kenapa network policy ini dibikin? Enam bulan lagi, lo (atau orang lain) bakal butuh konteks itu. Percaya gue, gue udah ngalamin kebingungan itu.
Penutup
Kubernetes itu tools yang powerful, dan kekuatan itu harus diimbangi dengan tanggung jawab. Cluster yang dibiarin terbuka bukan cuma risiko buat perusahaan, tapi juga beban mental buat engineer yang pegang tanggung jawabnya. Gue tau, karena gue pernah ngerasain tidur nggak nyenyak mikirin cluster yang gue kelola.
Tapi kabar baiknya: memperbaiki security cluster itu nggak perlu sempurna dalam semalam. Mulai dari RBAC — itu yang paling gampang dan paling berdampak. Terus network policy. Terus pod security. Satu per satu. Yang penting mulai.
Kalau lo baru mulai belajar Kubernetes security, atau lagi ngurus cluster yang udah jalan, mulailah dari audit. Cek service account lo. Cek binding lo. Cek network policy lo. Cek pod security lo. Lo bakal kaget — mungkin nggak seneng — dengan apa yang lo temuin. Tapi itu justru titik awal yang bagus.
Karena pada akhirnya, security itu bukan tentang tools atau config yang canggih. Security itu tentang kebiasaan: selalu mikir "kalau ini ke-compromise, apa yang bisa terjadi?" dan selalu mikir "apa permission minimal yang dibutuhin?" Kalau dua pertanyaan itu udah jadi kebiasaan, cluster lo — dan karir lo — bakal jauh lebih aman.
Selamat ngurus cluster lo, dan jangan lupa: default deny itu teman lo. Sampai ketemu di cerita berikutnya.