Selamlar ekip, ben Sedat. Kahvenizi aldıysanız bugün biraz can sıkacağız, biraz da o çok güvendiğimiz orkestra şefini, yani Kubernetes’i (K8s) ameliyat masasına yatıracağız.
Sektörde bir K8s çılgınlığıdır gidiyor. 'Hadi her şeyi konteynere koyalım, K8s ile ayağa kaldıralım, ölçekleyelim.' Harika, peki güvenlik? İşte orada işler biraz karışıyor. Çoğu dev ve ops ekibi için Kubernetes, her şeyi sihirli bir şekilde çözen bir kara kutu. Ama işin mutfağında, yani Red Team tarafında biz bu kara kutunun içindeki o minik 'default' ayarların nasıl birer felakete dönüştüğünü her gün görüyoruz.
O Meşhur Eleştiri: Helm Install ve 'Bana Bir Şey Olmaz' Mantığı
Direkt konuya en sert yerden gireyim: İnternette bulduğunuz, yıldızı bol diye gözünüz kapalı güvendiğiniz o popüler Helm Chart'ları prod ortamında kullanmak tam anlamıyla delilik. Evet, yanlış duymadınız. 'Helm install x-tool' dediğiniz anda, o YAML dosyasının içinde hangi ServiceAccount'a hangi yetkilerin verildiğini, hangi pod'un host'un dosya sistemine eriştiğini kontrol etmiyorsanız, aslında sisteminizin anahtarını tanımadığınız birine teslim ediyorsunuz demektir. 'Ama binlerce kişi kullanıyor' demeyin, supply chain saldırıları tam da bu 'güven' üzerine kurulur.
1. RBAC: 'Herkes Admin Olsun, İşimiz Hızlı Yürüsün' Faciası
K8s dünyasında en sık karşılaştığım zafiyetlerden biri Role-Based Access Control (RBAC) yapılandırmasındaki aşırı yetkilendirme. Genelde şöyle bir senaryo oluyor: Bir monitoring aracı kurulacak, 'Permission Denied' hatası alınıyor, sonra bir bakıyorsunuz ClusterRole'de * (wildcard) kullanımı patlamış.
Bakın, bir pod'un secrets okuma yetkisi varsa ve bu pod dış dünyaya açıksa (örneğin bir web uygulaması), o pod'u ele geçiren saldırgan tüm cluster'ın database şifrelerini, API key'lerini toplar ve gider.
Zararsızlaştırılmış Kötü Örnek (Bad RBAC):
# BU BİR GÜVENLİK RİSKİDİR, KULLANMAYIN!
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: monitoring-role-bad
rules:
- apiGroups: [""]
resources: ["*"] # Her şeye erişim!
verbs: ["get", "list", "watch"]
Bunun yerine her zaman 'Least Privilege' prensibiyle hareket etmelisiniz. Sadece ihtiyacı olan resource'a, sadece ihtiyacı olan verb'ü verin.
2. Container Escape: Konteynırdan Kaçış Planı
'Konteynırdayım, güvendeyim' sanıyorsanız yanılıyorsunuz. Eğer pod'unuz privileged: true olarak çalışıyorsa veya kritik host path'lerini mount ediyorsa, o konteynır sadece bir kağıttan kaplandır.
Özellikle hostPath ile /var/run/docker.sock (veya yeni nesil runtime'larda benzer socket'ler) mount edildiğinde, saldırgan konteynırdan çıkarak doğrudan host makinede root yetkisi alabilir. Red Team operasyonlarımızda en sevdiğimiz 'pivot' noktası burasıdır.
Zararsızlaştırılmış Riskli Pod Tanımı:
# DİKKAT: Host escape riskine yol açar!
spec:
containers:
- name: test-container
image: example.com/app:latest
securityContext:
privileged: true # ASLA PROD'DA KULLANMAYIN!
volumeMounts:
- mountPath: /host
name: host-root
volumes:
- name: host-root
hostPath:
path: / # Tüm host sistemine erişim
Nasıl Engelleriz?
- Pod Security Admission (PSA) veya Kyverno gibi araçlarla
privilegedkonteynırları yasaklayın. allowPrivilegeEscalation: falseayarını zorunlu tutun.- Konteynırları root kullanıcısı ile çalıştırmayın (
runAsNonRoot: true).
3. Network Policies: Duvarlar Olmadan Şehir Savunulmaz
K8s'in en tehlikeli varsayılan ayarlarından biri şudur: Cluster içindeki her pod, diğer her pod ile konuşabilir. Bir front-end uygulamanız hacklendiğinde, saldırganın elini kolunu sallayarak internal database'e veya ödeme servislerine gitmesine izin mi vereceksiniz?
Network Policy kullanmamak, bir banka şubesinde kapıyı kilitleyip içerdeki tüm kasaları açık bırakmaya benzer.
Defensive Örnek (Isolation):
Sadece belirli bir etiketli (app: backend) pod'dan gelen trafiğe izin veren basit bir policy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-only-backend-to-db
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432
4. Secrets Management: Base64 Şifreleme Değildir!
Bunu her seferinde söylüyorum ama hala 'Secret'ların gerçekten gizli olduğunu sananlar var. K8s Secret'ları varsayılan olarak sadece Base64 ile encode edilir. Yani kubectl get secret -o yaml diyen herkes o şifreyi görebilir.
Eğer etcd veritabanınız disk üzerinde şifrelenmemişse (Encryption at Rest), o disk imajını ele geçiren biri tüm cluster'ın sırlarını çözer.
Çözüm Önerisi:
- HashiCorp Vault veya AWS/Azure/GCP Secrets Manager gibi harici çözümleri K8s ile entegre edin.
SealedSecretskullanarak secret'ları Git üzerinde şifreli saklayın.- K8s KMS plugin'lerini kullanarak etcd seviyesinde şifreleme yapın.
Sedat'tan Saha Notları (Hardening)
Beyler, hanımlar; testCompany'de yaptığımız simülasyonlarda gördüğümüz şey şu: En karmaşık saldırılar bile genellikle en basit yapılandırma hatalarından sızıyor. K8s güvenliği bir 'check-list' işi değildir, bir kültürdür.
- Read-Only Root Filesystem: Pod'larınızın çalışma anında dosya sistemine bir şey yazmasına gerek var mı? Çoğu zaman yok.
readOnlyRootFilesystem: trueyapın, saldırganıncurlile zararlı script indirip çalıştırmasını engelleyin. - Resource Quotas: Bir pod ele geçirildiğinde saldırganın cluster'ı crypto-miner'a çevirmesini istemiyorsanız CPU ve Memory limitlerini belirleyin.
- Kube-Bench: Düzenli olarak CIS benchmark testlerini çalıştırın. Eksiklerinizi yüzünüze çarpar, iyidir.
Unutmayın, Kubernetes sizi hızlandırır ama yanlış yapılandırırsanız uçuruma daha hızlı gitmenizi sağlar. Güvenliği sonradan eklenen bir yama değil, mimarinin temeli olarak görün.
Bir sonraki yazıda 'Cloud-Native' dünyasının karanlık dehlizlerine inmeye devam edeceğiz. O zamana kadar loglarınıza sahip çıkın, anomalileri kaçırmayın!
