NSA Kubernetes Hardening Notları - 1. Bölüm

August 07, 2021

Merhaba,

NSA, 3 gün önce Kubernetes’e yönelik bir Hardening kitapçığı yayınladı. Genel olarak kendi alanımla ilgili uygulama ve yapıların güvenlik sıkılaştırmalarına yönelik özel bir ilgim olduğu için bu kaynaktaki bazı noktalara değinmek istedim.

İlk olarak şunu belirtmek gerekiyor; bu Kubernetes’e ait ilk hardening kaynağı değil, birkaç sene önce CIS de bir benchmark yayınlamıştı, tabii o kaynak teorik bilgiden çok parametrelerin kullanımına ve sistem tarafındaki konfigürasyona değiniyor ama genel olarak ikisinin de aynı amaca hizmet ettiğini söyleyebiliriz.

Hatta yaklaşık 4 sene önce CIS’ın bu kaynağını baz alarak hazırladığım bir script de vardı, maalesef yarıda bırakmıştım, yine de merak edenler için linki bırakayım:

Zephyrus

Bu yazı veya yazı dizisinde Kubernetes’in mimarisine veya giriş seviyesindeki bilgilere derinlemesine değinmeyeceğim, niyetim doğrudan hardening tarafına odaklanmak ve bunu yaparken NSA’in yayınladığı kaynağı kendimce yorumlayıp örneklerle aktarmaya çalışacağım.

İlk olarak çok kısa da olsa mimariden bahsedelim.

Bir Kubernetes cluster’ı, master ve worker node’lardan oluşmaktadır, bu node’lar fiziksel veya sanal makineler olabilir. Pod dediğimiz yapı(lar), worker node’lar içerisinde bulunur. Her bir Pod da uygulamaların çalıştığı container’ları host eder.

Cluster’ın beyni olarak nitelendirebileceğimiz Control Plane, şu yapıları içerir:

  • Controller manager - Varsayılan port numarası 10252
  • Cloud controller manager - Varsayılan port numarası 10258
  • API Server - Varsayılan port numaraları 6443 veya 8080
  • etcd - Varsayılan port numaraları 2379 ve 2380
  • kube-scheduler - Varsayılan port numarası 10251

Worker node’lar ise şu servislere sahiptir:

  • Kubelet
  • kube-proxy

k

Cluster’lar fiziksel veya sanal makinelerde oluşturulabileceği gibi, aynı zamanda Cloud sağlayıcıları üzerinde de host edilebilir ki zaten siber güvenlik perspektifinden baktığımızda saldırganların en çok hedef aldığı cluster yapıları genellikle bir cloud sağlayıcısında (CSP) bulunmaktadır.

Bu noktada elbette söz konusu CSP’nin kendisine ait sıkılaştırma işlemleri ayrı bir başlık konusudur.

Docker ve Kubernetes ortamlarına yönelik saldırılar, özellikle son iki senede büyük bir artış gösterdi. Burada sadece container uygulamalarına yönelik exploitlerden bahsedemiyoruz çünkü yanlış yapılandırma, sağlayıcı tarafında ele geçirilen bir birim üzerinden yetki yükseltme gibi senaryolar da azımsanamayacak kadar fazla.

Söz konusu Supply Chain Attack olduğunda, atak vektörleri ve hedef yapıların değişkenliğinden dolayı güvenliği tek bir noktaya bağlı olarak düşünmemek gerekmektedir. Mesela container düzeyindeki güvenlik açıkları, birden çok sebeple oluşabilir; kullanılan bir outdated paket & uygulama & kütüphane dosyası, eksik firewall yapılandırması, hatalı konfigürasyon vs. saldırganın en küçük birimden başlayıp Cluster seviyesine kadar gelebileceği bir zafiyet oluşturabilir.

Altyapı tarafında ise kümeyi biraz daha geniş tutabiliriz çünkü bu aşamada kullanılan ortamın fiziksel, sanal, hibrit veya cloud üzerinde yer almasına yönelik güvenlik önlemleri de değişkenlik gösterecektir. Örnek olarak, saldırganın herhangi bir yolla bir Linux sunucusunda yetki sağlaması, aynı şekilde cluster güvenliğini de tehlikeye atacaktır. Bu yüzden Kubernetes güvenliği tek aşamalı olarak düşünülmemeli, mimariye bir bütün olarak bakılmalıdır. Elbette burada sadece donanımsal veya yazılımsal bir güvenlik problemi olacak diye bir şey de yok, sosyal mühendislik ve ‘insan’ faktörü oldukça her an tetikte olmak zaruridir.

Pod Güvenliği

Pod’u Kubernetes mimarisindeki en küçük birim olarak özetleyebiliriz. Bir pod birden çok container içerebileceği gibi bu container’lar da birden fazla uygulamaya / mikro servise sahip olabilir. Bir saldırgan gözünden baktığımızda herhangi bir şekilde bir container’ın exploit edilmesi, mimariye göre yatay ve(ya) dikey olarak yetkilendirmeye yol açabilir, bu da gerekli izolasyonun sağlanmadığı bir senaryoda bir Pod’un ele geçirilmesinin aslında Cluster’ın bütününe etki edecek bir problem oluşturabileceğini göstermektedir.

Non-root container kavramı

Docker üzerinden konuşacak olursak, varsayılan olarak imajlar root kullanıcısına yönelik hazırlanmış olabilir. Bir Dockerfile içerisinde USER direktifiyle kullanıcı tanımladığımızda root yerine o kullanıcı üzerinden container oluşturulur, bu da en basit haliyle yetkinin kısıtlanması anlamına gelmektedir.

Bir busybox imajını herhangi bir ayar yapmadan doğrudan çalıştıralım ve kontrol sağlayalım:

1

Şimdi de bir Dockerfile oluşturarak yine aynı busybox imajını kullanalım fakat bu sefer ‘non-root’ olarak ilerleyelim.

FROM busybox
RUN adduser --uid 700 --shell bin/sh --skel /dev/null deniz --disabled-password
USER deniz
CMD ps aux

2

Bu işlemin Kubernetes tarafındaki karşılığı ise bir SecurityContext oluşturmaktır. runAsUser parametresiyle tanımlanan kullanıcı seçilerek, bir Pod içerisinde çalıştırılacak container root yetkisi olmadan kullanılabilir.

Görüldüğü gibi Dockerfile ile bu işlemi yapmak oldukça basit fakat bunu takip eden problem şu: her seferinde kendi imajımızı mı oluşturacağız veya kullandığımız imajı düzenlemek / kontrol etmek mi zorundayız? Hayır. Bu noktada birazdan Kubernetes’e dönüp Pod Security Policy konusuna bir göz atacağız ama öncelikle rootless kavramına da değinelim.

Rootless Container Engine kavramı

Özellikle ilk kurulumlarda ve sonrasında Docker’ın genellikle normal bir kullanıcı yerine root kullanıcısıyla çalıştırıldığını söyleyebiliriz.

Docker daemon’ı varsayılan olarak 2375 port’unu kullanmaktadır. Atak vektörü olarak düşünmemiz gereken noktalar ise; çeşitli platformlarda veya bir script / tool yardımıyla yapılacak tarama işlemlerinde, API’ın çalıştığı adresler expose edilebilir ve hassas verilerin ele geçirilmesi, cryptojacking saldırıları, container’ların çalıştığı internal network’lere pivoting saldırıları gibi çeşitli senaryoların yaşanabilmesi ihtimalidir.

Bu noktada özellikle cryptojacking konusuna ayrı bir parantez açmak isterim zira eski eğitimlerimde de değindiğim konulardan birisiydi, henüz büyük çapta saldırılar yaşanmamışken üzerine düşündüğüm ve potansiyel saldırı senaryoları oluşturduğum bir konuydu. İlk büyük saldırıyı 2018 yılında okumuştum, birkaç ay önce de 20 milyondan fazla indirme almış bazı Docker imajlarının cryptojacking saldırılarına hizmet ettiği ortaya çıkmıştı.

Örnek Cryptojacking saldırısı

Tekrar rootless mantığına dönelim.

Mevcut mimaride namespaces, cgroups, SELinux, seccomp, AppArmor gibi izolasyon sağlayan yapı ve uygulamalar bulunuyor, lakin root kullanıcısıyla çalışmak her halukarda potansiyel bir exploitation’a davet çıkarabilir desek yanlış olmaz.

Mesela runc’de bu tarz bir zafiyet yayınlanmıştı.

CVE-2019-5736

Docker, 19.03 sürümüyle birlikte rootless modunu bir nevi beta sürümü gibi kullanıma sunmuştu fakat burada bazı problemler mevcuttu; mesela cgroups ve OverlayFS desteklenmiyordu, minor release’lerle birlikte buradaki uyumsuzluklar da giderildi.

Rootless modunu Docker ile birlikte kullanmak için bir script yayınlandı, ayrıca 20.10 sürümünden itibaren paket kurulumuyla da geliyor. Şimdi root kullanıcısı ve sudo yetkisi olmadan bir Docker kurulumu yaparak bu bölümü bitirelim.

Öncelikle yeni bir kullanıcı oluşturalım.

adduser rootless

Docker’ın yayınladığı script’i indirip çalıştırıyoruz.

curl -sSL https://get.docker.com/rootless | sh

12

Gerekli olan çevresel değişkenleri set edelim.

export PATH=/home/rootless/bin:$PATH
export PATH=/home/rootless/bin:$PATH
export XDG_RUNTIME_DIR=/home/rootless/.docker/run

Not: Bu değişkenleri boot sonrası tekrar set etmemek için ~/.bashrc dosyasına yazabilirsiniz.

Son olarak herhangi bir imajı indirebilir veya shell alarak container çalıştırabilirsiniz.

13

rootless kavramının Kubernetes tarafındaki implementasyonu için usernetes projesini inceleyebilirsiniz.

Usernetes

Pod Security Policy

Kısaca bir PSP, Cluster’daki Pod’lara yönelik güvenlik mekanizmaları oluşturmamızı sağlar. Tüm detaylara girmeyeceğim, yine bir Pod’a yönelik yetki kısıtlama konusu üzerinden devam edelim ve bir önceki yaptığımız örneği bu sefer PSP ile gerçekleştirelim.

Öncelikle kullandığımız nsa isimli imaj üzerinden bir Deployment oluşturduğumuzda neler olduğunu görelim.

3

4

apiVersion: v1
kind: Pod
metadata:
name: psp
spec:
securityContext:
runAsUser: 700
containers:
- name: psp
  image: nsa
  command: ["sh", "-c", "sleep 700"]
  securityContext:
  allowPrivilegeEscalation: false
  imagePullPolicy: Never

Bir securityContext oluşturarak, Dockerfile üzerinde UID’sini 700 olarak tanımladığımız deniz isimli kullanıcı üzerinden yetkilendirme verdik, bunu da runAsUser parametresiyle belirttik. allowPrivilegeEscalation ile ek bir güvenlik katmanı oluşturulduğuna dikkat edin.

5

exec ile whoami komutunu çalıştırarak veya bir shell alarak sağlamayı yapabiliriz.

6

NSA’in örnek olarak verdiği diğer PSP kullanımlarına da değinmek gerekirse:

privileged: Pod’ların yetkilendirilmiş bir container’ı çalıştırıp çalıştıramayacağını belirler, false olarak kullanılması önerilir.

hostPID, hostPC: Container’ların host namespace’i ile bir süreç (process) paylaşımında bulunup bulunmayacağını belirler, false olarak kullanılması önerilir.

hostNetwork: Container’ların host ağını kullanıp kullanmayacağını belirler. false olarak kullanılması önerilir.

allowedHostPaths: Container’ların host dosya sistemine yönelik erişimini kısıtlamayı sağlar. Bu noktada host tarafında yeni bir dizin oluşturularak sadece salt okunur (read-only) izni verilmesi önerilir.

readOnlyRootFilesystem: Salt okunur root dosya sistemi kullanılmasını sağlar. Şayet kullandığınız ortam ve uygulamalar buna müsaitse ‘true’ olarak set edilmesi önerilir.

runAsUser, runAsGroup, supplementalGroups, fsGroup: Container’daki uygulamaların root veya root grubu yetkisiyle kullanılıp kullanılmayacağını belirler. root yerine ‘normal’ haklara sahip olan bir kullanıcı ve(ya) grubun kullanılması önerilir.

allowPrivilegeEscalation: Kullanıcı yetkilerinin root yetkisine yükseltilip yükseltilemeyeceğini kontrol eder. false olarak kullanılması önerilir.

seLinux: SElinux kurallarının hedef container’a uygulanıp uygulanmayacağını belirler. Şayet kullanılan baz imaj SElinux’u destekliyorsa, SElinux context’leri oluşturulabilir.

AppArmor parametreleri: AppArmor profilleri container’lara set edilebilir. Bu konuyla ilgili olan yazıma da bakabilirsiniz:

Kubernetes & AppArmor

seccomp parametreleri: sandbox container’ları için seccomp profilleri oluşturulabilir. seccomp’u SElinux’un bir varyasyonu gibi düşünebiliriz.

Oluşturulan PSP sınırlandırmaları iki nedenden dolayı tüm Cluster seviyesinde uygulanmaz:

  • Bir PSP uygulanmadan önce PodSecurityPolicy plugin’i aktif hale getirilmelidir.
  • Oluşturulan policy, RBAC üzerinden yetkilendirilmiş olmalıdır. Bu sayede Cluster’dan sorumlu olan kişi veya kişiler PSP’lerin etki alanını gözlemleyebilir.

NOT: PSP, 1.21 sürümüyle deprecated olarak işaretlenmiştir. Kubernetes’in 1.25 sürümüyle birlikte tamamen kaldırılacak, onun yerine geçici olarak PSP Replacement Policy şeklinde isimlendirilen bir mekanizma kullanılacaktır.

Salt okunur dosya sistemleri

Varsayılan olarak container’lardaki dosya sistemlerinde ek bir güvenlik önlemi bulunmaz. Yani bir container’ı ele geçiren bir saldırgan dosya / klasör oluşturma, şayet dış dünyaya erişimi varsa herhangi bir uygulama / script indirme, sistem dosyalarında değişiklik yapabilme gibi ciddi güvenlik problemleri oluşturabilecek eylemler yapabilir. Siber güvenlik dünyasında Post-exploitation olarak adlandırdığımız bu aşamayı engellemek için alınabilecek bazı önlemler vardır.

Yalnız burada dikkat edilmesi gereken bir nokta da şudur, dosya sistemi üzerinde kısıtlamalara gitmek, container’da bulunan uygulamaların çalışmasında bir uyumsuzluk - problem de oluşturabilir. Böyle bir senaryonun yaşanmaması için developer’larla birlikte hareket ederek container’da çalışan servislerin tam olarak ne yaptığı, sistem üzerinde nelere etki ettiği, özellikle dosya sistemi veya disk üzerinde herhangi bir işlem yapıp yapmadığı sorgulanmalı ve ancak bu sorular cevaplandıktan sonra gerekli önlemler alınmalıdır.

Bununla ilgili bir Deployment üzerinden örnek yapalım.

7

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: rofs
  name: rofs
spec:
  selector:
    matchLabels:
      app: rofs
  replicas: 1
  template:
    metadata:
      labels:
        app: rofs
    spec:
      containers:
        - command: ["sleep"]
          args: ["700"]
          name: nginx
          image: nginx
          securityContext:
            readOnlyRootFilesystem: true
          volumeMounts:
            - name: ro
              mountPath: /var/run
      volumes:
        - name: ro
          emptyDir:

Beklentimiz şu: /var/run dizini haricinde herhangi bir yerde herhangi bir nesne oluşturulmamalı, kontrol edelim.

8

Görüldüğü gibi, root kullanıcısında olmamıza rağmen /mnt ve hatta /tmp dizininde dahi boş bir dosya veya klasör oluşturamadık, fakat mountPath olarak belirttiğimiz /var/run dizininde yazım işlemi yapabiliyoruz.

İmajların güvenli hale getirilmesi

Docker kullanıcıları genellikle bir repository belirtilmediği sürece Docker Hub üzerinden imajlar çeker ve imajla ilgili herhangi bir araştırma yapmadan kullanır. Elbette official olarak tanıtılan imajların güvenilebilirliği konusunda bir şüphemiz olmasa da, public olarak yayınlanan, şahıslara bağlı imajlara yönelik aynı düşüncelerde bulunmak her zaman kolay değil. Diyebilirsiniz ki resmi imajların %100 güvenli olduğunu neye dayanarak söylüyorsunuz? Aslında hiçbir zaman %100 güvenlikten söz edemeyiz. Bir gün Docker sunucularının hacklenip, official imajların zararlı yazılım içeren imajlarla değiştirilmeyeceğinin veya bir Docker çalışanının cinnet geçirip aynı eylemi içeriden yapmayacağının garantisini kim verebilir ki?

2017’de yaşanan CCleaner olayını hatırlayanlar vardır, internal network’e erişim sağlayan saldırganlar, uygulamaya geçerli bir Avast sertifikası vesilesiyle malware yerleştirerek 2 milyondan fazla kişiye zararlı yazılım bulaşmasına neden olmuştu.

CCleaner Malware Attack

Biraz daha olası ihtimallere dönmek gerekirse, imajların oluşturulma aşamasında güvenliklerinin sağlanması, paketlerin güncel tutulması, mümkün olduğunca minimal distro’lar kullanılması ve imajların scan edilmesi önemlidir.

Bir Dockerfile oluştururken güvenlik açısından nelere dikkat edilmeli? sorusunun cevabına ise bir başka yazıda değinmeyi planlıyorum.

Service Account Güvenliği

Normalde bir Pod oluşturulduğunda, aksi belirtilmediği sürece bir Service Account secret token ile birlikte mount edilir. Burada sorulması gereken soru şudur: container’daki uygulamaların bu Service Account’a erişiminin olması gerekiyor mu? Eğer doğrudan bir erişim gereksinimi söz konusu değilse, automountServiceAccountToken parametresi false olarak tanımlanmalıdır.

Örnek olarak herhangi bir Pod’a bakalım, token ve mount ile ilgili bölümü görebiliyoruz:

9

Şimdi de yeni bir Pod oluşturalım ve Service Account token’ının mount edilmesini devre dışı bırakalım.

10

apiVersion: v1
kind: Pod
metadata:
  name: sa
spec:
  serviceAccountName: test
  automountServiceAccountToken: false
  volumes:
    - name: test
      emptyDir: {}
  containers:
    - name: test
      image: nginx
      volumeMounts:
        - name: test
          mountPath: /usr/share/nginx/html

Oluşturduğumuz Pod’a token’ın mount edilmemesi gerekiyor, kontrol sağlayalım.

11

Container Engine Güvenliği

Container izolasyonu için hypervisor katmanı da kullanılan uygulamalara göre sıkılaştırılabilir. NSA, kaynakta Hyper-V’yi örnek göstermiş, mesela Hyper-V üzerinden sağlanabilecek faydaları söylemek gerekirse:

  • Dedike edilmiş (dedicated) kernel kullanılabilir.
  • Uygulamaların kendi aralarındaki ve host işletim sistemine yönelik trust iletişimi kısıtlanabilir.
  • RAM, doğrudan Hyper-V tarafından atanır, bu da buffer overflow gibi ataklarda bir izolasyon sağlayabilir.

Windows üzerinde çalıştırılacak container uygulamaları, şayet Hyper-V desteği alınıyorsa özellikle Krypton ve Xenon tipindeki container’lar izolasyon ve kernel tarafında ekstra güvenlik sağlayacaktır.



Written by Deniz Parlak