CoreDNS Güvenlik Yapılandırması ve En İyi Pratikler
Kubernetes ortamlarında DNS güvenliği genellikle göz ardı edilen ama aslında saldırı yüzeyinin önemli bir parçasını oluşturan bir alan. Birkaç yıl önce bir müşteri ortamında yaşadığımız bir olay bunu somut hale getirdi: DNS cache poisoning ile başlayan bir saldırı zinciri, sonunda iç servislerin trafiğinin yanlış yönlendirilmesiyle noktalandı. O günden beri CoreDNS yapılandırmasını ciddiye alıyorum ve bu yazıda öğrendiklerimi paylaşmak istiyorum.
CoreDNS Güvenlik Açıklarını Anlamak
CoreDNS, Kubernetes’in varsayılan DNS çözümleyicisi olarak kullanılıyor ve bu durum onu kritik bir bileşen haline getiriyor. Ancak varsayılan kurulumun güvenlik açısından “yeterince iyi” olduğunu düşünmek büyük bir hata. Varsayılan Corefile yapılandırması işlevsellik odaklıdır, güvenlik odaklı değil.
Önce mevcut CoreDNS yapılandırmanızı gözden geçirelim:
# Mevcut CoreDNS ConfigMap'ini inceleyin
kubectl get configmap coredns -n kube-system -o yaml
# CoreDNS pod'larının durumunu kontrol edin
kubectl get pods -n kube-system -l k8s-app=coredns
# CoreDNS loglarına bakın
kubectl logs -n kube-system -l k8s-app=coredns --tail=100
Çıktıyı incelediğinizde büyük ihtimalle şu şekilde bir Corefile göreceksiniz:
.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
Bu yapılandırmada dikkat çeken birkaç sorun var. pods insecure direktifi pod kimlik doğrulaması yapmadan kayıt oluşturulmasına izin veriyor. forward . /etc/resolv.conf ile dış DNS sorgularını doğrulama yapmadan iletiyorsunuz. Ve cache 30 gibi kısa cache süreleri cache poisoning saldırılarına karşı daha az koruma sağlıyor.
Temel Güvenlik Yapılandırması
İlk adım olarak üretim ortamı için güvenlik odaklı bir Corefile hazırlayalım:
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
log . {
class error
}
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods verified
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . tls://8.8.8.8 tls://8.8.4.4 {
tls_servername dns.google
health_check 5s
max_concurrent 1000
}
cache 300 {
success 9984 300 60
denial 9984 5 1
prefetch 10 1m 10%
}
loop
reload
loadbalance round_robin
}
Bu yapılandırmada yaptığımız değişiklikleri açıklayayım:
pods verified: Pod kayıtları için kimlik doğrulaması zorunlu hale getirildi. insecure yerine verified kullanmak, herhangi bir pod’un keyfi DNS kayıtları oluşturmasını engelliyor.
forward . tls://…: DNS sorgularını DoT (DNS over TLS) ile şifreleyerek iletiyoruz. Düz metin UDP/TCP yerine TLS tüneli üzerinden gönderilen sorgular, man-in-the-middle saldırılarına karşı korumalı.
cache 300: Cache süresini 30 saniyeden 300 saniyeye çıkardık. Uzun cache süreleri hem performansı artırır hem de cache poisoning saldırılarının etkisini azaltır çünkü daha az dış sorgu yapılır.
denial cache: Negatif yanıtları da cache’liyoruz, bu sayede olmayan domain’ler için tekrarlanan sorgular önleniyor ve potansiyel bir DNS amplification vektörü kapatılıyor.
DNSSEC Yapılandırması
DNSSEC, DNS yanıtlarının imzalanarak doğrulanmasını sağlar. Özellikle dış DNS çözümlemesi için kritik bir güvenlik katmanı:
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
dnssec {
key file /etc/coredns/keys/Kcluster.local
}
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods verified
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
forward . tls://8.8.8.8 {
tls_servername dns.google
}
cache 300
loop
reload
loadbalance
}
DNSSEC anahtarları oluşturmak için:
# DNSSEC anahtar çifti oluşturun
dnssec-keygen -a ECDSAP256SHA256 -f KSK cluster.local
dnssec-keygen -a ECDSAP256SHA256 cluster.local
# Anahtarları Kubernetes secret olarak saklayın
kubectl create secret generic coredns-dnssec-keys
--from-file=Kcluster.local.key
--from-file=Kcluster.local.private
-n kube-system
# Secret'ı CoreDNS deployment'ına mount edin
kubectl patch deployment coredns -n kube-system --type=json -p='[
{
"op": "add",
"path": "/spec/template/spec/volumes/-",
"value": {
"name": "dnssec-keys",
"secret": {
"secretName": "coredns-dnssec-keys"
}
}
},
{
"op": "add",
"path": "/spec/template/spec/containers/0/volumeMounts/-",
"value": {
"name": "dnssec-keys",
"mountPath": "/etc/coredns/keys",
"readOnly": true
}
}
]'
Rate Limiting ve DDoS Koruması
DNS amplification saldırıları gerçek bir tehdit. CoreDNS’in yerleşik rate limiting’i sınırlı olsa da, iptables kuralları ve CoreDNS’in acl eklentisiyle katmanlı koruma sağlayabiliriz:
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
acl {
allow net 10.0.0.0/8
allow net 172.16.0.0/12
allow net 192.168.0.0/16
block
}
health {
lameduck 5s
}
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods verified
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
forward . tls://8.8.8.8 tls://8.8.4.4 {
tls_servername dns.google
max_concurrent 1000
}
cache 300
loop
reload
loadbalance
}
ACL bloğu, yalnızca RFC 1918 özel adres aralıklarından gelen sorguları kabul ediyor ve diğer her şeyi engelliyor. Kubernetes cluster içindeki trafik bu aralıklar içinde olacağından normal işleyiş etkilenmiyor.
Ek olarak, node seviyesinde iptables kuralları ekleyelim:
# CoreDNS portlarına dış erişimi kısıtlayın
# Yalnızca cluster iç trafiğine izin verin
iptables -A INPUT -p udp --dport 53 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 53 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p udp --dport 53 -j DROP
iptables -A INPUT -p tcp --dport 53 -j DROP
# Prometheus metrics endpoint'ini de koruyun
iptables -A INPUT -p tcp --dport 9153 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 9153 -j DROP
NetworkPolicy ile Erişim Kontrolü
Kubernetes NetworkPolicy kullanarak CoreDNS’e erişimi pod seviyesinde kontrol edebilirsiniz. Bu, hangi pod’ların DNS sorgulayabileceğini sınırlandırmanıza olanak tanır:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: coredns-network-policy
namespace: kube-system
spec:
podSelector:
matchLabels:
k8s-app: coredns
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
ports:
- protocol: TCP
port: 9153
egress:
- to:
- ipBlock:
cidr: 8.8.8.8/32
- ipBlock:
cidr: 8.8.4.4/32
ports:
- protocol: TCP
port: 853
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Bu policy ile:
- Ingress: Tüm namespace’lerden yalnızca DNS portlarına erişim izinli, Prometheus metrikleri ise sadece monitoring namespace’inden erişilebilir
- Egress: CoreDNS yalnızca Google DNS’e (DoT portu 853 üzerinden) ve cluster içi DNS sorgularına çıkış yapabilir
Özel Zone Yapılandırması ve Güvenlik
İç servisler için özel zone’lar kullandığınızda, bu zone’ların dışarıya sızmamasını sağlamak önemli. Örneğin bir fintech müşterisinin ortamında, iç API gateway’lerin DNS kayıtlarının dışarıdan çözümlenebildiğini fark etmiştik. Çözüm, zone transferlerini devre dışı bırakmak ve özel zone’ları açıkça tanımlamaktı:
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
cluster.local:53 {
kubernetes cluster.local {
pods verified
ttl 30
}
cache 300
errors
}
internal.company.com:53 {
file /etc/coredns/zones/internal.company.com.db
errors
log . {
class error
}
}
.:53 {
errors
acl {
allow net 10.0.0.0/8
allow net 172.16.0.0/12
block
}
forward . tls://8.8.8.8 tls://8.8.4.4 {
tls_servername dns.google
health_check 5s
}
cache 300 {
success 9984 300 60
denial 9984 5 1
}
loop
reload
loadbalance
}
İç zone dosyası için:
# /etc/coredns/zones/internal.company.com.db dosyası
$ORIGIN internal.company.com.
$TTL 300
@ IN SOA ns1.internal.company.com. admin.company.com. (
2024010101 ; Serial
3600 ; Refresh
900 ; Retry
86400 ; Expire
300 ; Minimum TTL
)
@ IN NS ns1.internal.company.com.
ns1 IN A 10.10.0.10
api IN A 10.10.1.100
db IN A 10.10.2.200
Pod Security Context ve RBAC Yapılandırması
CoreDNS pod’larının least privilege prensibiyle çalışması gerekiyor. Varsayılan deployment genellikle gereğinden fazla izinle çalışır:
apiVersion: apps/v1
kind: Deployment
metadata:
name: coredns
namespace: kube-system
spec:
replicas: 2
selector:
matchLabels:
k8s-app: coredns
template:
metadata:
labels:
k8s-app: coredns
spec:
serviceAccountName: coredns
tolerations:
- key: CriticalAddonsOnly
operator: Exists
nodeSelector:
kubernetes.io/os: linux
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: coredns
image: registry.k8s.io/coredns/coredns:v1.11.1
imagePullPolicy: IfNotPresent
resources:
limits:
memory: 170Mi
cpu: 500m
requests:
cpu: 100m
memory: 70Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
readOnlyRootFilesystem: true
ports:
- containerPort: 53
name: dns
protocol: UDP
- containerPort: 53
name: dns-tcp
protocol: TCP
- containerPort: 9153
name: metrics
protocol: TCP
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 5
readinessProbe:
httpGet:
path: /ready
port: 8181
initialDelaySeconds: 30
timeoutSeconds: 5
RBAC yapılandırması da güvenlik açısından kritik:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: coredns
rules:
- apiGroups:
- ""
resources:
- endpoints
- services
- pods
- namespaces
verbs:
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: coredns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: coredns
subjects:
- kind: ServiceAccount
name: coredns
namespace: kube-system
CoreDNS’in yalnızca ihtiyaç duyduğu kaynaklara list ve watch izni var, create, update, delete gibi yazma izinleri yok. Bu, ele geçirilmiş bir CoreDNS pod’unun cluster üzerindeki etkisini sınırlandırıyor.
Güvenlik Denetimi ve İzleme
Yapılandırmayı tamamladıktan sonra sürekli izleme şart. CoreDNS’in Prometheus metriklerini kullanarak anormal davranışları tespit edebilirsiniz:
# CoreDNS metriklerini kontrol edin
kubectl port-forward -n kube-system svc/kube-dns 9153:9153 &
# Önemli metrikleri sorgulayın
curl -s localhost:9153/metrics | grep -E
"coredns_dns_requests_total|coredns_dns_responses_total|coredns_forward_requests_total|coredns_cache_hits_total"
# NXDOMAIN oranını hesaplayın (yüksek oran saldırı işareti olabilir)
curl -s localhost:9153/metrics | grep 'coredns_dns_responses_total{.*rcode="NXDOMAIN".*}'
Grafana dashboard’u için kritik alarm kuralları:
groups:
- name: coredns-security-alerts
rules:
- alert: CoreDNSHighNXDOMAINRate
expr: |
rate(coredns_dns_responses_total{rcode="NXDOMAIN"}[5m]) /
rate(coredns_dns_responses_total[5m]) > 0.3
for: 5m
labels:
severity: warning
annotations:
summary: "CoreDNS yüksek NXDOMAIN oranı tespit edildi"
description: "NXDOMAIN yanıt oranı %30'un üzerinde, olası subdomain brute-force veya yanlış yapılandırma"
- alert: CoreDNSForwardRequestSpike
expr: |
rate(coredns_forward_requests_total[1m]) > 1000
for: 2m
labels:
severity: critical
annotations:
summary: "CoreDNS forward istek artışı"
description: "Saniyede 1000'den fazla forward isteği, olası DNS amplification saldırısı"
- alert: CoreDNSCacheHitRateLow
expr: |
rate(coredns_cache_hits_total[5m]) /
(rate(coredns_cache_hits_total[5m]) + rate(coredns_cache_misses_total[5m])) < 0.5
for: 10m
labels:
severity: warning
annotations:
summary: "CoreDNS cache hit oranı düşük"
description: "Cache hit oranı %50'nin altında, cache poisoning veya yapılandırma sorunu olabilir"
Yapılandırma Doğrulama
Değişiklikleri uygulamadan önce ve sonra doğrulama yapmak iyi bir alışkanlık:
# CoreDNS yapılandırmasını yeniden yükleyin (downtime olmadan)
kubectl rollout restart deployment/coredns -n kube-system
# Yeniden başlatma sonrası durumu kontrol edin
kubectl rollout status deployment/coredns -n kube-system
# DNS çözümlemesini test edin
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never --
nslookup kubernetes.default.svc.cluster.local
# DoT bağlantısını doğrulayın
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never --
sh -c "nslookup -port=853 google.com 8.8.8.8"
# ACL'nin çalıştığını doğrulayın - cluster dışından erişim reddedilmeli
# Bu komutu cluster dışında bir makinede çalıştırın
dig @<cluster-dns-ip> kubernetes.default.svc.cluster.local
Sonuç
CoreDNS güvenliği, “bir kez yapıp unuttuğun” bir şey değil. Özetleyecek olursam:
- pods insecure kullanmayı bırakın,
verifiedmoduna geçin. Bu tek başına ciddi bir saldırı vektörünü kapatıyor.
- DNS sorgularınızı düz metin UDP yerine DoT ile şifreleyin. Google, Cloudflare veya kendi DoT sunucunuzu kullanabilirsiniz.
- ACL ile erişimi kısıtlayın. CoreDNS’iniz yalnızca cluster iç trafiğine hizmet vermeli, dışarıdan erişilemez olmalı.
- NetworkPolicy’leri ihmal etmeyin. Pod seviyesinde erişim kontrolü, cluster içinden gelebilecek tehditlere karşı ek bir katman sağlıyor.
- Security context’i sıkılaştırın. root olmayan kullanıcı, read-only filesystem, minimum capability. Bunlar artık standart olmalı.
- Cache sürelerini optimize edin. Hem güvenlik hem performans için doğru cache yapılandırması kritik.
- Metrikleri izleyin ve alarm kurun. Anormal DNS davranışı genellikle bir saldırının ilk işareti. Bunu erken yakalarsanız büyük felaketlerin önüne geçebilirsiniz.
DNS altyapısının güvenliği, tüm cluster güvenliğinin temelini oluşturuyor. Bir saldırganın DNS’i ele geçirdiğinde neler yapabileceğini düşündüğünüzde, bu yapılandırmalara harcanan zamanın ne kadar değerli olduğunu anlıyorsunuz.
