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.
