Docker Güvenliği (Docker İçin En İyi Pratikler)

Docker, en yaygın kullanılan sanallaştırma ve konteynerizasyon teknolojilerinden bir tanesidir. Esasen bir platform olan Docker sayesinde, ana makineden izole bir şekilde çalışan ve esnek olarak konfigüre edilebilen konteynerler oluşturulabilir.

Docker Mimarisi

Docker, client-server mimarisiyle çalışır. Docker client, Docker daemon ile konuşarak asıl işi daemon’a yaptırır. Konteynerlerin build edilmesi, yürütülmesi ve dağıtılması Docker daemon tarafından yapılır.

  • Docker daemon (dockerd): Docker API isteklerini dinler ve docker imajlarını, konteynerları, ağları ve depolama alanlarını yönetir.
  • Docker client (docker, docker-compose, Docker Desktop): Docker API’ya istekler gönderen ve daemon ile iletişime geçen, yapılması gereken işi daemona ileten bir komut satırı arayüzüdür. Ayrıca Windows, Linux ve MAC sistemler için client görevi gören Docker Desktop da arka planda Docker daemon ile iletişime geçmektedir.
  • Docker registry: Docker imajlarının tutulduğu repositorydir. Remote repository olarak Docker Hub kullanılır. Kullanıcılar isterse docker hub üzerinde private repository oluşturabilir.

Docker-Arch

Eğer düzgün konfigüre edilirse, bir uygulamayı doğrudan ana makine üzerinde çalıştırmaya nazaran çok daha güvenlidir. Ancak konfigürasyon hataları, çeşitli güvenlik açıklarına neden olabilmektedir.

Docker İçin En İyi Sıkılaştırma Teknikleri (Best Practices)

Kural #0 - Ana Makinenizi ve Docker’u Güncel Tutun

Leaky Vessels gibi host sistemde (ana makinede) root yetkilerine erişebilmeye neden olan bilinen güvenlik açıklarından korunmak için hem ana makinenin hem de Docker’un güncel olduğundan emin olunmalıdır. Docker Engine’in yanında, kullanılan makinenin kernel’ı da güncel tutulmalıdır.

Leaky Vessels Güvenlik Açığı Nedir?

  • Snyk tarafından tespit edilen bu güvenlik açığı Docker engine ve Kubernetes gibi diğer konteynerizasyon araçları tarafından kullanılan runc fonksiyonundan kaynaklanmaktadır. runc ≤ 1.1.11 versiyonunda tespit edilen bu bug sayesinde, kötü niyetli bir saldırgan, konteyner içerisinden kaçıp (escape edip) host sistemde komut çalıştırabilmektedir.

Host sistem ve konteyner aynı çekirdeği (kernel) paylaştığı için, host sistemin çekirdeğinde tespit edilen bir güvenlik açığı doğrudan konteynerları da etkilemektedir.

Kural #1 - Docker daemon Soket’lerini Herkes Tarafından Ulaşılabilir Yapmayın

Docker soketi (/var/run/docker.sock), docker’ın dinlediği UNIX soketidir. Docker API için temel erişim noktasıdır ve soketin sahibi root kullanıcısıdır. Bir kullanıcıya Docker soketine erişim yetkisi vermek, doğrudan root yetkisi vermeye eşdeğerdir.

  • TCP Docker Daemon Soketini Aktifleştirmeyin: Docker’ı kullanırken -H tcp:0.0.0.0:xxx gibi bir komut ile daemon’u internetten gelen API isteklerini dinleyecek şekilde konfigüre etmeyin. Docker daemon’a illa internetten erişilmesi gerekiyorsa, şifreli bir kanal üzerinden dinlenmesi tavsiye edilmektedir. (Bkz: https://docs.docker.com/reference/cli/dockerd/#daemon-socket-option)
  • Docker Soketini Konteynerler Tarafından Erişilebilir Hale Getirmeyin: Docker imajları “-v /var/run/docker.sock://var/run/docker.sock” gibi bir komut kullanılarak oluşturulduysa, konteynerler içerisinden volume bağlantısı sayesinde docker daemon soketine erişim sağlabilir. Bu da saldırganın root yetkilerine erişmesine imkan verebilir. Bu sebeple imajlar çalıştırırken yukarıdaki gibi bir volume mount işlemi yapılmamalıdır. Eğer docker yerine docker-compose kullanıldıysa, aşağıdaki gibi hatalı bir konfigürasyon da yapılmamalıdır.

      volumes:
          - "/var/run/docker.sock:/var/run/docker.sock"
    

Kural #2 - Konteyner için Yetkisiz Bir Kullanıcı Oluşturun

Konteynerı, yetkili olmayan bir kullanıcıyı kullanacak şekilde yapılandırmak, olası yetki yükseltme saldırılarını önleme işe yarar. Konteynerı yetkisiz bir kullanıcıyı kullanacka şekilde ayarlamanın birkaç farklı yolu vardır:

  • İmaj üzerinden konteyner build ederken “-u” parametresi kullanılabilir. -u parametresi ile sadece belirtilen yetkilere sahip kullanıcı oluşturulur. Örnek kullanımlar:
    • docker run -u 4000 alphine (alphine imajı üzerinden bir konteyner oluşturuluyor ve id’si 4000 olan non-root kullanıcı tanımlanıyor)
    • Dockerfile üzerinden konteyner oluşturulurken konteyner için kullanıcı oluşturulabilir:

        FROM alpine
        RUN groupadd -r myuser && useradd -r -g myuser myuser
        #    <HERE DO WHAT YOU HAVE TO DO AS A ROOT USER LIKE INSTALLING PACKAGES ETC.>
        USER myuser
      
    • Docker daemon’da user namespace’i etkinleştirilebilir: --userns-remap=default Bu flag sayesinde docker dameon, konteyner içerisinde root user ve root group’u, konteyner dışında yetkisiz bir kullanıcıyla eşler.
    • Kubernetes’de, SecurityContext içerisinde runAsUser alanı ile pod için user konfigürasyonu yapılabilir:

        apiVersion: v1
        kind: Pod
        metadata:
          name: example
        spec:
          containers:
          - name: example
            image: gcr.io/google-samples/node-hello:1.0
            securityContext:
              runAsUser: 4000 # <-- This is the pod user ID
      

Kural #3 - Yetenekleri Sınırlandırın (Minimum Yetki Prensibi)

Güvenlik sektörünün altın kurallarından olan “gerekli minimum yetki neyse onu ver” prensibini docker için de uygulayabiliriz. Docker, varsayılan olarak konteyner çekirdeğini bazı yeteneklerle (capabalities) çalıştırır. Eğer gerekli değilse, bu yeteneklerin bazılarını devredışı bırakabilirsiniz.

Konteyner çalıştırılırken “--cap-drop” paremetresi ile gereksiz olan yetenekler kısılabilir. Örneğin aşağıdaki komut ile tüm yetenekler alınmış ve konteynere sadece CHOWN (dosya id ve uid’lerinde değişiklik yapabilme) yeteneği verilmiştir:

  • docker run --cap-drop all --cap-add CHOWN alpine

Ayrıca, konteyner kesinlikle –privileged parametresi ile çalıştırılmamalıdır. “–privileged” parametresi konteynere tüm yetenekleri verir.

Kubernetes tarafında ise “capabilities” keywordü ile linux yetenekleri sınırlandırılabilir:

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
  - name: example
    image: gcr.io/google-samples/node-hello:1.0
    securityContext:
          capabilities:
            drop:
              - ALL
            add: ["CHOWN"]

Kural #4 - Konteyner içi Yetki Yükseltmeyi Engelleyin

Docker imajlarını her zaman --security-opt=no-new-privileges parametresi ile çalıştırın. Bu parametre, konteynerin setuid ve setgid komutlarıyla yeni yetkiler kazanabilmesini önleyecektir.

Kubernetes tarafında “allowPrivilegeEscalation” keywordü ile bu durum kontrol edilebilir:

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
  - name: example
    image: gcr.io/google-samples/node-hello:1.0
    securityContext:
      allowPrivilegeEscalation: false

Kural #5 - Konteynerler Arası Bağlantıya Dikkat Edin

Varsayılan olarak tüm docker konteynerleri docker0 networkü üzerinden birbiri ile iletişim kurabilir. Eğer gerekli değil ise konteynerler arası iletişim kapatılmalı ya da sadece gerekli olan konteynerlerin birbiri ile iletişim kurması sağlanmalıdır. Bunun için yeni bir docker networkü oluşturulup onun üzerinden konfigürasyon yapılabilir.

Konteynerler arası iletişim, https://docs.docker.com/network/#communication-between-containers dökümanı takip edilerek konfigüre edilebilir.

Kural #6 - Linux Güvenlik Modülünü Kullanın

Varsayılan güvenlik modülünü devredışı bırakmadığınızdan emin olun. Ek olarak  seccomp ya da AppArmor gibi güvenlik modüllerini de kullanmayı düşünebilirsiniz.

  • seccomp: Linux secure computing mode olarak bilinen bu çekirdek özelliği, Linux sistemlerde çalışan işlemler (processler) için ekstra bir güvenlik katmanı sağlar. İşlemler için hangi sistem çağrılarını yapabileceklerini ve yapamayacaklarını tanımlamayı sağlar. Böylelikle hacklenen bir sistemde meydana gelebilecek etkiyi azaltır.
    • Docker konteyneri çalıştırılırken “--security-opt seccomp=/path/to/seccomp/profile.json” parametresi ile kullanılacak olan seccomp profili belirtilebilir.
  • AppArmor: seccomp’a göre üst katmanlarda kalan başka bir linux çekirdek özelliğidir. seccomp, proseslerin yapıp/yapamayacağı çağrıları sınırlandırırken, AppArmor uygulamalar için erişim sınırlamaları yapar. Kısacası bir MAC (Mandatory Access Control) olarak görev yapar.
    • Bir docker konteyeri çalıştırılırken varsayılan olarak docker-default AppArmor profili kullanılır. Bu profil değiştirilmek istenirse docker run komutu ile konteyner oluşturulurken --security-opt apparmor=your_profile parametresi kullanılarak değiştirilebilir.

Kubernetes tarafında seccomp konfigürasyonu için https://kubernetes.io/docs/tutorials/security/seccomp/ ve AppArmor için https://kubernetes.io/docs/tutorials/security/apparmor/ dökümanları takip edilebilir.

Kural #7 - Konteyner Kaynaklarını Sınırlandırın

DoS saldırılarını önlemenin en iyi yolu, konteyner için ayırılan kaynakların sınırlandırılması olacaktır. Bunun için  hafızaCPU, maksimum restart sayısı (--restart=on-failure:<number_of_restarts>), maksimum file descriptior sayısı (--ulimit nofile=<number>) ve maksimum proses sayısı (--ulimit nproc=<number>) kontrol edilebilir.

Bkz: https://docs.docker.com/reference/cli/docker/container/run/#ulimit

Kaynak sınırlandırma işlemi kubernetes için de benzer şekilde yapılabilir. Bunun için:

Kural #8 - Dosya Sistemi ve Volume’leri Read-Only Yapın

Konteynerleri çalıştırırken --read-only parametresi ile dosya sistemini sadece okuma yetkisine sahip olacak şekilde konfigüre edin. Böylelikle konteyner içinde dosya sisteminde sadece okuma işlemi yapılabilir, yazma işlemi yapılamaz.

Eğer konteyner içerisinde bir uygulamanın yazma yetkisine sahip olması gerekiyorsa -read-only parametresi --tmpfs ile birleştirilebilir:

docker run --read-only --tmpfs /tmp alpine sh -c 'echo "whatever" > /tmp/file'

Docker-compose üzerinde read-only kullanımı:

version: "3"
services:
  alpine:
    image: alpine
    read_only: true

Kubernetes üzerinde read-only kullanımı:

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
  - name: example
    image: gcr.io/google-samples/node-hello:1.0
    securityContext:
      readOnlyRootFilesystem: true

Ek olarak, eğer bir volume sadece dosya okumak amacıyla konteynere bağlandıysa (mount edildiyse), sadece read-only (:ro) belirteci eklenerek mount edilmelidir:

  • docker run -v volume-name:/path/in/container:ro alpine
  • Ya da “–mount” parametresi de kullanılabilir:
    • docker run --mount source=volume-name,destination=/path/in/container,readonly alpine

Kural #9 - CI/CD Pipeline Süreclerine Konteyner Tarama Araçlarını Dahil Edin

Yazılım geliştirme süreçlerinde Sürekli Entegrasyon (CI) ve Sürekli Dağıtım (CD) kavramları kritik rol oynamaktadır. CI/CD pipeline’larına Fortify Static Code Analyzer gibi SAST araçları ve Sonatype Container Security gibi konteyner tarama araçları dahil edilerek güvenlik açıkları canlı ortama ulaşmadan engellenmedir.

Ek olarak, kullanıcılar tarafından tanımlanabilen ve yazılım süreçlerinde belli standartların ve en iyi pratiklerin uygulanabilmesini sağlayan linterler oluşturularak konteynerların güvenliği sağlanmalıdır. Örneğin pipeline sürecine aşağıdaki gibi bir linter eklenerek konteynerler için basit güvenlik önlemleri alınabilir. Bunun için https://github.com/hadolint/hadolint gibi araçlar kullanılabilir.

Linter Örneği:

Ensure a USER directive is specified
Ensure the base image version is pinned
Ensure the OS packages versions are pinned
Avoid the use of ADD in favor of COPY
Avoid curl bashing in RUN directives

Referanslar:

Konteyner tarama araçları:

Docker imajlarındaki hassas verileri tespit etmek için araçlar:

Kubernetes konfigürasyon hatalarını taramak için araçlar:

Docker konfigürasyon hatalarını taramak için araçlar:

Kural #10 - Docker Daemon Log Seviyesini info’da Tutun

Docker daemon’un varsayılan loglama seviyesi “info”dur, bu seviye /etc/docker/daemon.json dosyası üzerinden log-level ile kontrol edilebilir. Eğer daemon, --log-level parametresi ile çalıştırılırsa, daemon.json dosyasındaki log-level ifadesinin üzerine yazılır.

Daemon’un info dışında farklı bir loglama seviyesi ile çalışıp çalışmadığı aşağıdaki komut ile kontrol edilebilir:

  • ps aux | grep '[d]ockerd.*--log-level' | awk '{for(i=1;i<=NF;i++) if ($i ~ /--log-level/) print $i}’

Debug seviyesi detaylı bilgi vereceği için debug işlemi gerekmediği sürece seçilmemelidir. Bu durum potansiyel güvenlik risklerine neden olur.

Kural #11 - Docker’ı Root Yetkisi Olmayan Kullanıcı ile Çalıştırın

Docker çalıştırılırken rootless modda (normal kullanıcı modu) çalıştırılırsa bir saldırgan konteyner içinden host sisteme ulaşsa bile root yetkilerine ulaşamaz, docker’ı çalıştıran kullanıcını yetkilerine sahip olur. (Bkz: https://docs.docker.com/engine/security/rootless/)

Kural #12 - Hassas Veri Yönetimi için Docker Secrets Kullanın

Docker Secrets, parola veya API key gibi hassas verileri depolamak ve yönetmek için güvenli bir arayüz sağlar.

docker secret create my_secret /path/to/super-secret-data.txt
docker service create --name web --secret my_secret nginx:latest

Docker compose için docker secrets kullanımı:

version: "3.8"
  secrets:
    my_secret:
      file: ./super-secret-data.txt
  services:
    web:
      image: nginx:latest
      secrets:
        - my_secret

Docker secrets, docker ortamları için güvenli bir arayüz sağlarken kubernetes ortamında secret veriler açık metin olarak saklandığı için tavsiye edilmemektedir.

Kural #13 - Tedarik Zinciri Güvenliğini Artırın

Tedarik zinciri güvenliğini artırmak için Kural 9’da da bahsettiğimiz üzere yazılım geliştirme süreçlerine ekstra güvenlik önlemlerinin eklenmesi gerekmektedir.

  • Image Provenance: Bütünlük ve hata ayıklama için konteynerın geçmişi ve geliştirme süreci dökğmante edilmelidir.
  • SBOM Generation: Her imaj için Software Bill of Materials (SBOM) yani yazılımın içerdiği tüm bileşenleri ve bileşeler arasındaki ilişkiyi dökümante edin. Bu sayede zafiyet yönetimi süreci daha transparan ve yönetilebilir olacaktır.
  • Image Signing: İmajların bütünlüğü için imajlar dijital olarak imzalanmalıdır.
  • Trusted Registry: İmajlar için oluşturulan dökümantasyonları, SBOM verilerini güçlü bir yetki kontrolü ile kontrol edilen yönetim sisteminde saklayın.
  • Secure Deployment: İmaj doğrulama, runtime güvenliği ve devamlı izleme gibi güvenlik politalarını uygulayarak geliştirme sürecini ilerletin.

Referans:

Written by

Resul Bozburun