Docker Container Güvenlik Testi ve Sıkılaştırma

Bir üretim ortamında Docker container’larınızın güvenliğini test etmeden deploy etmek, kapıyı açık bırakıp tatile çıkmaya benziyor. Yıllar içinde pek çok kurumda güvenlik denetimi yaptım ve şunu net olarak söyleyebilirim: Container güvenliği hâlâ en çok ihmal edilen alanların başında geliyor. “Zaten container izole çalışıyor, ne olabilir ki?” düşüncesi ne kadar tehlikeli olursa olsun, bu yanılgı çok yaygın. Bu yazıda hem teorik hem de pratik açıdan Docker container güvenlik testini ve sıkılaştırma adımlarını ele alacağız.

Neden Container Güvenliği Önemli?

Container’lar sanal makinelerden farklı olarak host işletim sistemi çekirdeğini paylaşır. Bu mimari avantaj aynı zamanda ciddi bir saldırı yüzeyi oluşturur. Bir container’dan host’a kaçış (container escape) senaryoları gerçek hayatta belgelenmiş vakalar, bunları görmezden gelemeyiz.

Docker daemon varsayılan olarak root yetkisiyle çalışır. Yanlış yapılandırılmış bir container, tüm host sistemine erişim kapısı açabilir. Bunun yanı sıra zayıf image’lar, gereksiz yetkiler, açık portlar ve sırların (secrets) kötü yönetimi de saldırganların işini kolaylaştırır.

Temel Güvenlik Denetim Araçları

Docker Bench for Security

Docker’ın kendi referans aldığı CIS Docker Benchmark’ına göre sisteminizi tarayan bu araç, başlangıç noktanız olmalı.

docker run --rm --net host --pid host --userns host --cap-add audit_control 
  -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST 
  -v /etc:/etc:ro 
  -v /usr/bin/containerd:/usr/bin/containerd:ro 
  -v /usr/bin/runc:/usr/bin/runc:ro 
  -v /usr/lib/systemd:/usr/lib/systemd:ro 
  -v /var/lib:/var/lib:ro 
  -v /var/run/docker.sock:/var/run/docker.sock:ro 
  --label docker_bench_security 
  docker/docker-bench-security

Bu komutun çıktısı size PASS, WARN ve INFO etiketli sonuçlar verir. WARN etiketli her maddeye ayrı ayrı bakmak gerekiyor. Özellikle şu kategorilere dikkat edin:

  • Host Configuration: Audit log ayarları, kernel parametreleri
  • Docker daemon configuration: TLS, live restore, log seviyeleri
  • Container Images: Root ile çalışan image’lar, gereksiz paketler
  • Container Runtime: Privilege mod, capability’ler, volume mount’lar

Trivy ile Image Zafiyet Taraması

Trivy, image içindeki OS paketlerini ve uygulama bağımlılıklarını tarayarak bilinen CVE’leri raporlar. Aqua Security tarafından geliştirilen bu araç ücretsiz ve son derece güçlü.

# Basit image taraması
trivy image nginx:latest

# Sadece HIGH ve CRITICAL zafiyetleri göster
trivy image --severity HIGH,CRITICAL nginx:latest

# JSON çıktısı al (CI/CD pipeline için)
trivy image --format json --output rapor.json nginx:latest

# Dosya sistemi taraması (yerel proje için)
trivy fs --security-checks vuln,config ./uygulama-dizini

Üretim ortamında kullandığım bir yaklaşım: CI/CD pipeline’ına Trivy entegre edip CRITICAL zafiyet varsa build’i durduruyorum. Bu sayede zafiyetli image’ların production’a geçmesi engelleniyor.

Grype ile Alternatif Tarama

Trivy’ye ek olarak Grype de kullanılabilir, farklı veritabanları farklı zafiyetleri yakalayabiliyor:

# Grype kurulumu
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

# Image taraması
grype nginx:latest

# Belirli severity seviyesinde başarısız say
grype nginx:latest --fail-on high

Container Runtime Güvenlik Testi

Falco ile Gerçek Zamanlı İzleme

Falco, CNCF bünyesindeki bir runtime security aracı. Container’lar çalışırken anormal davranışları kernel seviyesinde yakalıyor. Bir container içinden shell açılması, hassas dosyalara erişim, ağ bağlantıları gibi aktiviteleri anlık olarak tespit ediyor.

# Falco kurulumu (Ubuntu/Debian)
curl -fsSL https://falco.org/repo/falcosecurity-3672BA8F.asc | sudo gpg --dearmor -o /usr/share/keyrings/falco-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] https://download.falco.org/packages/deb stable main" | sudo tee /etc/apt/sources.list.d/falcosecurity.list
sudo apt-get update && sudo apt-get install -y falco

# Servis olarak başlat
sudo systemctl start falco
sudo systemctl enable falco

# Gerçek zamanlı log takibi
sudo journalctl -fu falco

Falco’nun varsayılan kuralları oldukça kapsamlı ama kendi kurallarınızı da yazabilirsiniz. Örneğin üretim ortamında bir container içinden curl veya wget çalıştırılmasını anormal kabul edip alert üretebilirsiniz.

Manuel Güvenlik Kontrolleri

Araçların yanı sıra manuel kontroller de şart. Çalışan container’ları elle inceleme alışkanlığı kazanın:

# Tüm çalışan container'ların güvenlik ayarlarını incele
docker inspect $(docker ps -q) | jq '.[].HostConfig | {
  Privileged: .Privileged,
  CapAdd: .CapAdd,
  CapDrop: .CapDrop,
  ReadonlyRootfs: .ReadonlyRootfs,
  NetworkMode: .NetworkMode,
  UsernsMode: .UsernsMode
}'

# Root ile çalışan container'ları bul
docker ps -q | xargs -I {} docker inspect {} --format '{{.Id}}: User={{.Config.User}}'

# Açık portları kontrol et
docker ps --format "table {{.Names}}t{{.Ports}}"

Bu kontrolleri otomatikleştirip haftalık rapor olarak mail gönderen basit bir script bile güvenlik duruşunuzu önemli ölçüde iyileştirir.

Dockerfile Sıkılaştırma

Güvenlik image build aşamasında başlar. Kötü yazılmış bir Dockerfile, ne kadar sıkılaştırma yaparsanız yapın temel sorunları taşır.

Root Olmayan Kullanıcı Tanımlama

FROM node:18-alpine

# Uygulama dizini oluştur
WORKDIR /app

# Bağımlılıkları önce kopyala (layer cache optimizasyonu)
COPY package*.json ./
RUN npm ci --only=production

# Uygulama kodunu kopyala
COPY . .

# Güvenlik: root olmayan kullanıcı oluştur
RUN addgroup -g 1001 -S appgroup && 
    adduser -u 1001 -S appuser -G appgroup && 
    chown -R appuser:appgroup /app

# Root olmayan kullanıcıya geç
USER appuser

EXPOSE 3000
CMD ["node", "server.js"]

Multi-Stage Build ile Minimal Image

# Build aşaması
FROM golang:1.21-alpine AS builder

WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o uygulama .

# Final aşama: sadece binary
FROM scratch

# Sertifika dosyalarını kopyala (HTTPS için)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /build/uygulama /uygulama

USER 65534:65534

ENTRYPOINT ["/uygulama"]

scratch base image kullanmak saldırı yüzeyini minimuma indirir. İçinde shell yok, paket yöneticisi yok, gereksiz binary yok. Container’a giren bir saldırganın yapabileceği şeyler ciddi ölçüde kısıtlanıyor.

Docker Compose ve Runtime Sıkılaştırma

Image güvenliği kadar önemli olan bir diğer konu runtime sıkılaştırması. Compose dosyanızda veya docker run komutlarınızda aşağıdaki güvenlik ayarlarını mutlaka uygulayın:

version: '3.8'

services:
  webapp:
    image: myapp:v1.2.3  # latest tag KULLANMAYIN
    user: "1001:1001"
    read_only: true      # Root filesystem'i salt okunur yap
    
    # Geçici dizinler için tmpfs kullan
    tmpfs:
      - /tmp:noexec,nosuid,size=100m
      - /var/run:noexec,nosuid,size=10m
    
    security_opt:
      - no-new-privileges:true  # Privilege escalation engelle
      - seccomp:seccomp-profil.json
    
    # Gereksiz tüm capability'leri düşür, sadece gerekeni ekle
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE  # Sadece port bind için
    
    # Resource limitleri
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
        reservations:
          memory: 256M
    
    # Ağ izolasyonu
    networks:
      - frontend
    
    # Sağlık kontrolü
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

networks:
  frontend:
    driver: bridge
    internal: false
  backend:
    driver: bridge
    internal: true  # İnternete çıkış yok

Bu yapılandırmada dikkat edilmesi gereken noktalar:

  • no-new-privileges: Container içinde bir sürecin setuid/setgid ile yetki yükseltmesini engeller
  • read_only: Root filesystem’i salt okunur yapmak birçok exploit’i işe yaramaz hale getirir
  • cap_drop: ALL: Tüm Linux capability’lerini kaldırıp sadece ihtiyaç duyulanları geri ekliyoruz
  • internal: true: Backend network’ü dış dünyadan izole eder

Seccomp Profili Oluşturma

Seccomp (Secure Computing Mode), container’ın yapabileceği system call’ları kısıtlar. Docker varsayılan bir seccomp profili uyguluyor ama daha sıkı bir profil oluşturmak mümkün.

# Önce container'ı strace ile çalıştırıp hangi syscall'ları kullandığını öğren
docker run --rm --security-opt seccomp=unconfined 
  --name syscall-test myapp:latest &

# Başka bir terminal'de
strace -p $(docker inspect --format '{{.State.Pid}}' syscall-test) 
  -f -e trace=all 2>&1 | grep -oP 'syscall_Kw+|^w+(?=()' | sort -u

# Veya OCI runtime hook'larıyla daha temiz bir yaklaşım
docker run --rm --cap-add SYS_PTRACE 
  --security-opt seccomp=unconfined 
  -v /var/run/docker.sock:/var/run/docker.sock 
  docker/whaler myapp:latest

Minimum izin veren bir seccomp profili:

cat > /etc/docker/seccomp-strict.json << 'EOF'
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": [
        "accept", "accept4", "access", "arch_prctl",
        "bind", "brk", "close", "connect",
        "epoll_create1", "epoll_ctl", "epoll_wait",
        "exit", "exit_group", "fstat", "futex",
        "getpid", "getppid", "getsockname",
        "listen", "lseek", "mmap", "mprotect",
        "munmap", "nanosleep", "open", "openat",
        "poll", "read", "recvfrom", "recvmsg",
        "rt_sigaction", "rt_sigprocmask", "sendmsg",
        "sendto", "set_robust_list", "setsockopt",
        "sigaltstack", "socket", "stat", "write"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}
EOF

# Bu profil ile container çalıştır
docker run --security-opt seccomp=/etc/docker/seccomp-strict.json myapp:latest

Docker Daemon Sıkılaştırması

Container güvenliği sadece container’ların kendisiyle sınırlı değil. Docker daemon’ını da doğru yapılandırmak şart.

# /etc/docker/daemon.json
cat > /etc/docker/daemon.json << 'EOF'
{
  "icc": false,
  "userns-remap": "default",
  "live-restore": true,
  "userland-proxy": false,
  "no-new-privileges": true,
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "content-trust": true
}
EOF

sudo systemctl restart docker

Bu ayarların ne işe yaradığı:

  • icc: false: Container’lar arası direkt iletişimi keser, sadece tanımlanmış network’ler üzerinden konuşabilirler
  • userns-remap: Container içindeki root’u host’ta sıradan bir kullanıcıya eşler, container escape senaryolarını zorlaştırır
  • no-new-privileges: Daemon seviyesinde tüm container’lara uygulanır
  • content-trust: Sadece imzalı image’lara izin verir

Gerçek Dünya Senaryosu: Sızma Testi

Bir müşteri projesinde yaşadığım somut bir örneği paylaşayım. Uygulama container’ı privileged: true ile çalışıyordu ve bunu kimse fark etmemiş. Bu durumun ne kadar tehlikeli olduğunu göstermek için şu testi yaptım:

# Privileged container içinden host'a erişim PoC (test ortamında!)
# Container içinde:
fdisk -l  # Host disk'leri görünüyor mu?
ls /dev   # Host device'ları erişilebilir mi?

# Eğer privileged ise host filesystem'i mount etmek mümkün
mkdir /tmp/host-root
mount /dev/sda1 /tmp/host-root
ls /tmp/host-root  # Host'un / dizini!

# Bu noktada container'dan çıkış (escape) tamamlanmış sayılır
chroot /tmp/host-root

Bu testi canlı gösterdikten sonra kimse privileged container kullanmak istemedi. Bazen en iyi güvenlik dersi görsel bir demonstrasyon oluyor.

CI/CD Pipeline’a Güvenlik Entegrasyonu

Güvenlik testlerini pipeline’a entegre etmek, güvenliği sürecin bir parçası haline getirir. GitLab CI örneği:

# .gitlab-ci.yml
stages:
  - build
  - security-scan
  - deploy

variables:
  IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

build:
  stage: build
  script:
    - docker build -t $IMAGE_NAME .
    - docker push $IMAGE_NAME

container-scan:
  stage: security-scan
  script:
    - trivy image --exit-code 1 --severity CRITICAL $IMAGE_NAME
    - trivy image --exit-code 0 --severity HIGH $IMAGE_NAME --format template --template "@/contrib/gitlab.tpl" -o gl-container-scanning-report.json
  artifacts:
    reports:
      container_scanning: gl-container-scanning-report.json
  allow_failure: false  # CRITICAL zafiyet varsa pipeline durur

config-audit:
  stage: security-scan
  script:
    - trivy config --exit-code 1 --severity HIGH,CRITICAL ./
  allow_failure: false

Sonuç

Docker container güvenliği katmanlı bir yaklaşım gerektiriyor. Tek bir araç veya tek bir kural yeterli değil. Build aşamasından başlayıp runtime’a uzanan, daemon yapılandırmasından network politikalarına kadar her noktaya dikkat etmek gerekiyor.

Pratik olarak şunu öneririm: Önce mevcut ortamınızda Docker Bench çalıştırın ve WARN’ları listeleyin. Sonra Trivy ile production image’larınızı tarayın. Bu iki adım bile çoğu ortamda onlarca güvenlik açığını gün yüzüne çıkarıyor.

Güvenlik bir kez yapıp geçilen bir şey değil, sürekli izleme ve iyileştirme gerektiren canlı bir süreç. Falco gibi runtime güvenlik araçlarını production’da aktif tutmak, bilinmeyen tehditlere karşı erken uyarı sistemi sağlıyor. Container ekosistemi hızla gelişiyor, bu yüzden güvenlik pratiklerinizi de güncel tutmanız kritik önem taşıyor.

Bir yanıt yazın

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