Kubernetes Güvenliği: RBAC, Pod Escape ve Sertleştirme
Container kaçışından cluster ele geçirmeye giden zincir ve onu kıran savunma katmanları
Kubernetes'te cluster ele geçirmenin baskın yolu bir CVE değil, yanlış yapılandırmadır: aşırı izinli RBAC, privileged/hostPath pod'ları, açık kubelet ve etcd servisleri ve ağ segmentasyonu eksikliği. Bu yazı tek bir container'dan node'a, oradan tüm cluster'a uzanan saldırı zincirini adım adım gösterir; ardından Pod Security Standards, least-privilege RBAC, seccomp/AppArmor ve default-deny NetworkPolicy ile bu zinciri hangi noktada kırabileceğinizi kanıtlarıyla ele alır.
Kubernetes Saldırı Yüzeyinin Anatomisi: Tek Pod’dan Cluster’a
Kubernetes güvenliğinde asıl tehlike, egzotik bir sıfır-gün açığı değil; art arda dizilen yanlış yapılandırmalardır. Saldırganın kalıbı neredeyse her zaman aynıdır: internete açık bir uygulama üzerinden bir container’a ayak basılır (initial access), o container’ın sahip olduğu servis hesabı token’ı veya yetkileriyle kontrol düzlemine (control plane) doğru yetki yükseltilir (privilege escalation), ardından node’a inilerek (container escape) ve ağ üzerinde yanal hareketle (lateral movement) tüm cluster ele geçirilir. Kubernetes dokümantasyonunun ve saha araştırmalarının (Aqua Security, Palo Alto Unit 42) ortak bulgusu nettir: bir kubelet’i veya etcd’yi savunmasız bırakan şey genellikle bir güvenlik yaması eksikliği değil, --anonymous-auth=true gibi bir bayrak ya da cluster-admin bağlanmış bir servis hesabıdır. Bu yazı, bu zinciri her halkasıyla ofansif olarak açıklar ve her halkayı kıran defansif kontrolü kanıtıyla verir.
Referans çerçeve olarak OWASP Kubernetes Top Ten (2022) listesini kullanacağız; çünkü saldırı zincirimizin her adımı bu risklerden birine denk gelir: K01 (Insecure Workload Configurations), K03 (Overly Permissive RBAC Configurations), K08 (Secrets Management Failures), K07 (Missing Network Segmentation Controls) ve K09 (Misconfigured Cluster Components).
Aşırı İzinli RBAC: escalate, bind, impersonate ve Pod Oluşturma Tuzağı
RBAC (Role-Based Access Control), Kubernetes’te kimin neye erişebileceğini belirleyen katmandır ve yetki yükseltmenin en verimli zeminidir. En sık görülen hata, “hızlı olsun” diye verilen wildcard’lardır — bir Role’de resources: ["*"] ve verbs: ["*"] görmek, pratikte o namespace’te cluster-admin vermek demektir.
Bir saldırgan, ele geçirdiği kimlikle ilk iş olarak yetkilerini haritalar:
# Mevcut kimliğin (veya token'ın) tüm izinlerini listele
kubectl auth can-i --list
# Belirli bir tehlikeli eylem mümkün mü?
kubectl auth can-i create pods --all-namespaces
kubectl auth can-i '*' '*'
Üç RBAC fiili (verb) özellikle kritiktir, çünkü doğrudan “yetki üreten” fiillerdir ve neredeyse hiçbir uygulamaya gerekli değildir:
escalate: Sahip olmadığınız izinleri bir Role/ClusterRole’e ekleyebilmenizi sağlar. Normalde Kubernetes, kendi yetkinizin üstünde bir Role yazmanızı engeller;escalatebu koruma korkuluğunu kaldırır.escalate+ Role düzenleme yetkisi olan bir kimlik, kendisine keyfi izin atayabilir.bind: Var olan bir ClusterRole’ü (örneğincluster-admin) kendi hesabınıza bağlayabilmenizi sağlar.impersonate: Başka bir kullanıcı, grup veya servis hesabı kimliğine bürünerek API çağrısı yapmanızı sağlar — etkili biçimde kimlik doğrulamayı atlar.
bind fiiline sahip bir kimliğin klasik yetki yükseltme hamlesi:
# Saldırgan kendi hesabını cluster-admin'e bağlar
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: pwn
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: default
namespace: default
Bunlara ek olarak, kısıtsız create pods yetkisi tek başına bir yetki yükseltme primitifidir: privileged bir pod ya da hostPath bağlayan bir pod oluşturabilen herkes, bir sonraki bölümdeki container kaçışını tetikleyebilir. Aynı şekilde pods/exec, pods/attach ve secrets üzerinde get/list yetkileri de doğrudan veri sızdırmaya veya çalışan yüksek-yetkili pod’lara komut enjekte etmeye açar. MITRE ATT&CK Container matrisi bu tekniği “Account Manipulation” olarak sınıflandırır: saldırgan bir RoleBinding/ClusterRoleBinding oluşturarak yetki ekler.
Container Kaçışı: Privileged Pod, hostPath ve hostPID ile Node’a İnmek
Container kaçışı (container escape), pod izolasyonunu delip altındaki node’un işletim sistemine erişmektir. Node’a root eriştiğinizde, o node üzerinde çalışan diğer tüm pod’ların secret’larını, kubelet kimliğini ve nihayetinde cluster’ı ele geçirebilirsiniz. Kaçışı mümkün kılan üç ayar, OWASP K01’in (Insecure Workload Configurations) somut karşılığıdır.
1) privileged: true — Container’ı neredeyse tüm çekirdek yeteneklerine ve host aygıtlarına erişimle çalıştırır. Böyle bir pod içinden host dosya sistemini mount edip chroot ile node’a geçmek mümkündür.
2) hostPath volume — Node’un dosya sistemindeki bir dizini doğrudan pod’a bağlar. / yolunu bağlamak, node’un tamamına okuma/yazma erişimi verir:
apiVersion: v1
kind: Pod
metadata:
name: escape
spec:
hostPID: true # Node'un süreç ad-alanını paylaş
containers:
- name: escape
image: alpine
securityContext:
privileged: true # K01: tam ayrıcalık
command: ["/bin/sh", "-c", "sleep 1d"]
volumeMounts:
- name: host
mountPath: /host
volumes:
- name: host
hostPath:
path: / # Node kök dizini pod içine bağlandı
Bu pod içine girildiğinde node’u ele geçirmek tek satırdır:
kubectl exec -it escape -- chroot /host /bin/bash
# Artık node üzerinde root'sunuz; kubelet client sertifikasını okuyun:
cat /var/lib/kubelet/pki/kubelet-client-current.pem
3) hostPID: true / hostNetwork / hostIPC — Host ad-alanlarını paylaşır. hostPID, node’daki tüm süreçleri /proc üzerinden görünür kılar; saldırgan yüksek-yetkili süreçlerin bellek/ortam değişkenlerinden token ve secret toplayabilir, hatta süreçlere enjeksiyon yapabilir.
Kritik dürüstlük notu: Bu tekniklerin hiçbiri bir “exploit” değildir — hepsi Kubernetes’in tasarım gereği desteklediği, ancak üretim iş yüklerinde gereksiz olan özelliklerdir. Savunma bu yüzden yamalama değil, bu özellikleri admission katmanında yasaklamaktır.
Servis Hesabı Token’larının Kötüye Kullanımı
Her pod, aksi belirtilmedikçe bir servis hesabı (ServiceAccount) token’ı ile doğar. Kubernetes v1.22’den itibaren bu token, TokenRequest API’si üzerinden alınan, süreli ve otomatik dönen (bound) bir token olarak projected volume ile şu yola bağlanır:
/var/run/secrets/kubernetes.io/serviceaccount/token
Sorun şu: automountServiceAccountToken varsayılan olarak açıktır. Bir web uygulamasını ele geçiren saldırgan, hiçbir ek zafiyete ihtiyaç duymadan bu token’ı okuyarak o servis hesabının tüm yetkilerini devralır. Eğer bu hesap aşırı yetkiliyse (örneğin secret’ları okuyabiliyor veya pod oluşturabiliyorsa), tek bir uygulama açığı cluster çapında erişime dönüşür (OWASP K03).
# Ele geçirilmiş pod içinden token ve CA ile doğrudan API'ye konuş
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
APISERVER=https://kubernetes.default.svc
curl -sk -H "Authorization: Bearer $TOKEN" \
--cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
$APISERVER/api/v1/namespaces/default/secrets
Bir başka tehlikeli senaryo, kötü niyetli bir image’ın node üzerindeki yüksek-yetkili başka bir pod’un token’ını çalmasıdır (hostPID veya paylaşılan volume ile). Çalınan token, cluster dışından bile kullanılabildiği için — token cluster IP kısıtlaması taşımaz — saldırgan kendi makinesinden API’ye komut gönderebilir. Bunun kanonik tespiti audit log’dur: bir servis hesabına ait çağrıların beklenmedik kaynak IP’lerinden gelmesi, token hırsızlığının en net sinyalidir.
Kontrol Düzlemine Doğrudan Erişim: Kubelet API (10250) ve etcd (2379)
Bazı saldırılar RBAC’ı hiç uğraştırmaz; kontrol düzleminin altındaki servislere doğrudan konuşur (OWASP K09 — Misconfigured Cluster Components).
Kubelet API (TCP 10250): Her node’da çalışan kubelet, pod’ları listeleyen ve içlerinde komut çalıştıran bir HTTPS API sunar. Baskın risk bir CVE değil, iki bayraktır: --anonymous-auth=true ve --authorization-mode=AlwaysAllow. Bu ikisi birlikte kubelet’i kimlik doğrulamasız hale getirir. Aqua Security’nin taramaları internete açık on binlerce kubelet API’sinin bu şekilde erişilebilir olduğunu raporlamıştır.
# Anonim erişimle çalışan pod'ları listele
curl -sk https://<NODE_IP>:10250/runningpods/ | jq '.items[].metadata.name'
# Bir container içinde komut çalıştır (uzaktan kod çalıştırma)
curl -sk -X POST "https://<NODE_IP>:10250/run/<ns>/<pod>/<container>" -d "cmd=id"
etcd (TCP 2379): etcd, cluster’ın tüm durumunu tutan anahtar-değer deposudur — ConfigMap içerikleri, ortam değişkenleri ve tüm Secret nesneleri dahil. Kimlik doğrulamasız açık bırakılmış bir etcd, cluster’ın tamamının okunması ve değiştirilmesi demektir. Üstelik Kubernetes secret’ları varsayılan olarak şifrelenmez, yalnızca base64 ile kodlanır; API sunucusunda encryption-at-rest yapılandırılmamışsa, etcd’yi okuyan saldırgan tüm parolaları ve token’ları çözebilir. NCC Group’un araştırması, ele geçirilmiş bir etcd’nin cluster ve node’lar üzerinde tam kontrole nasıl dönüştüğünü ayrıntılandırır.
# Kimlik doğrulamasız etcd'den tüm secret'ları dök
etcdctl --endpoints=https://<IP>:2379 get / --prefix --keys-only | grep secrets
etcdctl --endpoints=https://<IP>:2379 get /registry/secrets/default/db-cred
Ağ Segmentasyonu Eksikliği ve Yanal Hareket
Çoğu CNI eklentisi (Calico, Cilium vb.) varsayılan olarak tüm pod-pod trafiğine izin verir. Yani hiçbir NetworkPolicy tanımlamadıysanız, ele geçirilen tek bir pod cluster içindeki her servise, her veritabanına, hatta kubelet ve etcd portlarına erişmeyi deneyebilir. OWASP bunu K07 (Missing Network Segmentation Controls) olarak listeler ve saha bulguları bunu üretim cluster’larındaki en yaygın açıklardan biri olarak gösterir.
Çözüm, default-deny duruşuyla başlayıp yalnızca meşru akışları allowlist’e almaktır. Aşağıdaki politika bir namespace’teki tüm gelen trafiği varsayılan olarak reddeder:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: uygulama
spec:
podSelector: {} # Namespace'teki TÜM pod'lar
policyTypes: ["Ingress"]
# ingress kuralı yok = her şey reddedilir
Kritik bir tuzak: default-deny egress uygularsanız, DNS’i açıkça izinlemezseniz pod’lar CoreDNS’e (kube-system, UDP/TCP 53) ulaşamaz ve tüm isim çözümlemesi bozulur. Bu yüzden egress politikalarında DNS istisnasını daima ekleyin. Doğru uygulanmış NetworkPolicy, ele geçirilmiş bir pod’u yalnızca gerçekten ihtiyaç duyduğu iki-üç akışla sınırlar — bu, tüm cluster’ı tarayabilen bir dayanak ile yalnızca kendi bağımlılıklarına konuşabilen bir dayanak arasındaki farktır.
Savunma Mimarisi: Katmanları Üst Üste Koymak
Tek bir kontrol yeterli değildir; savunma derinliği (defense in depth) her saldırı halkasını farklı bir katmanda kırar. Aşağıdaki dört katman birlikte uygulanmalıdır.
1) Pod Security Standards (PSS) ile admission kontrolü. Kubernetes 1.25’te (Ağustos 2022) stabil (GA) hale gelen Pod Security Admission, namespace düzeyinde üç profil dayatır: privileged, baseline, restricted. Baseline, en bilinen kaçış vektörlerini engeller — privileged container yok, host ad-alanları (hostPID/hostNetwork/hostIPC) yok, güvenli listede olmayan hostPath yok. Restricted bunun üstüne çıkar: container’lar root olmayan kullanıcıyla çalışmalı, allowPrivilegeEscalation: false olmalı, tüm capability’ler düşürülmeli ve seccomp profili ayarlanmalı. Namespace’e etiketle uygulanır:
apiVersion: v1
kind: Namespace
metadata:
name: uygulama
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/warn: restricted
2) restricted uyumlu securityContext. Her pod/container şu şablona yakınsamalıdır:
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
seccompProfile:
type: RuntimeDefault # tehlikeli syscall'ları kısıtlar (runtime varsayılan profili)
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
RuntimeDefault seccomp profili, container runtime’ın (containerd/CRI-O) yerleşik profilini kullanarak tehlikeli çekirdek çağrılarını engeller; bu, restricted profiline geçişte en sık atlanan gereksinimdir. Daha ince kontrol için AppArmor/SELinux profilleri de node düzeyinde dayatılmalıdır.
3) Least-privilege RBAC. Wildcard’ı yasak sayın. Bir uygulamanın gerçekten ihtiyaç duyduğu minimal Role şuna benzemeli — belirli kaynak, belirli fiil, mümkünse resourceNames ile daraltılmış:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: uygulama
name: configmap-okuyucu
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"]
verbs: ["get", "list"]
escalate, bind, impersonate fiillerini ve cluster-admin bağlamalarını asla rutin iş yüklerine vermeyin. automountServiceAccountToken: false ile token’ları varsayılan olarak enjekte etmeyi kapatın; token’a ihtiyaç duyan pod’da açıkça açın. Cluster genelinde create pods gibi tehlikeli izinleri OPA/Gatekeeper veya Kyverno gibi politika motorlarıyla bir admission kapısına bağlayarak (OWASP K04 — Lack of Centralized Policy Enforcement) sınırlayın.
4) Kontrol düzlemi ve ağ sertleştirmesi. Kubelet’te --anonymous-auth=false ve --authorization-mode=Webhook; etcd’de istemci mTLS sertifika doğrulaması ve ağ izolasyonu; API sunucusunda EncryptionConfiguration ile secret’ların KMS/aescbc ile şifrelenmesi (K08 — Secrets Management Failures). Tüm bunların üstüne default-deny NetworkPolicy ve kapsamlı audit log (K05 — Inadequate Logging and Monitoring) gelir.
Yanlış Yapılandırma × Risk × Sertleştirme
| Yanlış Yapılandırma | Saldırı Sonucu / Risk | Sertleştirme Kontrolü |
|---|---|---|
privileged: true |
Container kaçışı, node’da root | PSS Baseline/Restricted ile privileged reddi |
hostPath: / mount |
Node dosya sistemine yazma, kubelet kimlik hırsızlığı | hostPath yasağı (Baseline), gerekiyorsa readOnly + dar yol |
hostPID/hostNetwork/hostIPC |
/proc üzerinden token toplama, süreç enjeksiyonu |
Host ad-alanı yasağı (Baseline) |
allowPrivilegeEscalation: true, seccomp yok |
setuid/syscall istismarı | Restricted: false + seccompProfile: RuntimeDefault |
RBAC * / cluster-admin bağlaması |
Cluster ele geçirme | Least-privilege Role, wildcard yasağı, düzenli auth can-i denetimi |
escalate/bind/impersonate fiilleri |
Keyfi yetki verme, kimliğe bürünme | Bu fiilleri iş yüklerine hiç vermeme |
Kısıtsız create pods / pods/exec |
Privileged pod ile kaçış, komut enjeksiyonu | Admission policy (Kyverno/OPA), namespace izolasyonu |
automountServiceAccountToken: true + aşırı yetkili SA |
Token hırsızlığı → API erişimi | automount: false, bound + minimal-yetkili SA |
Kubelet anonymous-auth=true + AlwaysAllow |
Uzaktan komut çalıştırma (10250) | anonymous-auth=false, Webhook authz, ağ/FW kısıtı |
| etcd 2379 kimlik doğrulamasız | Tüm secret’ların dökümü | İstemci mTLS, ağ izolasyonu, encryption-at-rest |
| NetworkPolicy yok | Serbest yanal hareket | Default-deny + explicit allowlist (DNS istisnasıyla) |
| Secret’lar sadece base64 | etcd/API okumasıyla açığa çıkma | EncryptionConfiguration (KMS/aescbc) |
Operasyonel Özet: Zinciri Nerede Kırarsınız?
Kubernetes güvenliğinin özü, tek bir “sihirli” kontrol değil, saldırı zincirinin her halkasını farklı bir katmanda kesmektir. İlk erişim kaçınılmaz kabul edilir; asıl kazanç, o ilk container’ı bir çıkmaz sokağa hapsetmektir. Pod Security Standards restricted container kaçışını, least-privilege RBAC + automount: false token’la yetki yükseltmeyi, kubelet/etcd sertleştirmesi kontrol düzlemi atlamasını ve default-deny NetworkPolicy yanal hareketi keser. Bu katmanlar tek tek de değer taşır; ama birlikte uygulandığında, tek bir uygulama açığının tüm cluster’ı düşürdüğü o klasik zinciri fiilen imkânsız hale getirir.
Denetime bugün başlamak isteyen ekipler için üç somut adım: (1) kubectl auth can-i --list ile aşırı yetkili servis hesaplarını ve escalate/bind/impersonate taşıyan Role’leri çıkarın; (2) tüm üretim namespace’lerine pod-security.kubernetes.io/enforce: restricted etiketini önce warn modunda deneyip sonra dayatın; (3) her namespace’e bir default-deny NetworkPolicy ekleyip meşru akışları tek tek açın. Bu üç adım, OWASP Kubernetes Top Ten’in en yüksek etkili risklerini (K01, K03, K07) tek hamlede kapatmaya başlar.
Sen de yaz, arşivde yerini al
AltaySec Arşiv'e katkı ver — uzmanlığını Türkçe siber güvenlik literatürüne kat.
Yazar Ol