Container Hardening – Deo 1: Docker – bezbednost hosta i osnovna izolacija

Ovim tekstom počinje nova serija posvećena bezbednosti kontejnerizovanih radnih opterećenja — prvo Docker, zatim Kubernetes u nastavku serije. Kontejneri se često percipiraju kao inherentno izolovani i time bezbedni po difoltu — pull-uješ image, pokreneš ga, i on radi u sopstvenom, odvojenom prostoru. Ova percepcija je delimično tačna i delimično opasna: izolacija kontejnera je stvarna, ali nikad apsolutna, i podrazumevana Docker konfiguracija je optimizovana za praktičnost prvog korišćenja, ne za produkcionu bezbednost. Prvi deo pokriva ono što je fundamentalno — bezbednost samog Docker daemon-a i hosta na kojem radi.

Zašto host-nivo hardening dolazi prvi

Isti princip koji smo primenili na Hyper-V u ranijoj seriji na ovom blogu važi identično i ovde: kontejner nije virtuelna mašina sa sopstvenim jezgrom (kernel-om) — svi kontejneri na jednom hostu dele isti Linux kernel host mašine. To znači da je granica izolacije tanja nego kod pune virtuelizacije, i da kompromitacija Docker daemon-a ili hosta praktično poništava svaku izolaciju koju kontejneri međusobno imaju. Bezbednost hosta je gornja granica bezbednosti svega što se na njemu izvršava — princip koji se ponavlja kroz svaku seriju na ovom blogu, jer je fundamentalno tačan u svakom sloju infrastrukture.

Docker socket — najvažnija stvar koju treba razumeti u celoj seriji

Docker daemon (proces koji stvarno upravlja kontejnerima) se podrazumevano kontroliše preko Unix socket-a na putanji /var/run/docker.sock. Ovo je tehnički detalj koji zaslužuje detaljno objašnjenje, jer njegovo nerazumevanje je uzrok velikog broja stvarnih bezbednosnih incidenata: bilo šta što ima pristup pisanja na taj socket ima, u praksi, pun root pristup samom hostu — ne samo kontrolu nad kontejnerima.

Razlog je jednostavan kad se objasni: preko tog socket-a je moguće narediti Docker daemon-u da pokrene novi kontejner sa proizvoljnim podešavanjima — uključujući podešavanje koje montira ceo fajl sistem hosta (korenski direktorijum "/") unutar tog novog kontejnera. Kad se to uradi, granica između "unutar kontejnera" i "na hostu" praktično nestaje — proces unutar tog kontejnera može da čita, piše i menja bilo šta na hostu, jer mu je host fajl sistem direktno dostupan. Zbog toga je pristup Docker socket-u ekvivalentan root pristupu na hostu, bez obzira na to koliko "obično" izgleda proces koji taj pristup ima.

Šta treba uraditi

  • Nikad ne montirati Docker socket (/var/run/docker.sock) unutar kontejnera, čak i kad neki alat ili tutorijal to predlaže kao "praktično rešenje" za kontejner koji treba da upravlja drugim kontejnerima — ovo je jedna od najčešćih, a najozbiljnijih grešaka u praksi
  • Ako je alatu zaista, dokumentovano potreban pristup Docker API-ju (npr. alat za monitoring ili CI/CD koji pokreće build-ove), koristiti posrednički proxy koji ograničava koje tačno operacije taj alat sme da izvrši preko socket-a, umesto direktnog pristupa punom API-ju
  • Ako se Docker daemon izlaže preko mreže (TCP, portovi 2375/2376) umesto lokalnog socket-a, obavezno koristiti uzajamnu TLS autentifikaciju (mTLS) — nešifrovan TCP pristup Docker daemon-u je ekvivalentan ostavljanju root pristupa hostu otvorenim ka mreži bez ikakve lozinke
  • Ako mrežni pristup daemon-u uopšte nije potreban (najčešći slučaj), ne uključivati ga — koristiti lokalni Unix socket i, gde je udaljeno upravljanje zaista potrebno, SSH-bazirano povezivanje koje ponovo koristi već uspostavljene SSH ključeve i pristup opisan u ranijoj seriji o hardeningu Linux servera

Napomena o riziku: ovo nije podešavanje koje "možda malo povećava rizik" — nezaštićen pristup Docker socket-u ili daemon-u je direktan, potpun put do kompromitacije celog hosta, ne samo jednog kontejnera. Ako je bilo koji postojeći kontejner u infrastrukturi trenutno montira socket, tretirati to nalaz kao hitan prioritet za ispravku, ne kao stavku za "kad se nađe vremena".

Privileged mod — gotovo nikad stvarno potreban

Docker nudi opciju pokretanja kontejnera u privilegovanom modu (--privileged), koja kontejneru daje praktično sve Linux mogućnosti (capabilities) i uklanja veći deo standardnih cgroup ograničenja. Ovo efektivno poništava veliki deo izolacije koju kontejner inače pruža — privilegovan kontejner može da radi gotovo sve što bi mogao i proces direktno na hostu.

Ovaj mod postoji za retke, legitimne slučajeve (npr. kontejner koji upravlja samom Docker infrastrukturom, ili direktno pristupa hardveru hosta), ali se u praksi često koristi kao brzo rešenje kad neka aplikacija "ne radi" bez njega — umesto da se identifikuje tačna, uža dozvola koja aplikaciji zaista nedostaje.

Šta treba uraditi

  • Tretirati zahtev za --privileged modom kao signal za detaljniju analizu, ne kao rutinsko rešenje — u velikoj većini slučajeva, aplikaciji je potrebna samo jedna ili dve konkretne dodatne mogućnosti, ne sve odjednom
  • Umesto punog privilegovanog moda, koristiti selektivno dodavanje konkretnih Linux capabilities (--cap-add) uz istovremeno uklanjanje svih podrazumevanih (--cap-drop=ALL kao polazna tačka, zatim dodati samo ono što je zaista potrebno)
  • Uključiti opciju --security-opt=no-new-privileges, koja sprečava proces unutar kontejnera da eskalira svoje privilegije čak i preko setuid izvršnih fajlova ili sličnih mehanizama — ovo je jednostavna, gotovo besplatna dodatna zaštita koja bi trebalo da bude podrazumevana za svaki kontejner

Rootless Docker — uklanjanje privilegije, ne samo ograničavanje

Podrazumevano, i sam Docker daemon i kontejneri koje pokreće rade sa root privilegijama na hostu. Ovo znači da ranjivost u samom daemon-u, ili uspešan "escape" iz kontejnera (izlazak iz izolacije nazad na host), automatski daje napadaču root pristup hostu, jer je upravo tim privilegijama daemon i radio.

Rootless Docker rešava ovaj problem na fundamentalnom nivou: sam daemon radi kao običan, neprivilegovan korisnik, koristeći Linux user namespace mehanizam da simulira root privilegije unutar kontejnera bez stvarnih root privilegija na hostu. Ako dođe do escape scenarija u rootless konfiguraciji, napadač izlazi u kontekst neprivilegovanog korisnika na hostu, ne u root kontekst — razlika koja u praksi znači razliku između ozbiljnog, ali ograničenog incidenta i potpune kompromitacije hosta.

Šta treba uraditi

  • Za nove implementacije, razmotriti rootless Docker kao podrazumevani izbor, posebno na hostovima koji pokreću više kontejnera ili kontejnere iz manje pouzdanih izvora
  • Gde rootless mod nije izvodljiv (neki mrežni ili storage scenariji imaju ograničenja u rootless konfiguraciji), koristiti user namespace remapping (userns-remap podešavanje u daemon.json) kao međukorak — ovo mapira root korisnika unutar kontejnera na neprivilegovanog korisnika na hostu, umanjujući, iako ne u potpunosti eliminišući, isti rizik
  • U samom Dockerfile-u, koristiti USER direktivu da se aplikacija unutar kontejnera izvršava kao neprivilegovan korisnik, nezavisno od toga da li je sam daemon rootless — ovo je dodatan, komplementaran sloj zaštite, ne zamena za host-nivo meru

Napomena o riziku: prelazak na rootless Docker na postojećoj, aktivnoj infrastrukturi je netrivijalna migracija — menja se putanja do socket-a, neka mrežna podešavanja rade drugačije, i pojedini volume mount scenariji zahtevaju prilagođavanje. Ovo planirati kao poseban projekat sa testiranjem u odvojenom okruženju, ne kao brzu izmenu konfiguracije na produkcionom hostu. Za nove implementacije, ovaj trošak se izbegava u potpunosti postavljanjem rootless moda od samog početka.

Mrežna izolacija između kontejnera

Podrazumevana Docker mreža (bridge) dozvoljava svim kontejnerima na njoj da međusobno komuniciraju bez ograničenja — praktično, "svako sa svakim" pristup, čak i kad aplikacije koje ti kontejneri pokreću nemaju stvarnu poslovnu potrebu za tom komunikacijom. Ovo je isti obrazac koji smo kritikovali u seriji o Windows Server hardeningu za mrežnu segmentaciju uopšte: podrazumevano "sve dozvoljeno" umesto svesno definisanog, ograničenog pristupa.

Šta treba uraditi

  • Kreirati odvojene, namenske Docker mreže za grupe kontejnera koje zaista treba da komuniciraju, umesto oslanjanja na podrazumevanu bridge mrežu za sve
  • Onemogućiti međusobnu komunikaciju kontejnera na podrazumevanoj mreži (icc: false u daemon.json) kao polaznu, restriktivniju tačku, uz eksplicitno kreiranje mreža tamo gde je komunikacija zaista potrebna
  • Za mreže koje ne treba da imaju izlaz ka internetu (npr. interna mreža za komunikaciju baze podataka i aplikacije), koristiti internal:true opciju, čime se ta komunikacija ograničava isključivo na kontejnere u toj mreži
  • Vezivati (bind) portove kontejnera na localhost (127.0.0.1) umesto na sve interfejse (0.0.0.0), osim kad je spoljna dostupnost zaista namerna i potrebna — isti princip opisan u ranijoj seriji za servise na Linux serverima uopšte

Sledeći deo serije prelazi na bezbednost Docker image-a — od čega se kontejner gradi, kako proveriti da li sadrži poznate ranjivosti pre nego što uopšte dođe do pokretanja, i zašto je izbor baznog image-a jedna od najuticajnijih odluka u celom procesu.

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)