Container Hardening – Deo 2: Docker image bezbednost

Prvi deo serije pokrio je bezbednost Docker hosta i daemon-a — sloj koji štiti sve kontejnere odjednom. Drugi deo se spušta jedan nivo niže, na sam image od kojeg se kontejner gradi. Ovo je podjednako bitan sloj, jer čak i savršeno bezbedan host i pažljivo podešen runtime ne pomažu mnogo ako se svakodnevno pokreće image pun nepotrebnih paketa i poznatih ranjivosti. Istraživanja iz 2025. i 2026. godine dosledno pokazuju da izbor baznog image-a određuje veliku većinu (procene se kreću i do 60–80%) ukupnog broja ranjivosti koje skener kasnije prijavi na tom image-u — što ovu odluku čini jednom od najuticajnijih u celom procesu.

Zašto veličina i sadržaj baznog image-a nisu sporedno pitanje

Pun, opšte-namenski bazni image (npr. standardan ubuntu ili debian) dolazi sa stotinama paketa koje aplikacija nikad neće koristiti — kompajlerima, uslužnim alatima, bibliotekama za scenarije koji se na tom serveru nikad neće desiti. Svaki od tih paketa je potencijalna ranjivost, bez obzira na to da li ga aplikacija ikad pozove. Napadač koji uspe da izvrši kod unutar takvog kontejnera ima na raspolaganju i sve te dodatne alate — shell, mrežne uslužne programe, pakete koji olakšavaju dalje istraživanje sistema ili lateralno kretanje.

Šta treba uraditi

  • Kao polaznu tačku birati minimalne bazne image-e — Alpine (mala Linux distribucija sa sopstvenim paket menadžerom, tipično oko 5 MB) ili "slim" varijante standardnih distribucija, umesto pune verzije
  • Za produkcione radne opterećenja, razmotriti distroless image-e (npr. Google-ov Distroless projekat) — ovo nisu samo "manje" verzije standardnih image-a, već image-i koji namerno ne sadrže shell, paket menadžer niti alate za otklanjanje grešaka, jer produkciona aplikacija ne treba nijedno od toga za normalan rad
  • Izbegavati "latest" oznaku (tag) prilikom povlačenja bilo kog image-a, uključujući bazne image-e — "latest" je pokretna meta koja se menja bez upozorenja, čineći build proces nepredvidljivim i teškim za reprodukciju
  • Umesto oznake verzije, koristiti pinning po digest-u (kriptografskom otisku konkretne verzije image-a) tamo gde je reproduktivnost bitna — ovo garantuje da se uvek povlači tačno ista, verifikovana verzija, ne bilo šta što se trenutno nalazi iza te oznake

Napomena o riziku: prelazak sa punog baznog image-a na Alpine ili distroless nije uvek prosta zamena jedne reči u Dockerfile-u — Alpine koristi drugačiju standardnu biblioteku (musl umesto glibc), što povremeno izaziva suptilne probleme sa kompatibilnošću kod aplikacija koje očekuju glibc ponašanje. Distroless image-i, budući da nemaju shell, otežavaju uobičajene tehnike otklanjanja grešaka (npr. `docker exec` sa interaktivnom ljuskom). Testirati promenu baznog image-a temeljno, uključujući manje uobičajene scenarije korišćenja aplikacije, pre nego što se ta promena primeni u produkciji.

Multi-stage build — odvajanje alata za građenje od onoga što se stvarno pokreće

Mnoge aplikacije zahtevaju alate za građenje (kompajlere, sistem za upravljanje zavisnostima, alate za testiranje) koji su neophodni tokom procesa pravljenja finalnog artefakta, ali potpuno nepotrebni u trenutku kad se aplikacija pokreće u produkciji. Multi-stage build je Docker mehanizam koji rešava ovaj problem elegantno: definiše se jedna faza (stage) koja sadrži sve alate potrebne za građenje, i druga, odvojena, minimalna faza koja iz prve preuzima samo gotov artefakt — sam finalni image nikad ne sadrži kompajler, izvorni kod pre kompilacije, niti bilo šta drugo iz "prljave" build faze.

Šta treba uraditi

  • Strukturirati Dockerfile kao multi-stage build za svaku aplikaciju koja zahteva korak kompajliranja ili pripreme (Node.js sa build korakom, Java, Go, i slično) — prva faza sa punim alatima za građenje, druga, minimalna faza (idealno distroless ili Alpine) koja sadrži samo gotov artefakt
  • Proveriti da finalna faza zaista ne nasleđuje ništa nepotrebno iz build faze — samo eksplicitno kopirani (COPY --from=) artefakti treba da pređu u finalni image, ne cela build faza
  • Za tajne (kredencijale, tokene) koje su potrebne tokom build procesa (npr. pristup privatnom paket registru), koristiti namensku BuildKit funkcionalnost za tajne (--secret), koja garantuje da tajna ne ostane zapisana ni u jednom sloju finalnog image-a, čak ni privremeno

Šta se slučajno ušunja u image

Docker build proces po difoltu šalje ceo sadržaj direktorijuma (build context) Docker daemon-u, i sve što nije eksplicitno isključeno može, namerno ili slučajno, završiti unutar nekog sloja image-a. Ovo je čest, a lako sprečiv izvor curenja osetljivih podataka — .env fajl sa lozinkama, .git direktorijum sa istorijom koda, privatni SSH ključevi zaboravljeni u istom direktorijumu kao Dockerfile.

Šta treba uraditi

  • Kreirati .dockerignore fajl za svaki projekat, po istom principu kao .gitignore, i eksplicitno isključiti osetljive fajlove i direktorijume (.env, .git, privatne ključeve, fajlove sa kredencijalima) iz build konteksta
  • Nikad ne koristiti standardnu ARG ili ENV instrukciju u Dockerfile-u za prenošenje tajni tokom build procesa — obe ostavljaju trag u istoriji slojeva image-a, čitljiv čak i posle build-a preko `docker history` ili sličnog alata, bez obzira na to što promenljiva "nestane" iz konačnog runtime okruženja
  • Skenirati sam izgrađen image na prisustvo tajni (ne samo izvorni kod pre build-a) kao deo CI/CD procesa — tajna može ući u image kroz keširan sloj ili preko posredne zavisnosti, ne samo kroz direktnu COPY instrukciju koju je programer svesno napisao

Napomena o riziku: ako je tajna već jednom ušla u sloj postojećeg, objavljenog image-a, njeno naknadno "brisanje" iz Dockerfile-a i ponovni build ne uklanja je iz prethodno već distribuiranih verzija tog image-a — svako ko je povukao stariju verziju i dalje ima pristup toj tajni unutar sopstvene kopije. Ako se otkrije da je tajna procurela na ovaj način, jedini pouzdan odgovor je rotacija same tajne (promena lozinke, poništavanje tokena), ne samo ispravka Dockerfile-a.

Skeniranje na poznate ranjivosti — i zašto "nula nalaza" ne znači "bezbedno"

Alati poput Trivy-ja ili Grype-a upoređuju pakete instalirane unutar image-a sa bazama poznatih ranjivosti (CVE) i prijavljuju šta je pronađeno, sa nivoom ozbiljnosti za svaki nalaz. Ovo je neophodan, ali ne i dovoljan korak — dobra ilustracija ograničenja ovog pristupa je slučaj otkriven 2025. godine, kada su istraživači pronašli da desetine zvaničnih Debian-baziranih Docker Hub image-a i dalje sadrže poznati XZ Utils backdoor (CVE-2024-3094, sa maksimalnom CVSS ocenom 10.0) mesecima nakon što je ranjivost javno objavljena i zakrpljena — timovi koji su svoj image "zaključali" (pinovali) na stariju, tada naizgled stabilnu oznaku, nesvesno su nasledili taj backdoor zajedno sa njom.

Vredi eksplicitno naglasiti i drugo ograničenje: standardni skeneri ranjivosti proveravaju poznate pakete operativnog sistema i deklarisane zavisnosti aplikacije — oni ne otkrivaju greške u konfiguraciji na nivou aplikacije, tvrdo kodirane tajne u samom izvornom kodu, niti namerno zlonameran, prilagođen kod. "Nula CVE nalaza" znači da skener nije pronašao poznate, katalogizovane probleme — ne da je image u apsolutnom smislu bezbedan.

Šta treba uraditi

  • Uključiti skeniranje ranjivosti kao obavezan korak u CI/CD procesu, ne kao opcionalnu, povremenu proveru — svaki image treba skenirati pre nego što uđe u registar, i ponovo pri promociji iz test u produkciono okruženje
  • Definisati jasnu politiku koja stvarno prekida (fail) build proces kad se pronađu ranjivosti visoke ili kritične ozbiljnosti, umesto da se rezultat skeniranja samo generiše kao izveštaj koji niko ne čita
  • Redovno, po unapred definisanom rasporedu, ponovo graditi (rebuild) postojeće image-e čak i kad se aplikativni kod nije promenio — ovo povlači najnovije, zakrpljene verzije baznog image-a i njegovih paketa, po istom principu discipline ažuriranja opisanom u ranijoj seriji o hardeningu Linux servera
  • Dodatno, gde je moguće, verifikovati poreklo image-a kroz digitalne potpise (npr. Cosign) pre nego što se image pokrene u produkciji — ovo potvrđuje da image zaista dolazi iz očekivanog, poverljivog izvora i nije usput izmenjen
  • Ne oslanjati se isključivo na skener ranjivosti kao jedinu proveru bezbednosti — kombinovati ga sa praksama iz ovog i prethodnog dela serije (minimalni bazni image, multi-stage build, nemontiranje Docker socket-a), jer svaka pojedinačna mera pokriva drugačiji deo rizika

Sledeći deo serije prelazi na runtime bezbednost — konkretna podešavanja kontejnera u trenutku pokretanja (read-only fajl sistem, ograničenja resursa, seccomp profili) koja ograničavaju štetu čak i kad sve prethodno opisane mere iz nekog razloga ipak zakažu.

Comments

Popular posts from this blog

Šta je ISO 27001 i da li je mojoj firmi stvarno potreban?

ISO 22301:2019 u praksi — Praktična obuka na primeru fabrike piva

ISO 27031: Upravljanje IRBC-om i usklađivanje sa ciljevima kontinuiteta (klauzula 6.1–6.3)