Blog

Mely ügyfeleknek van valójában szükségük saját VM-re? Többszintű Docker-izolációs terv

Minden ügyfélnek VM-et adni túlzás. Így döntheti el, mennyi elkülönítésre van szüksége minden ügyfélnek – és automatizálhatja a döntést.

Összefoglaló

Az ügynökségek gyakran pánikba esnek, amikor egy ügyfél megkérdezi, mennyire van valójában elkülönítve az adataik a többi bérlőtől. A Docker névterei és cgroupjai valódi elkülönítést adnak, de ezek nem ugyanazok, mint egy hardverhatár. Ahelyett, hogy minden ügyfelet VM-en futtatnánk – vagy ami még rosszabb, minden ügyfelet ugyanúgy kezelnénk –, építsünk egy kis készletet az elkülönítési szintekből, és minden ügyfelet illesszünk egyhez az adatérzékenység, a bizalom és a megfelelőség alapján. Egy lezárt konténer (nem root, elvett képességek, seccomp, csak olvasható gyökérfájlrendszer) a legtöbb webhelyre elegendő; a szabályozott vagy ellenséges munkaterhelések VM-et vagy konténer-VM hibridet kapnak. Ez a bejegyzés egy ismételhető döntési folyamatot, egy összehasonlító táblázatot és egy őszinte pillantást ad arra, hogy mikor túlzás a nagyobb elkülönítés.

Eljutott már arra a pontra egy értékesítési hívásban, amikor az új ügyfél azt mondja: „egészségügyi szektor vagyunk, mutasd meg, hogy az adataink el vannak különítve a többi ügyfeledtől”, és inkább bármi másról beszélne?

Ez az ügynökségi probléma: nem egy tökéletes telepítés, hanem ugyanaz a megbízható telepítés ismétlődik tucatnyi ügyfélnél, különböző költségvetésekkel, kockázati profilokkal és megfelelőségi követelményekkel. Íme a becsületes változat. A Docker-izoláció valós, de specifikus. A névterek minden konténernek saját nézetet adnak a folyamatokról, a hálózatról és a fájlrendszerről; a cgroupok korlátozzák a CPU-t, a memóriát és a lemez I/O-t, így a bérlők nem éheztethetik ki egymást. Amit ez nem ad meg, az egy hardverfal a konténer és a host kernel között. Ha egy támadó kijut a konténerből, akkor abban az egyetlen kernelben vannak, amivel rendelkezel. A cikk további része ezt a kényelmetlen tényt ismételhető döntéssé alakítja: sorolja be minden ügyfelet az adatérzékenység és a bizalom alapján, alkalmazzon egy alapszintű megerősítési profilt, és csak akkor nyúljon VM-hez, ha a biztonsági rés költsége magasabb, mint a VM költsége.

Várj, nem a konténerek már elkülönítettek?

A Docker Linux névtereken és cgroupokon fut, és ezek a szavak valódi munkát végeznek. A névterek szétválasztják a folyamatazonosítókat, a hálózati vermeket, a csatolási pontokat és a felhasználókat, így az egyik konténerben lévő folyamat nem látja a másik folyamattábláját. A cgroupok korlátokat szabnak: adjon egy konténernek 0,5 CPU-t, 512 MB memóriát és egy fix blokk I/O súlyt, és pontosan azt kapja. Egy elszabadult hurok az egyik bérlőben lekorlátozódik, ahelyett, hogy leállítaná a szomszédot. Ha nem konfigurált korlátokat, akkor kihagyta a legalapvetőbb dolgot, amire a cgroupok valók.

Vegyünk egy egyszerű PHP alkalmazást az A konténerben. Látja a saját fájlrendszerét, a saját hálózati interfészét, a saját PID 1-jét. A B konténer ugyanazzal rendelkezik, de más nézettel. Ezek a névterek. Most sétáljon el, és hagyja ki a memóriakorlátot: az A konténer megtöltheti a host RAM-ját, és a B konténert kínlódásra kényszerítheti. Pontosan ezt hivatottak megakadályozni a cgroupok. De két konténer elkülöníthető egymástól névterekkel, és mégis osztoznak a host kernelén, ami minden konténer-szökési történet középpontjában áll. Egy kihasználás, amely eléri a kernelt, potenciálisan elérheti a host összes bérlőjét.

„A Docker izolált” – ez egy fél-igaz mondat. A pontos verzió: „A Docker névterekkel és cgroupokkal izolál, és a kernel sebezhetősége a robbanási sugár.” Mielőtt megbízna egy bérlőben, hogy nem megbízható kódot futtasson, gondolkodjon el ezen egy percig. A válasz nem az, hogy „soha ne használjunk konténereket” – ez a könnyű pánik. A válasz egy szintrendszer.

Szóval miért van szükségük néhány ügyfélnek többre, mint a névterek?

A becsületes válasz az, hogy az elkülönítés nem egy kapcsoló, hanem egy spektrum. Az egyik végén van egy teljesen megosztott konténer, ahol mindenki gyakorlatilag egy alkalmazásban van. A másik végén van egy külön VM minden bérlőnek, saját kernellel. A legtöbb ügynökségi munka a kényelmetlen középben él, és a közép nem bináris választás a „Docker megfelelő” és a „mindenkinek futtassunk VM-et” között.

Az, hogy egy ügyfél jobbra tolódik, nem a méretétől függ. Hanem négy kérdéstől:

  • Tárolnak szabályozott adatokat? Egészségügyi nyilvántartások, fizetési kártyaadatok, bármi, amit egy szabályozó érzékenynek nevez.
  • Van-e egy bérlőjükön bekövetkezett biztonsági résnek reális útja egy másik bérlőhöz? Ha tetszőleges kódot futtathatnak, akkor igen.
  • Megbízik-e a kódban és az azt telepítő emberekben? Egy ügyfél, aki a legolcsóbb szabadúszót veszi fel, nem azonos bizalmi szint, mint egy ügyfél, akinek a fejlesztőcsapatát ismeri.
  • Tartalmaz-e a szerződésük „dedikált”, „elkülönített” vagy „privát” kifejezést? Ha igen, már ígért egy szintet; az egyetlen feladat most a megfelelő kiválasztása.

Ha még nem tudja megválaszolni ezeket a kérdéseket, helyezze az ügyfelet egy alap szintre, és írja le a feltételezéseket. Ez nem biztonsági audit; ez egy józan ész ellenőrzés, amelyet minden ügyfélbevezetésnél megismétel.

Hogyan döntsek ügyfelenként anélkül, hogy minden alkalommal biztonsági auditot futtatnék?

Készítsen egy kis táblázatot, és tartsa magát hozzá. Nincs szüksége negyven cellás mátrixra. Négy szint szinte minden ügyfelet lefed, akit egy ügynökség lát.

ÜgyfélpozícióMi választja el őket valójábanMikor használjuk
1. szint: Megosztott alkalmazás/konténerCsak alkalmazáslogikaBelső segédprogramok, alacsony kockázatú adatok, olyan projektek, ahol mindenki kifejezetten egy bejelentkezési rendszerben van
2. szint: Ugyanaz a host, külön konténerekNévterek és cgroupokA legtöbb marketingoldal, kapcsolatfelvételi űrlapok, nincs érzékeny adat
3. szint: Lezárt konténer2. szint + nem root, elvett képességek, seccomp, csak olvasható gyökérfájlrendszer, hálózati szegmentációE-kereskedelem, PII, egyedi kód, amelyben nem bízik meg teljesen
4. szint: Bérlőnkénti VMHipervizor és külön kernelEgészségügy, pénzügy, megfelelőségi adminisztráció, nem megbízható kód, zajos szomszédok

Így néz ki ez a gyakorlatban. Egy pékség ügyfél, kapcsolatfelvételi űrlappal és Instagram-linkkel, a 2. szintre kerül: egy konténer egy megosztott hoston, alapértelmezett Docker-hálózat, erőforrás-korlátok, kész. Egy online áruház, amely ügyfélneveket, címeket és fizetési átirányításokat tárol, a 3. szintre kerül: ugyanaz a megosztott host, de a konténer nem root felhasználóként fut, nincs extra kernelképessége, seccomp-profilt használ, és csak a 443-as portot teszi elérhetővé. Egy orvosi adatfelvételi portál, amely védett egészségügyi információkat tárol, a 4. szintre kerül: VM bérlőnként, mert a biztonsági rés költsége nem az, hogy „majd kitakarítjuk”, hanem hogy „nem tudjuk megmutatni az ügyfélnek, hogy komolyan vettük őket.”

Az egész trükk az, hogy nem gondolod újra az architektúrát minden ügyfél számára. Csak kiválasztasz egy sort egy táblázatból, amelyben már megállapodtál. Így tud egy öt fős ügynökség száz webhelyet futtatni anélkül, hogy száz külön biztonsági megszállottsága lenne. Ez azt is jelenti, hogy a következő ügyfél nem kap olyan választ, ami attól függ, hogy melyik csapattag vette fel a telefont. A választások mögötti mélyebb architektúra-vitához ez a több-bérlős izolációs szintek tervezéséről szóló útmutató részletesebben tárgyalja a kompromisszumokat.

Hogyan néz ki valójában egy lezárt konténer?

Hagyjuk abba a „lezárt” kifejezést, és legyünk konkrétak. Ezt jelenti a 3. szint egy tipikus WordPress vagy PHP ügyfél esetében.

Először is, cserélje le a felhasználót. A legtöbb hivatalos kép még mindig rootként fut alapértelmezés szerint; a Dockerfile-ban hozzon létre egy nem root felhasználót, és az alkalmazást azzal a felhasználóval futtassa. Ez azonnal megszünteti a leggyakoribb módját annak, hogy egy konténer-kompromittáció host-kompromittációvá váljon. Másodszor, dobja el a nem szükséges képességeket. Futtassa a --cap-drop ALL paranccsal, és csak egyet adjon vissza, általában a NET_BIND_SERVICE-t, hogy az alkalmazás a 80-as porton hallgathasson. Ez önmagában nagyobb változás, mint amit a legtöbben várnának. Harmadszor, tegye a gyökérfájlrendszert csak olvashatóvá a --read-only kapcsolóval, és a writable könyvtárakat (feltöltések, adatbázis adatkönyvtár) csatolja fel kötetként vagy tmpfs-ként. Negyedszer, alkalmazzon seccomp-profilt, és ha a host támogatja, AppArmort vagy SELinuxot. Végül helyezze a konténert egy dedikált Docker-hálózatra, és csak azokat a portokat tegye elérhetővé, amelyeknek valóban elérhetőnek kell lenniük.

Nézzünk végig egy WordPress-példát. Az alapkép valószínűleg rootként fut, ezért hozzáad egy useradd lépést és egy USER utasítást. A konténert memóriakorláttal és CPU-korláttal futtatja, így a pluginforgalom kitörése nem árt a szomszédnak. Feltölti a /var/www/html/wp-content/uploads mappát írható kötetként. Beállítja a --read-only kapcsolót. Olyan hálózathoz csatlakoztatja, amelynek közelében nincs --privileged zászló. Az eredmény egy konténer, amely korábban „WordPress-oldal” volt, most pedig „olyan WordPress-oldal, amely véletlenül jobban le van zárva, mint a legtöbb virtuális privát szerver”.

Ha úgy érzi, hogy mindezt kézzel összerakni törékeny, van egy egyszerűbb középút: a Docker Enhanced Container Isolation, amely felhasználói névtér-izolációt és biztonságos konténerfutási környezetet használ. Ez egy legitim parancsikon, de nem felmentés a nem root vagy a képességelvetés kihagyására. A bérlőnek továbbra is ésszerű képre van szüksége. A különbség az, hogy a kernel felé néző támadási felület kisebb lesz anélkül, hogy egyik napról a másikra seccomp-szakértővé válna. Ha pontos sorrendet szeretne egyetlen bérlőhöz, a lépésről lépésre haladó izolációs megerősítési útmutató ezt a részt másolható parancsokká alakítja.

Mikor hagyom abba a rétegzést, és csak adok nekik egy VM-et?

Itt van az ellentmondásos rész: a nagyobb elkülönítés nem automatikusan jobb. A VM-ek hardverszintű elkülönítést, külön kernelt és sokkal kisebb támadási felületet adnak, ha a vendég kernel elesik. Pontosan ezt várják az egészségügyi és pénzügyi ügyfelek, amikor azt mondják: „elkülönítve szeretnénk lenni”. De minden VM javítási, biztonsági mentési és számítási költséget ad, és megsokszorozza a flotta frissítésének munkáját. Ha minden ügyfélnek VM-et ad, mert egy ügyfél egyszer azt mondta, hogy a Docker megijesztette őket, akkor valódi pénzt költött biztonsági színházra.

A VM a helyes válasz, ha a kockázat bérlőnként magasabb, mint a VM üzemeltetési költsége bérlőnként. Ez szabályozott adatokat, írásbeli megfelelőségi követelményeket, nem megbízható harmadik féltől származó kódot vagy olyan ügyfelet jelent, akinek szüksége van a zajos szomszéd eltávolítására. Ez a helyes válasz akkor is, ha az ügyfél szerződése szó szerint dedikált környezetet ígér, mert a „konténer” nem az, amit elképzelnek, amikor aláírják a „dedikált” szót.

De a VM nem mentesíti a hanyag konténert. Gyakori csapda, hogy az ügyfelet VM-be helyezik, majd kihagyják a megerősítést, mert „a VM megvédi őket”. A VM a hostot védi a bérlőtől, nem a bérlőt a saját rossz képétől. Továbbra is szeretne nem root felhasználót, elvett képességeket és seccomp-ot a VM-en belül. A hibrid megközelítés – konténerek egy VM-en belül – gyakran az ideális pont: a VM adja a határt a megfelelőségi beszélgetésekhez, a konténer pedig a már ismert telepítési munkafolyamatot. Ennek a vitának van egy hosszabb változata: Minden bérlőnek saját VM-re van szüksége?, de a rövid válasz az, hogy a VM a szerződésért van, nem a félelemért.

Hogyan teszem ezt ismételhetővé minden ügyfélnél?

Úgy teszi ismételhetővé, hogy a szintrendszert sablonná teszi, nem emlékké. Tartson egy könyvtárat Compose-fájlokkal, szintenként egyet: tier2-baseline, tier3-locked, tier4-vm-hybrid. Amikor új ügyfél érkezik, másolja a sablont, módosítsa a környezeti változókat, és már tudja is az elkülönítés alakját, mielőtt egy sor új infrastruktúrát írt volna.

Ezután írja le a döntést. Nem egy 400 oldalas biztonsági jelentést, hanem egy rövid bekezdést az ügyfél repójában: milyen adatokat tárolnak, melyik szinten vannak, miért, és mi emelné feljebb őket egy szinttel. Ez a bekezdés többet ér, mint száz tűzfalszabály, mert ezt mutathatja meg a következő auditornak vagy a következő aggódó ügyfélnek. Az is megakadályozza, hogy emlékeznie kelljen arra, miért kapott a pékség 2. szintet és az e-kereskedelmi üzlet 3. szintet, miután az eredeti értékesítési hívás elhalványult.

Automatizálja az unalmas ellenőrzéseket. A CI-je minden ügyfélképet szkenneljen, és buktassa meg a buildet, ha rootként fut, ha minden képességgel rendelkezik, vagy ha a szint által megengedett portokon kívül más portot próbál publikálni. Egyik sem egzotikus; csak azt biztosítja, hogy a sablont ne törje el véletlenül egy jószándékú fejlesztő. Ha úgyis a környező hoszting-munkafolyamatot építi, a gyártásra kész Docker-hoszting stratégiákról szóló cikk lefedi a konténerek meghatározása után következő részt.

Egyik sem csábító. Egyik blogbejegyzés sem fogja úgy hangozni a „bérlői izolációt”, mintha egy zöldmezős architektúra-diagram izgalmas lenne. De ez a különbség az olyan ügynökség között, amelyik a „mennyire vagyunk elkülönítve?” kérdésre keresztbe tett ujjakkal azt válaszolja, hogy „teljesen”, és az között, amelyik fel tud mutatni egy szintet, egy konfigurációt és egy okot. A konténerek nem varázsfal. A VM-ek nem csodafegyverek. A szintrendszer csak egy döntés, amelyet leír és újrahasznál – és egy ügynökség számára az ismételhetőség az egész játék.

Sources (5)