Merhaba,
İlk bölümde ağırlıklı olarak Pod güvenliğine ve Pod Security Policy kavramına değinmiştik. İkinci bölümde ilk olarak namespace kavramına değindikten sonra Network tarafını inceleyeceğiz.
Namespaces
Bir namespace, Kubernetes objelerini mantıksal olarak kümelere bölmemizi sağlar. Buradaki amaç, farklı takımların kendilerine ait kaynaklarını bir başka takımın erişememesidir. Elbette bu namespace haricinde farklı auuthentication yöntemleriyle de yapılabilir ama namespace bu izolasyonu sağlayabileceğimiz en pratik yoldur.
Namespace’ler kendi içlerinde de label yardımıyla bir kullanıcı veya Pod atamasına yönelik özelleştirilebilir. Varsayılan olarak üç namespace bulunur ve bu namespace’ler silinemez:
- default (Kullanıcının oluşturduğu kaynakları tutar)
- kube-public (Public kaynaklar tutulur)
- kube-system (Kubernetes bileşenlerini içerir)
Ekran görüntüsünden aradaki farkı daha iyi anlayabilirsiniz.
default namespace’i hariç, oluşturulan herhangi bir nesne diğer iki varsayılan namespace’de tutulmamalıdır. Farklı namespace’ler içerisinde bulunan Pod ve(ya) servisler, herhangi bir engelleyici kural oluşturulmadığı sürece birbirleriyle iletişim kurabilirler.
Bir namespace oluşturmanın iki yolu vardır, birincisi tek komut olarak şu şekildedir:
kubectl create namespace namespace_adı
İkinci yol ise YAML dosyası kullanmaktır, örnek olarak:
apiVersion: v1
kind: Namespace
metadata:
name: test
Yeni bir kaynak oluştururken namespace özelleştirmesi aynı şekilde metadata altında yapılabilir veya komut tarafında ise —namespace veya -n parametresiyle hedef namespace belirtilebilir.
Network Policies
Networking, birden çok uygulamanın veya nesnenin birbirleriyle haberleştiği hemen hemen her mimaride ayrı bir önem arz etmektedir. Kubernetes dünyasında ise Podların, containerların, master ve worker node bileşenlerinin birbirleriyle olan iletişimi sebebiyle bir saldırgan gözünden bakıldığında olası zayıf noktalar bir şekilde dikkat çekeceği için, cluster yöneticisi olarak network düzeyinde gerekli yapılandırmaları ve kuralları uygulamak oldukça önemlidir.
Bir network policy, cluster üzerindeki trafiğin kural bazında yönetilmesine olanak sağlar. Farklı bir aksiyon alınmadığı sürece, varsayılan olarak herhangi bir network policy uygulanmaz.
Bir policy oluşturabilmek için en önemli şart, NetworkPolicy API’nı destekleyen bir network plugin’in kullanılması gerektiğidir.
Not: Kullanılan CNI plugin’i türüne göre Network Policy’nin kullanım formatı farklılık gösterebilir.
Örneklere başlamadan önce Policy Type kavramına değinelim. Bir Network Policy oluştururken iki seçeneğimiz bulunur:
ingress: Gelen trafiği kontrol eder. egress: Çıkan trafiği kontrol eder.
Lakin birazdan örneklerde de göreceğiniz üzere her iki tip de aynı Network Policy içerisinde bulunabilir. Bu type’lar, from ve to parametreleriyle kuralın hangi Pod veya Pod’lar üzerinden geleceği veya hangi Pod veya Pod’lara uygulanacağı konusunu belirler.
Bir diğer önemli bileşen de podSelector parametresidir. podSelector, kuralların uygulanacağı Pod veya Pod’ları belirler.
Şimdi sık kullanılabilecek bazı senaryoları örnekleyelim.
Gelen tüm trafiğe izin verilmesi:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-ingress
spec:
podSelector: {}
ingress:
- {}
policyTypes:
- Ingress
Gelen tüm trafiğin engellenmesi:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
Giden tüm trafiğe izin verilmesi:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-egress
spec:
podSelector: {}
egress:
- {}
policyTypes:
- Egress
Giden tüm trafiğin engellenmesi:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
spec:
podSelector: {}
policyTypes:
- Egress
Son olarak, yine pratik olarak kullanılabilecek bir diğer senaryo olan hem gelen hem de giden trafiğin engellenmesine bakalım.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Tüm bu işlemlerde podSelector parametresinin {} olarak ayarlandığına dikkat edin, bu şekilde Pod seçimi spesifik olmaktan çıkıp cluster’daki tüm Pod’lara yönelik olacaktır.
Şimdi biraz farklı örnekler yapalım.
Mesela, TCP protokolü için tüm Pod’ların 80. port’a erişimlerini bloklayalım.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all-egress
spec:
podSelector: {}
egress:
- to:
ports:
- protocol: TCP
port: 80
policyTypes:
- Egress
TCP haricinde aynı şekilde UDP protokolü de eklenebilir.
ports:
- protocol: TCP
port: 80
- protocol: UDP
port: 80
podSelector haricinde namespace üzerinden de hedef(ler) belirlenebilir. Mesela şu örneği inceleyelim:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all-egress
spec:
podSelector: {}
egress:
- to:
- namespaceSelector:
matchLabels:
namespace: test
podSelector:
matchLabels:
app: ssh
ports:
- protocol: TCP
port: 22
policyTypes:
- Egress
Buradaki kural tüm Pod’lara yönelik uygulanmıştır. Lakin hedef olarak sadece test namespace’i ve label’ı app=ssh olarak belirtilen Pod’lar seçilmiştir.
Bir başka örneği inceleyelim.
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-egress-test
namespace: test
spec:
podSelector:
matchLabels:
app: matrix
egress:
- to:
- ipBlock:
cidr: 172.17.0.0/24
Bu örnekte ise hedef olarak CIDR belirtilmiştir. ipBlock parametresiyle 172.17.0.0/24 bloğunda bulunan Pod’lara giden trafiğe izin verilmiştir.
Son olarak her iki tipi de içeren bir örnekle bu bölümü sonlandıralım.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: den1z
namespace: test
spec:
podSelector:
matchLabels:
app: matrix
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.18.0.0/24
- namespaceSelector:
matchLabels:
name: allowedns
- podSelector:
matchLabels:
app: allowedpod
ports:
- protocol: TCP
port: 22
egress:
- to:
- ipBlock:
cidr: 10.0.7.0/24
- namespaceSelector:
matchLabels:
name: targetns
- podSelector:
matchLabels:
app: targetpod
ports:
- protocol: TCP
port: 53
Ingress ve Egress aynı anda uygulanmış, Ingress için CIDR bloğu ve hedef Port olarak 22 verilerek namespace ve Pod label’ı özelinde kuralın kimlere uygulanacağı belirtilmiştir. Egress tarafında da aynı şekilde hem namespace hem Pod için selector kullanılmış, DNS portu belirtilerek sadece 10.0.7.0/24 bloğundaki Pod’lar kurala dahil edilmiştir.
Resource Policies
Namespace ve node düzeyinde limitlendirme işlemlerinde kullanılabilecek iki resource policy vardır:
- LimitRange
- ResourceQuota
LimitRange, herhangi bir namespace’deki Pod ve(ya) container’daki CPU ve disk kullanımlarını kısıtlamayı sağlar. Her bir namespace’e yönelik sadece bir LimitRange kısıtlayıcısı oluşturulabilir.
ResourceQuota ise namespace üzerindeki toplam CPU / RAM kullanımına yönelik kısıt oluşturmaya yarar. İki kısıtlayıcıyı da örneklemeden önce testlerde kullanmak üzere ‘test’ isminde yeni bir namespace oluşturalım:
kubectl create namespace test
Şimdi de cpu isminde bir dosya oluşturarak aşağıdaki gibi bir konfigürasyon sağlayalım. Minimum CPU isteğini 200 MB, maksimum 500 MB olarak belirleyelim.
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-min-max-demo
spec:
limits:
- max:
cpu: "500m"
min:
cpu: "200m"
type: Container
k apply -f cpu --namespace=test
Not: k komutunu kubectl’in alias’ı olarak atadığım için örneklerde kestirme olarak bu şekilde kullanıyorum. Siz de aynı şekilde kullanmak istiyorsanız aşağıdaki komutu girebilirsiniz:
alias k=kubectl
Bu kuralı namespace özelinde spesifik hale getirdiğimize dikkat edin. Şimdi aynı test namespace’i üzerinde CPU limitlerini aşarak yeni bir Pod oluşturmayı deneyelim.
apiVersion: v1
kind: Pod
metadata:
name: constraints-cpu-demo
namespace: test
spec:
containers:
- name: constraints-cpu-demo
image: nginx
resources:
limits:
cpu: "900m"
requests:
cpu: "700m"
Gördüğünüz gibi maximum CPU limitini 500 MB olarak ayarladığımız için, Pod tarafında 900M limitini kullanmamıza izin vermedi ve Pod’u oluşturmadı. Pod limitini 499M olarak, requests limitini de 400M şeklinde güncelleyelim ve tekrar deneyelim.
k describe po ...
komutu ile incelediğimizde limitleri görebiliyoruz.
Şimdi de ResourceQuota kullanarak bir namespace üzerindeki tüm containerların kullanabileceği toplam CPU ve RAM miktarını kısıtlayan bir örnek yapalım. İlk olarak ResourceQuota nesnesini oluşturalım ve önceden oluşturduğumuz namespace’i kullanalım.
apiVersion: v1
kind: ResourceQuota
metadata:
name: mem-cpu-test
namespace: test
spec:
hard:
requests.cpu: "1"
limits.cpu: "2"
requests.memory: 700M
limits.memory: 1Gi
Şimdi de iki ayrı Pod konfigürasyonu oluşturalım ve limitler toplamının oluşturduğumuz kuralı geçmesine göre ayarlayalım.
1. Pod
apiVersion: v1
kind: Pod
metadata:
name: quota-mem-cpu-test
namespace: test
spec:
containers:
- name: quota-mem-cpu-test
image: nginx
resources:
limits:
memory: "400Mi"
cpu: "400m"
requests:
memory: "400Mi"
cpu: "400m"
2. Pod
apiVersion: v1
kind: Pod
metadata:
name: quota-mem-cpu-test2
namespace: test
spec:
containers:
- name: quota-mem-cpu-test2
image: nginx
resources:
limits:
memory: "700Mi"
cpu: "700m"
requests:
memory: "700Mi"
cpu: "700m"
Şimdi beklentimiz şu; bu Pod’ları ayrı ayrı oluşturmamız durumunda herhangi bir hata almamamız gerekiyor çünkü kendilerine ait olan limit değerleri kuralı geçmiyor, fakat biz art arda oluşturmayı deneyerek hata almayı bekleyeceğiz.
Evet, tam olarak istediğimiz şekilde 2. Pod’u oluştururken hata aldık. Burada dipnot edilmesi gereken nokta, az önce oluşturuduğumuz LimitRange nesnesinin öncelikli olarak çalıştığıdır, yani ResourceQuota sınırlamasına gelmeden önce şayet talep edilen kaynak miktarı LimitRange kuralının üzerindeyse hata alırsınız, bu yüzden:
k delete -f dosya_adı -n test
komutu ile LimitRange’i silebilirsiniz.
Bu başlıkta önerilen aksiyonları özetlemek gerekirse:
- NetworkPolicy API’nı destekleyen bir CNI plugin’i kullanın.
- Policyleri podSelector ve(ya) namespaceSelector ile uygulayın.
- Gelen ve giden trafiği engellemek için bir ‘default’ policy kullanın.
- Bir namespace veya Pod düzeyinde kaynak limitlemek için LimitRange ve ResourceQuota policylerini kullanın.
kube-apiserver Güvenliği
kube-apiserver, bir önceki bölümde de değindiğimiz gibi çekirdek bileşenlerden birisi ve yanlış - eksik yapılandırmadan dolayı API üzerinden saldırganların cluster erişimini ele geçirmesi, potansiyel atak senaryolarından sadece bir tanesidir.
Bir önceki yazıda da bahsettiğim cryptojacking saldırısıyla yaklaşık 3.5 sene önce Tesla’nın başı bu konuda yandığı için, bilmeyenlerin ilgisini çeker diye kaynak paylaşıyorum:
Yine önceki yazıda da belirttiğim CIS’in yayınladığı benchmark tavsiyelerini kube-apiserver üzerinde uygulayabilirsiniz.
Bu işlemleri uygulamak için /etc/kubernetes/manifests/kube-apiserver.yaml dosyasını uygun şekilde değiştirdikten sonra yeni konfigürasyonun uygulanması için kubelet servisini yeniden başlatabilirsiniz.
CIS No | Title | Varsayılan |
---|---|---|
1.2.1 | —anonymous-auth parametresi kullanılmamalı ya da ‘false’ olarak set edilmelidir | Aktif |
1.2.2 | —basic-auth-file parametresi kullanılmamalıdır | Aktif |
1.2.3 | —token-auth-file parametresi kullanılmamalıdır | Aktif |
1.2.4 | —kubelet-https parametresi kullanılmamalı ya da ‘true’ olarak set edilmelidir | Aktif |
1.2.5 | –-kubelet-client-certificate ve —kubelet-client-key parametreleri uygun şekilde set edilmelidir | Aktif |
1.2.6 | -–kubelet-certificate-authority parametresi uygun şekilde set edilmelidir | Pasif |
1.2.7 | –-authorization-mode parametresi AlwaysAllow olarak set edilmemelidir | Aktif |
1.2.8 | -–authorization-mode parametresi Node’u kapsamalıdır | Aktif |
1.2.9 | -–authorization-mode parametresi RBAC’ı kapsamalıdır | Pasif |
1.2.10 | Admission control plugin’i EventRateLimit set edilmelidir | Pasif |
1.2.11 | Admission control plugin’i AlwaysAdmit kullanılmamalıdır | Pasif |
1.2.12 | Admission control plugin’i AlwaysPullImages kullanılmamalıdır | Pasif |
1.2.13 | PSP kullanılmıyorsa, admission control plugin’i SecurityContextDeny set edilmelidir | Pasif |
1.2.14 | Admission control plugin’i ServiceAccount set edilmelidir | Pasif |
1.2.15 | Admission control plugin’i NamespaceLifecycle set edilmelidir | Pasif |
1.2.16 | Admission control plugin’i PodSecurityPolicy set edilmelidir | Pasif |
1.2.17 | Admission control plugin’i NodeRestriction set edilmelidir | Aktif |
1.2.18 | —insecure-bind-address parametresi kullanılmamalıdır | Aktif |
1.2.19 | -–insecure-port parametresi 0 olarak set edilmelidir | Aktif, 0 |
1.2.20 | -–secure-port parametresi 0 olarak kullanılmamalıdır | Aktif, 6443 |
1.2.21 | -–profiling argument parametresi ‘false’ olarak set edilmelidir | Pasif |
1.2.22 | ––audit-log-path parametresi set edilmelidir | Pasif |
1.2.23 | -–audit-log-maxage parametresi ‘30’ veya bir başka uygun değere set edilmelidir | Pasif |
1.2.24 | -–audit-log-maxbackup ‘10’ veya bir başka uygun değere set edilmelidir | Aktif |
1.2.25 | —audit-log-maxsize ‘100’ veya bir başka uygun değere set edilmelidir | Aktif |
1.2.26 | -–request-timeout parametresi uygun bir değere set edilmelidir | Aktif |
1.2.27 | -–service-account-lookup parametresi ‘true’ olarak set edilmelidir | Pasif |
1.2.28 | —service-account-key-file parametresi uygun bir değere set edilmelidir | Aktif |
1.2.29 | ––etcd-certfile ve —etcd-keyfile parametreleri uygun değerlere set edilmelidir | Aktif |
1.2.30 | ––tls-cert-file ve -–tls-private-key-file parametreleri uygun değerlere set edilmelidir | Aktif |
1.2.31 | ––client-ca-file uygun bir değere set edilmelidir | Aktif |
1.2.32 | ––etcd-cafile argument uygun bir değere set edilmelidir | Aktif |
1.2.33 | ––encryption-provider-config parametresi uygun bir değere set edilmelidir | Pasif |
1.2.34 | Encryption sağlayıcıları konfigüre edilmelidir | Pasif |
1.2.35 | API Server sadece güçlü kriptografik cipher’lar kullanmalıdır | Pasif |
etcd Güvenliği
etcd, cluster’a ait durum bilgisini ve secret’ları muhafaza eder, bu durum da kendisini güvenlik açısından son derece önem arz eden bir konuma getirir. Bir şekilde etcd erişimini ele geçiren bir saldırgan, tüm cluster düzeyinde yetki sağlayabilir ki böyle bir durumda secret’ları ve diğer hassas verileri de okuyabilir. etcd’nin sadece API server üzerinden bir yetkilendirme mekanizması doğrultusunda erişilebilmesi önerilmektedir. Alınabilecek aksiyonlardan birisi, etcd ve API server arasındaki iletişimin TLS sertifikaları ile HTTPS protokolüne zorunlu olarak yönlendirilmesidir.
Kubernetes, varsayılan kurulumda etcd için gerekli olan TLS encryption’ı sağlamaktadır. Process kontrolü ile üç parametreyi kontrol etmeniz gereklidir:
- —cert-file=…
- —client-cert-auth=…
- —key-file=…
ps aux | grep etcd komutuyla kontrol sağlayalım.
Görüldüğü gibi etcd için TLS sağlanmış durumda. (Görüntüyü yeni sekmede açıp kontrol edebilirsiniz, some resolution problems…)
Not: Benim örnekte kullandığım cluster Minikube ile oluşturulduğu için, /var/lib/minikube/… dizinini göstermektedir. kubeadm veya bir başka araç ile oluşturulan cluster’lar için bu dizinlerin yolu farklılık gösterebilir.
Bu noktada Kubernetes’in önerdiği ek güvenlik önlemlerinden birisi de verilere encryption uygulamaktır. Varsayılan ayarlarda bu güvenlik katmanı uygulanmaz, kube-apiserver parametrelerini kontrol ederek —encryption-provider-config parametresinin bulunmadığını görebilirsiniz.
Şimdi bir EncryptionConfiguration nesnesi oluşturarak ilgili parametreyi bu nesneyi işaret edecek şekilde set edeceğiz.
İlk olarak 32 bytelık bir random anahtar oluşturarak base64 ile encode edelim.
head -c 32 /dev/urandom | base64
Çıkan değeri kullanarak bir konfigürasyon oluşturalım. Ben /mnt dizini altında encry.yaml adında bir dosya oluşturdum.
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key
secret: QsGpzJCzuuBXhw8/6QtmRGujD4VAcsnyzE8Lhakfhn0=
- identity: {}
Şimdi oluşturduğumuz bu yapılandırmayı —encryption-provider-config parametresiyle kube-apiserver üzerinde set edelim. Bunun için ilk olarak /etc/kubernetes/manifests/kube-apiserver.yaml dosyasını açıyoruz.
—encryption-provider-config=…
parametresini dosyayı oluşturduğunuz dizine set ettikten sonra API server’ı yeniden başlatıyoruz. Bunun için kubelet servisini yeniden başlatabilir veya kube-apiserver.yaml dosyasını silebilirsiniz, otomatik olarak tetiklenecektir.
kubeconfig dosyasının Güvenliği
Daha önce Kubernetes ile herhangi bir şekilde vakit geçirdiyseniz, kubeconfig dosyasının cluster’ın temel yapıtaşlarından birisi olduğunu zaten fark etmişsinizdir. Bu dosya, cluster veya cluster’lara ait yetkilendirme parametreleri, kullanıcı, endpoint, namespaces gibi önemli bilgileri içerir ve varsayılan olarak kullanıcının ev dizini altındaki .kube klasöründe tutulur.
Ek bir güvenlik önleminin alınmadığı bir senaryoda, kubeconfig dosyasını ele geçirmiş bir saldırgan, cluster’a erişim sağlayabilir. Bu durumda konu Kubernetes özelinden çıkıp belki biraz daha Linux tarafında değerlendirilebilir fakat Cloud sağlayıcıların da kubeconfig dosyasının yetkilendirilmesiyle ilgili sundukları çözümler bulunmaktadır. Olaya Linux tarafından baktığımızda, en basitinden chmod ile dosya ve dizin yetkileri kontrol edilebilir.
TLS Bootstrapping
Control plane güvenliğindeki aşamalardan birisi de TLS Bootstrapping’tir. Bir cluster’ın TLS 1.2 ya da TLS 1.3 ile birlikte kullanılması önerilmektedir. Bu işlemleri bu yazıda göstermeyeceğim ama kaynak olarak Kubernetes’in dokümantasyonunu veya Kubernetes The Hard Way reposunu inceleyebilirsiniz.
Secrets
Bir secret; parola, OAuth token, SSH anahtarı gibi nesnelerin bilgilerini tutar. Hassas verileri bir YAML konfigürasyonunda, Dockerfile içerisinde veya çevresel değişken olarak plain text şeklinde saklamaktansa, secret nesnesini kullanırız. Varsayılan olarak secret bilgileri base64 olarak encode edilmiş string’ler şeklinde tutar. Bu bağlamda secret erişimlerinin RBAC ile yetkilendirilmesi veya secret’lara yönelik encryption uygulanması önerilir.
Encryption; API server üzerinden veya cloud sağlayıcılarında da bulunan bir Key Management Service (KMS) ile sağlanabilir.
API Server örneği için etcd başlığındaki işlemi takip edebilirsiniz. KMS ise hemen hemen aynı şekilde yapılır, aradaki fark olarak provider’ı kms’i kullanarak endpoint & cachesize & timeout parametrelerini belirtmek gerekir.
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- kms:
name: myKmsPlugin
endpoint: unix:///tmp/socketfile.sock
cachesize: 100
timeout: 3s
- identity: {}
Aynı şekilde —encryption-provider-config parametresi API Server için set edildikten sonra yeniden başlatılması gerekir.
- bölümde görüşmek üzere.