Dockerfile hataları ve kök çalışma
- USER yönergesi eksik bir Dockerfile süreci kök olarak başlatır; sahiplikli kopyalama ve çok aşamalı derleme bu riski belirgin biçimde daraltır.
- USER, COPY --chown ve çok aşamalı derleme üç temel düzeltmedir.
- Küçük bir Dockerfile değişikliği, kod çalıştırma açığının etkisini belirgin daraltır.
Puan bütün etkinlikleri cevaplayınca hesaba yazılır. Bu dersi tamamlayınca +55 XP alacaksın. Giriş yapmadan ilerlersen puan yalnız bu tarayıcıda durur.
Bir Dockerfile çoğu zaman kod incelemesinde en az bakılan dosyadır. Oysa o dosya sürecin hangi kimlikle çalışacağını, hangi araçları taşıyacağını ve imaja nelerin gireceğini tek başına belirler. Bu derste kök çalışan bir konteynerin neden riskli olduğunu görür, USER yönergesiyle kök olmayan bir kullanıcıya geçersin.
Çok aşamalı derlemeyi, sahiplikli kopyalamayı ve bağlam hijyenini adım adım kurarsın. En sonda denetimi otomatikleştiren bir aracı boru hattına takarsın.
USER yoksa süreç köktür
Bir Dockerfile'da USER yönergesi yoksa uygulama kök olarak çalışır. Çoğu ekip bunu fark etmez, çünkü uygulama zaten çalışıyordur. Fark, bir gün kod çalıştırma açığı çıktığında ortaya çıkar.
Kök olarak çalışan bir süreç konteyner içinde her dosyaya dokunur, paket kurar ve yeteneklerin çoğunu kullanır. Kaçış zinciri de aynı yerden başlar. Kök olmayan bir kullanıcı bu adımların çoğunu baştan kapatır. OWASP'ın konteyner listesinde bu iki başlık ayrı ayrı geçer: güvenli kullanıcı eşlemesi ve güvenli varsayılanlar.
Kök varsayılanı bir kolaylıktan doğar. İmaj kurulurken paket kurmak, dosya sahipliğini ayarlamak ve bağlanacak portu açmak kök yetkisi ister. Derleme bittikten sonra bu yetkiye artık ihtiyaç yoktur, ama kimse geri almazsa süreç kök kalır.
Etkiyi somut düşün: kök çalışan bir konteynerde kod çalıştıran kişi paket yöneticisini çağırır, yeni araç kurar ve dosya sisteminin her yerine yazar. Aynı kişi kök olmayan bir kullanıcıyla bu adımların çoğunda izin hatasına takılır.
Etkinlik 1 / 7
Bu Dockerfile'da konteyneri kök yetkisiyle bırakan eksikliği taşıyan satırı bul.
Düzeltme birkaç satırdır. Derleme sırasında sayısal kimliği olan bir kullanıcı tanımlarsın ve USER yönergesiyle ona geçersin. USER sonraki tüm komutların ve giriş noktasının kimliğini değiştirir; dosya sahipliğine ise dokunmaz. Sayısal kimlik yazmanın bir nedeni de Kubernetes'tir.
Pod'da runAsNonRoot açıksa kubelet, konteyneri başlatmadan önce imajdaki kullanıcının kök olmadığını doğrular. USER uygulama gibi bir ad gördüğünde bu doğrulamayı yapamaz. Pod tanımında ayrıca sayısal bir runAsUser yoksa konteyner başlamaz. USER 10001 yazmak bu belirsizliği ortadan kaldırır ve denetim ilk denemede geçer.
Etkinlik 2 / 7
Bu iki satır eklenirse çalışma zamanı kimliği ne olur?
Önce kodu oku, sonra çıktıyı seç.
RUN useradd -r -u 10001 uygulama USER 10001
Çok aşamalı derleme ve sahiplik
Kullanıcı geçişi tek başına yetmez, sahiplik de doğru verilmelidir. Uygulama dosyaları kök sahipliğinde kalırsa kök olmayan kullanıcı onlara yazamaz.
Doğrusu, sahipliği kopyalama anında doğru kullanıcıya vermektir. Bunun en temiz yolu çok aşamalı derlemedir; güvenlik ile boyut aynı anda burada kazanılır. İlk aşama tam donanımlı bir tabandan başlar, bağımlılıkları kurar ve uygulamayı derler.
İkinci aşama ince bir tabandan başlar ve yalnız çalışma zamanı için gerekeni ilk aşamadan alır. Derleyici, paket yöneticisi ve geçici dosyalar ilk aşamada kalır; son imaja hiç girmez. Aşağıdaki örnek üç düzeltmeyi bir arada gösterir: çok aşamalı derleme, sahiplikli kopyalama ve kök olmayan kullanıcı.
FROM node:22-slim AS derleme WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY . . FROM gcr.io/distroless/nodejs22-debian13 WORKDIR /app COPY --from=derleme --chown=10001:10001 /app /app USER 10001 CMD ["sunucu.js"]
Örneği satır satır oku. İlk aşamanın adı derleme'dir ve bağımlılıkları kurar. İkinci aşama distroless bir tabandan başlar; içinde kabuk ve paket yöneticisi yoktur. COPY --from=derleme, çıktıyı ilk aşamadan alır.
--chown=10001:10001 kısmı kopyalanan dosyaların sahipliğini baştan uygulama kullanıcısına verir, böylece sonradan izin düzeltmeye gerek kalmaz. USER 10001 süreci kök olmayan bir kimlikle başlatır. Bu tek dosya, tek aşamalı bir imajda yüzlerce paketle gelen açıkların çoğunu daha derlemeden atar.
İkinci aşamanın tabanını seçerken çalışma zamanının ne istediğine bakarsın. Statik derlenen bir ikili scratch üstünde koşar. Bir dil çalışma zamanı gerekiyorsa distroless o dile özel bir taban sunar ve içinde yalnız çalışma zamanı ile kütüphaneler bulunur.
Her iki durumda da kabuk ve paket yöneticisi son imaja girmez. Bunun güvenlik etkisi doğrudan ölçülür. Tek aşamalı bir imaj derleyiciyi, paket önbelleğini ve geçici dosyaları da taşır; bunların her biri ayrı bir açık kaynağıdır.
İki sayıyı yan yana izlersin: imaj boyutu ve yüksek dereceli açık sayısı. Doğru kurulumda ikisi birlikte düşer.
Etkinlik 3 / 7
Çok aşamalı derlemenin adımlarını sıraya koy.
Bir öğeye odaklan, yukarı ve aşağı ok tuşlarıyla taşı. Home tuşu en üste, End tuşu en alta alır.
- 1Kök olmayan kullanıcıyı ayarla ve giriş noktasını yaz
- 2Yalnız üretilmiş çıktıyı ikinci aşamaya kopyala
- 3İkinci aşamayı ince bir tabandan başlat
- 4Derleme aşamasını tam donanımlı bir tabandan başlat
- 5Bağımlılıkları kur ve uygulamayı derle
Etkinlik 4 / 7
COPY --chown=10001:10001 . /app yazmanın amacı nedir?
Bağlam, sır ve giriş noktası
Derleme bağlamını da unutmamak gerekir. Dockerfile'ın yanındaki .dockerignore yoksa, COPY . . satırı ne varsa alır: yerel .env dosyaların, .git dizinin, hatta özel anahtarların imaja girer. .dockerignore dosyası bu girdileri baştan eler. Küçük ama sık görülen bir hata da ADD ile COPY'yi karıştırmaktır.
ADD, uzak bir adresten dosya indirir ve sıkıştırılmış arşivleri kendiliğinden açar; bu iki davranış da imaja beklenmedik içerik sokar. Dosya ve dizin kopyalamak için COPY yeter ve ne yaptığı açıktır. Çalışma zamanında kök dosya sistemini salt okunur açmak da aynı hijyenin parçasıdır. Yalnız gerçekten yazılması gereken dizini geçici bir bellek biriminde açarsın.
Sır hijyeni ayrı bir başlıktır. Derleme sırasında özel bir depoya erişmek için belirteç gerekebilir. Bu belirteci bir derleme değişkeniyle geçirmek risklidir, çünkü değer imajın geçmişine düz metin yazılır ve docker history ile okunur. Doğru yol, sırrı yalnız o komut çalışırken belleğe bağlamaktır; komut bitince bağ kalkar ve değer ne katmana ne geçmişe girer.
Çalışma zamanında gereken sır ise imaja hiç yazılmaz, dışarıdan verilir. Yanlışlıkla imaja giren bir sırrı silmek de yetmez. Sır iptal edilir ve yenisi üretilir, çünkü eski imaj kopyaları onu taşımaya devam eder.
Başlangıç komutunu yazarken liste biçimini seçersin. Kabuk biçiminde yazılan komut, uygulamayı /bin/sh -c altında bir alt süreç olarak başlatır. Bu durumda uygulama birinci süreç olmaz ve durdurma sinyalini almaz. Konteyneri durdurduğunda uygulama düzgün kapanmaz, bekleme süresi dolunca zorla sonlandırılır.
Liste biçimi uygulamayı doğrudan birinci süreç yapar ve sinyaller ona ulaşır. Sinyali almayan uygulama açık bağlantılarını kapatamaz ve yarım kalan işini kaydedemez. Fark Dockerfile'da tek satırdır: CMD node sunucu.js kabuk biçimidir, CMD ["node", "sunucu.js"] ise liste biçimidir.
Etkinlik 5 / 7
CMD komutunu kabuk biçiminde (dizgi olarak) yazarsan ne olur?
Otomatik denetim: hadolint
Dockerfile denetimini otomatikleştirmek de mümkündür. Statik denetim araçları kök kullanıcıyı, sabitlenmemiş etiketi ve gereksiz paket kurulumlarını yakalar. hadolint bu işi yapan yaygın bir araçtır; Dockerfile'ı okur ve her kuralı bir kod ile önem derecesiyle raporlar.
Kök kullanıcı için DL3002, latest etiketi için DL3007, sabitlenmemiş paket sürümü için DL3008 kuralı uyarı verir. Kuralları boru hattına takarsan, gözden geçirmede insanın aklında tutması gereken madde sayısı düşer. hadolint RUN satırlarındaki kabuk komutlarını da ShellCheck ile denetler ve hatalı tırnaklamayı SC koduyla işaretler.
$ hadolint Dockerfile Dockerfile:1 DL3007 warning: Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag Dockerfile:3 DL3008 warning: Pin versions in apt get install. Dockerfile:5 DL3020 error: Use COPY instead of ADD for files and folders Dockerfile:9 DL3002 warning: Last USER should not be root
Çıktıyı satır satır oku. Birinci satır taban imajın latest etiketiyle çekildiğini söyler ve uyarı düzeyindedir. İkinci satır apt-get install sırasında paket sürümlerinin yazılmadığını gösterir.
Üçüncü satır dosya kopyalamak için ADD kullanıldığını yakalar; DL3020 hata düzeyindedir. Son satır, dosyadaki son USER yönergesinin kök kaldığını yazar. Önem derecesine göre boru hattında bir eşik koyarsın.
hadolint --failure-threshold error Dockerfile komutu yalnız hata düzeyindeki bulguda hattı durdurur; uyarılar rapora düşer. Art arda gelen RUN satırları DL3059 ile bilgi düzeyinde işaretlenir, liste biçiminde yazılmamış CMD ise DL3025 ile uyarı alır.
Etkinlik 6 / 7
hadolint bir Dockerfile'da DL3002 kuralını uyarı olarak veriyor. Bu ne anlama gelir?
Sabitleme ve gözden geçirme
Son adım sabitlemedir. Taban imajı ve paket sürümlerini sabitlemek, aynı imajın yarın başka bir içerikle üretilmesini engeller. apt-get install sırasında sürüm yazmazsan, aynı Dockerfile yarın farklı bir paket sürümü çeker.
Sabitlenen sürüm ise zamanla eskir. Bu yüzden sabitleme ile düzenli yükseltme turu birlikte yürür: sürüm kilitlidir, ama planlı bir aralıkla yenilenir. Güvenlik yamalarını bu turda alırsın. Tur takvime bağlıysa acil bir yama da aynı yoldan hızla geçer.
Taban imajı özet değeriyle sabitlemek için FROM satırında etiketin yanına sha256 değerini yazarsın: FROM node:22-slim@sha256:... gibi. Etiket okuyana sürümü anlatır, özet değeri ise içeriği kilitler. Bu yazımı elle güncel tutmak zordur.
Bu yüzden ekipler bir bağımlılık güncelleme botu çalıştırır. Bot yeni bir taban yayımlandığında özet değerini değiştiren bir birleştirme isteği açar. Boru hattı yeni imajı tarar ve sonuç temizse değişiklik birleştirilir. Böylece sabitleme ile güncellik arasında seçim yapmak zorunda kalmazsın.
Son bir alışkanlık, Dockerfile'ı uygulamanın parçası gibi gözden geçirmektir. Kod incelemesinde kimse bir satırı atlamazken, aynı depodaki Dockerfile çoğu zaman okunmadan onaylanır. Oysa o dosya sürecin kimliğini, taşıdığı araçları ve imaja gireni tek başına belirler.
Bir de temel dosya izinlerini gözden geçirmekte yarar var. Uygulama dizinindeki dosyaların yazılabilir olması gerekmez; okuma ve çalıştırma çoğu durumda yeter. Yazılamaz bir uygulama dizini, kod çalıştırma elde eden kişinin kalıcılık kurmasını da zorlaştırır.
Etkinlik 7 / 7
Doğru mu, yanlış mı?
latest etiketini kullanmak, dağıtımın hangi imajla çalıştığını belirsiz bırakır.
Derinleş:Derleme sırrını imaja gömmeden vermek
Bir uygulama derlenirken çoğu zaman özel bir paket deposuna erişim gerekir ve bu erişim bir belirteç ister. İlk akla gelen yol belirteci bir derleme değişkeniyle geçirmektir, ama bu değer imajın geçmişine düz metin olarak yazılır ve docker history ile okunur. Doğru yol bağlı sır kullanmaktır.
RUN --mount=type=secret ile sır yalnız o komut çalışırken belleğe bağlanır; komut bittiğinde bağ kalkar ve değer ne katmana ne de geçmişe girer. Böylece paket deposuna erişilir, ama belirteç imajda iz bırakmaz. Aynı ilke ortam değişkenleri için de geçerlidir: bir sır çalışma zamanında gerekiyorsa, imaja yazmak yerine dışarıdan verilir. İmaja yazılan her sır, imajı eline geçiren herkesin sırrı olur.
Okumayı istediğin sırayla sürdürebilirsin. Ders, bütün etkinlikleri cevapladığında tamamlanır.