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, verified moduna 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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir