Container Hardening – Deo 3: Docker runtime bezbednost

Prva dva dela serije pokrila su host i image — okruženje u kojem kontejner radi, i od čega je sagrađen. Treći deo se bavi trenutkom kad se kontejner stvarno pokrene: konkretnim podešavanjima koja ograničavaju šta pokrenut kontejner sme da radi, čak i ako je sve prethodno (bezbedan host, čist image) na neki način zaobiđeno ili zakazalo. Ovo je poslednja linija odbrane pre nego što kompromitovan proces unutar kontejnera dobije priliku da napravi stvarnu štetu — i vredi objasniti tehnički kako svaki mehanizam radi, jer se često pominju zajedno bez jasnog razgraničenja šta koji od njih štiti.

Seccomp i AppArmor rade na različitim nivoima — i zato se dopunjuju

Ovo je razlika koja se često zamagljuje, pa vredi razjasniti je eksplicitno. Seccomp (Secure Computing Mode) je mehanizam koji filtrira sistemske pozive (syscalls) — direktne zahteve koje proces upućuje kernelu za osnovne operacije poput otvaranja fajla, kreiranja procesa, ili manipulacije memorijom. Seccomp profil kaže, u suštini: "ovaj proces sme da pozove samo ove tačno navedene sistemske pozive, ništa van te liste."

AppArmor (obrađen detaljnije u ranijem delu serije o hardeningu Linux servera), s druge strane, radi na višem nivou apstrakcije — ne ograničava sistemske pozive same po sebi, već ono do čega proces preko njih pokušava da dođe: koje fajl putanje sme da čita ili menja, koje mrežne operacije sme da izvrši, koje Linux mogućnosti (capabilities) sme da koristi.

Ova razlika u nivou objašnjava zašto se oba mehanizma zajedno koriste, ne kao dupliranje iste zaštite, već kao dva odvojena sloja: čak i ako napadač pronađe način da zaobiđe ograničenje sistemskih poziva (npr. kroz neki nekonvencionalan, redak poziv koji seccomp profil nije predvideo), AppArmor i dalje ograničava do kojih fajlova ili mrežnih resursa taj poziv stvarno može da dođe. Isti princip odbrane u dubinu (defense in depth) koji se provlači kroz svaku seriju na ovom blogu — nijedan pojedinačan sloj se ne tretira kao dovoljan sam po sebi.

Šta treba uraditi

  • Nikad ne pokretati kontejnere sa isključenim (--security-opt seccomp=unconfined) podrazumevanim seccomp profilom — Docker-ov podrazumevani profil već blokira stotinak poznato opasnih sistemskih poziva (uključujući one koji omogućavaju direktnu manipulaciju kernel modulima ili neobične oblike montiranja fajl sistema) uz dozvoljavanje svega što tipična aplikacija stvarno koristi, pa je isključivanje ove zaštite retko opravdano
  • Za posebno osetljive ili visoko izložene kontejnere, razmotriti prilagođen, uži seccomp profil koji dozvoljava samo sistemske pozive koje ta konkretna aplikacija stvarno koristi — ovo zahteva analizu stvarnog ponašanja aplikacije (npr. praćenjem koji pozivi se izvršavaju tokom normalnog rada), ali pruža znatno strože ograničenje od generičkog podrazumevanog profila
  • Primenjivati AppArmor profil na kontejnere (Docker po difoltu učitava osnovni profil, gde je AppArmor dostupan na hostu) i, za kritične radne opterećenja, razmotriti prilagođen profil koji dodatno ograničava fajl putanje i mrežne operacije specifično za tu aplikaciju
  • Kombinovati oba mehanizma sa smanjenjem Linux capabilities opisanim u prvom delu serije — tri sloja (seccomp, AppArmor, capabilities) rade na različitim aspektima istog problema, i njihova kombinacija je ono što stvarno otežava napadaču da iz kompromitovanog kontejnera pobegne na host

Napomena o riziku: prestrog, ručno pisan seccomp profil koji ne dozvoljava neki sistemski poziv koji aplikacija povremeno, retko koristi (npr. samo pri startu ili pri specifičnom, ređe korišćenom putu koda) može izazvati grešku koja se ne ispolji odmah pri testiranju, već tek kasnije, u produkciji, u trenutku kad se taj redak scenario prvi put desi. Graditi prilagođene profile postepeno, počev od "learning" ili "audit" pristupa (beleženje šta bi bilo blokirano, bez stvarnog blokiranja) pre prelaska na stvarno ograničavajući profil — isti princip permissive/complain testiranja opisan za SELinux i AppArmor u ranijem delu serije.

Fajl sistem samo za čitanje — kontejneru retko treba da menja sopstveni sadržaj

Kontejner podrazumevano ima pisiv (writable) fajl sistem, iako većina aplikacija, jednom pokrenutih, nema stvarnu potrebu da menja sopstvene programske fajlove tokom rada. Ako napadač uspe da izvrši kod unutar kontejnera, pisiv fajl sistem mu omogućava da tu zapiše dodatne alate, izmeni postojeće izvršne fajlove, ili na drugi način ostavi trajniji trag unutar tog kontejnera. Fajl sistem postavljen isključivo za čitanje (read-only) ovo sprečava na osnovnom nivou — ako fajl sistem fizički ne dozvoljava pisanje, napadaču ta opcija jednostavno ne postoji, bez obzira na to koje privilegije uspe da pribavi unutar kontejnera.

Šta treba uraditi

  • Pokretati kontejnere sa opcijom --read-only (ili odgovarajućim podešavanjem u compose fajlu) kao podrazumevanim izborom, posebno za produkciona radna opterećenja
  • Za aplikacije kojima je zaista potreban privremeni prostor za pisanje (keš fajlovi, privremeni upload, log fajlovi pre nego što se prosleđuju centralnom sistemu opisanom u ranijoj seriji), koristiti tmpfs montiranje za tačno određene, uske putanje — ovo daje aplikaciji prostor za pisanje koji postoji samo u memoriji i nestaje pri restartu kontejnera, umesto da se ceo fajl sistem ostavi pisivim "za svaki slučaj"
  • Testirati aplikaciju temeljno nakon uvođenja read-only fajl sistema — neke aplikacije pokušavaju da pišu na neočekivana mesta (privremeni fajlovi, keš biblioteka, PID fajlovi) koja treba unapred identifikovati i pokriti odgovarajućim tmpfs montiranjem

Ograničavanje resursa — zaštita od iscrpljivanja, ne samo od upada

Ne svaki bezbednosni problem je pokušaj krađe podataka ili preuzimanja kontrole — kontejner koji, greškom u kodu ili namernim napadom, potroši sav dostupan CPU ili memoriju hosta, može onesposobiti sve ostale kontejnere na tom istom hostu, čak i ako nikad nije "probio" sopstvenu izolaciju u klasičnom smislu. Ovo je poseban rizik na hostu koji pokreće više kontejnera različitih aplikacija ili različitog nivoa poverenja jedne prema drugoj.

Šta treba uraditi

  • Postaviti eksplicitna ograničenja memorije i CPU-a za svaki kontejner (--memory, --cpus ili odgovarajuća polja u compose/Kubernetes definiciji), umesto da se kontejner ostavi bez ograničenja i oslanja na to da će se "ponašati pristojno"
  • Ograničiti broj procesa koje kontejner sme da kreira (--pids-limit) — ovo sprečava jedan specifičan, čest oblik iscrpljivanja resursa (fork bomb), gde proces brzo kreira ogroman broj potprocesa dok ne iscrpi dostupne resurse sistema
  • Ograničiti broj otvorenih fajl deskriptora (--ulimit nofile) i broj automatskih restartova pri padu (--restart=on-failure:N sa konkretnim brojem, ne bezuslovno ponavljanje unedogled) — kontejner koji se ruši i restartuje u beskonačnoj petlji troši resurse bez ikakve koristi

Napomena o riziku: prenisko postavljena ograničenja resursa mogu izazvati da aplikacija bude nasilno prekinuta (OOM kill kod memorijskog ograničenja) u trenucima legitimno povišenog opterećenja, ne samo u slučaju stvarnog napada ili greške. Postaviti ograničenja na osnovu stvarno izmerenog obrasca korišćenja resursa te konkretne aplikacije (kroz period posmatranja pod realnim opterećenjem), ne na proizvoljno izabranu, "razumnu" vrednost bez ikakve prethodne provere.

Sve zajedno — slojevi koji se sabiraju

Vredi na kraju eksplicitno povezati sve što je opisano kroz prva tri dela serije, jer pojedinačno gledano nijedna mera ne deluje dovoljno impresivno, dok njihova kombinacija stvarno menja težinu napada. Rootless daemon (prvi deo) znači da čak i puno "probijanje" kontejnera ne vodi direktno do root-a na hostu. Minimalan, skeniran image (drugi deo) znači da napadaču na raspolaganju nema ni shell-a ni nepotrebnih alata čak i ako uspe da izvrši kod. Dropovane capabilities, seccomp, AppArmor i read-only fajl sistem (ovaj deo) znače da čak i taj izvršen kod ima drastično ograničen prostor za dalje delovanje. Ograničenja resursa sprečavaju da čak i neuspešan pokušaj napada postane sam po sebi razlog za prekid rada ostatka sistema.

Nijedan pojedinačan sloj nije "nepробojan" — svrha je da kombinacija svih slojeva učini uspešan, potpun proboj iz kontejnera na host scenarijem koji zahteva istovremeno zaobilaženje više nezavisnih mehanizama, ne samo jednog previda u konfiguraciji.

Ovim se zaokružuje Docker deo serije. Sledeći deo prelazi na Kubernetes — orkestraciju kontejnera na više hostova, gde se veći deo principa iz ove tri celine ponovo pojavljuje, ali uz dodatan, poseban sloj pitanja: ko sme šta da radi unutar samog klastera, kako se pravila (poput onih opisanih u ovom tekstu) primenjuju dosledno na hiljade pod-ova odjednom, i šta je novo i specifično za orkestrisano, distribuirano okruženje.

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)