Production ortamında her şey tıkır tıkır işlerken, bir sabah Slack'te 'Neden tüm namespace silindi?' mesajıyla uyanıp soğuk terler döktün mü hiç? Eğer cevabın 'hayır' ise ya çok şanslısın ya da Kubernetes (K8s) dünyasının o meşhur 'varsayılan' (default) ayarlarına henüz kimse dokunmadı. Ama biliyorsun, Red Teamer'lar ve saldırganlar o 'varsayılan' ayarlara bayılırlar.
Kubernetes, modern yazılım dünyasının orkestra şefi. Ancak bu şef bazen o kadar cömert davranıyor ki, sahneye çıkan her enstrümana (yani pod'a) tüm orkestrayı yönetme yetkisi verebiliyor. Bugün seninle testCompany laboratuvarımızda sıkça karşılaştığımız, 'çalışıyor işte kurcalama' denilerek geçiştirilen ama aslında birer saatli bomba olan K8s güvenlik açıklarını ve bunları nasıl yamayacağımızı konuşacağız.
API Server: Kaleye Giriş Bileti
K8s dünyasının kalbi kube-apiserver'dır. Her şey onun üzerinden döner. Eğer bu kapıyı açık bırakırsan, saldırganın içeri girmesi için sadece bir curl komutu yeterli olur. Çoğu zaman geliştirme ortamlarında 'kolaylık olsun' diye anonim erişimlere izin verildiğini görüyoruz. Bu, dükkanın anahtarını paspasın altına koyup üstüne de neon tabelayla 'Anahtar Burada' yazmaya benziyor.
Ne yapmalı?
Öncelikle anonymous-auth parametresinin false olduğundan emin olmalısın. Ayrıca API Server'ı sadece belirli IP bloklarına (whitelist) açmak, dış dünyaya tamamen kapatmak en mantıklı hareket olur.
RBAC: Kim, Nerede, Ne Yapıyor? (Ya da Yapamıyor)
Role-Based Access Control (RBAC), K8s güvenliğinin bel kemiğidir. Ama gel gör ki, genelde 'Permission Denied' hatası almamak için herkes cluster-admin yetkisiyle donatılır. Bir podun sadece kendi namespace'indeki configmap'i okuması gerekiyorken, tüm cluster'daki secret'ları listeleme yetkisine sahip olması tam bir felaket senaryosudur.
Bir Red Teamer gözüyle bakarsak, ele geçirdiğim tek bir pod üzerinden tüm cluster'ı sömürmek için aradığım ilk şey 'Over-privileged ServiceAccount'lardır.
Zararsızlaştırılmış (Defanged) Örnek: Tehlikeli Bir ClusterRole
# BU BİR GÜVENLİK RİSKİDİR - KULLANMAYIN
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: her-seye-yetkili-rol
rules:
- apiGroups: [""]
resources: ["*"] # Her kaynağa erişim
verbs: ["*"] # Her işlemi yapabilme
Bu tür bir rolü bir pod'a bağladığında (ServiceAccount vasıtasıyla), o podun içindeki bir saldırgan şu komutla tüm secret'larını çekebilir:
curl -k -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" https://127.0.0.1:6443/api/v1/secrets
Çözüm: Principle of Least Privilege (En Az Yetki Prensibi). Sadece ihtiyacı olanı, ihtiyacı olduğu kadar ver.
Konteynerlar Sınırsız Değildir: Privileged Pods
'Docker içinde çalışıyorum, bana bir şey olmaz' demeyin. Eğer bir pod privileged: true flag'i ile çalıştırılıyorsa, o pod aslında host makinenin neredeyse tüm yetkilerine sahiptir. Saldırgan bu pod üzerinden host'un dosya sistemine erişebilir (mount edebilir) ve oradan tüm sunucuyu ele geçirebilir.
Tehlikeli Pod Tanımı (Mock Code):
apiVersion: v1
kind: Pod
metadata:
name: tehlikeli-pod
spec:
containers:
- name: ubuntu
image: ubuntu:latest
securityContext:
privileged: true # ASLA YAPMAYIN
volumeMounts:
- mountPath: /host-root
name: root-disk
volumes:
- name: root-disk
hostPath:
path: /
Bu pod ayağa kalktığında, /host-root altına girdiğinde aslında ana sunucunun (node) içindesin demektir. Buradan sonrası chroot yapıp kalıcı bir backdoor bırakmaya bakar.
Network Policies: Sessizce Sızan Trafik
K8s'te varsayılan olarak her pod, her pod ile konuşabilir. Yani senin frontend pod'un, veritabanı (DB) pod'una doğrudan SQL query'si atabilir. Arada hiçbir engel yok. Bir mikroservisin ele geçirilmesi durumunda saldırganın 'lateral movement' (yanal ilerleme) yapması için harika bir ortam, değil mi?
Savunma Hattı: Mutlaka bir CNI (Calico, Cilium vb.) kullan ve 'Default Deny' politikası uygula. Önce her şeyi kapat, sonra sadece gereken iletişime izin ver.
Örnek Güvenli Network Policy (Defanged):
# Sadece frontend'den gelen trafiğe izin veren DB politikası
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-access-policy
spec:
podSelector:
matchLabels:
role: db
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 5432
Secret Management: Base64 Şifreleme Değildir!
Bunu hala söylüyoruz çünkü hala karşılaşıyoruz: K8s Secret'ları varsayılan olarak sadece Base64 formatında tutulur. Yani echo "YWRtaW4=" | base64 -d dediğinde şifre karşında. Bu bir güvenlik önlemi değil, sadece bir format dönüşümüdür.
Doğru Yaklaşım: Secret'ları 'at rest' (dururken) şifrelemek için bir KMS (Key Management Service) entegrasyonu şart. Ya da daha iyisi, HashiCorp Vault gibi harici bir çözüm kullanıp podların içine sadece çalışma anında (runtime) enjekte etmek.
Son Söz Yerine: Sürekli Denetim
K8s güvenliği bir kerelik bir iş değil, bir süreçtir. kube-bench gibi araçlarla CIS Benchmark testlerini düzenli yapmalı, falco gibi araçlarla runtime güvenliğini (kim hangi dosyayı değiştirdi, hangi pod garip bir yere istek attı?) izlemelisin.
Unutma, testCompany'de biz her zaman şunu deriz: 'Güvenlik, en zayıf halkan kadar güçlüdür.' K8s'te o zayıf halka genellikle unutulmuş bir ServiceAccount veya açık bırakılmış bir dashboard olur.
Şimdi git ve cluster'ındaki o 'unprivileged' olması gereken podlara bir bak. Gerçekten öyleler mi?
