Blog

Którzy klienci naprawdę potrzebują własnej maszyny wirtualnej? Warstwowy plan izolacji Dockera

VM dla każdego klienta to przesada. Oto jak zdecydować, ile izolacji potrzebuje każdy najemca—i zautomatyzować tę decyzję.

Podsumowanie

Agencje często panikują, gdy klient pyta, jak naprawdę odizolowane są ich dane od innych najemców. Przestrzenie nazw (namespaces) i grupy cgroups w Dockerze zapewniają realną izolację, ale nie są tym samym co granica sprzętowa. Zamiast uruchamiać każdego klienta na maszynie wirtualnej — lub co gorsza, traktować każdego klienta jednakowo — zbuduj mały zestaw poziomów izolacji i dopasuj każdego klienta do jednego z nich na podstawie wrażliwości danych, zaufania i zgodności z przepisami. Utwardzony kontener (nie-root, odebrane uprawnienia, seccomp, root w trybie tylko do odczytu) pokrywa większość witryn; regulowane lub wrogie obciążenia otrzymują maszynę wirtualną lub hybrydę kontener-w-maszynie-wirtualnej. Ten wpis przedstawia powtarzalny proces decyzyjny, tabelę porównawczą i szczere spojrzenie na to, kiedy większa izolacja jest przesadą.

Czy jesteś w trakcie rozmowy sprzedażowej, podczas której nowy klient mówi „jesteśmy firmą medyczną, pokaż mi, że nasze dane są odizolowane od twoich innych klientów”, a Ty wolałbyś rozmawiać o czymkolwiek innym?

To jest problem agencji: nie jedno idealne wdrożenie, ale to samo niezawodne wdrożenie powtarzane u kilkunastu klientów z różnymi budżetami, profilami ryzyka i wymaganiami zgodności. Oto szczera wersja. Izolacja w Dockerze jest realna, ale jest specyficzna. Przestrzenie nazw dają każdemu kontenerowi własny widok procesów, sieci i systemu plików; grupy cgroups ograniczają CPU, pamięć i I/O dysku, aby najemcy nie mogli się nawzajem zagłodzić. Czego to nie zapewnia, to sprzętowa bariera między kontenerem a jądrem hosta. Jeśli atakujący wydostanie się z kontenera, jest wewnątrz jedynego jądra, jakie masz. Reszta tego artykułu zamienia ten niewygodny fakt w powtarzalną decyzję: sklasyfikuj każdego klienta według wrażliwości danych i zaufania, zastosuj podstawowy profil utwardzania i sięgnij po maszynę wirtualną tylko wtedy, gdy koszt naruszenia bezpieczeństwa jest wyższy niż koszt maszyny wirtualnej.

Zaraz, czy kontenery nie są już izolowane?

Docker działa na przestrzeniach nazw i grupach cgroups Linuksa, i te słowa wykonują prawdziwą pracę. Przestrzenie nazw oddzielają identyfikatory procesów, stosy sieciowe, punkty montowania i użytkowników, dzięki czemu proces w jednym kontenerze nie widzi tablicy procesów innego. Grupy cgroups ustawiają limity: daj kontenerowi 0,5 CPU, 512 MB pamięci i stałą wagę wejścia/wyjścia blokowego, i dokładnie to otrzymuje. Szalejąca pętla u jednego najemcy zostaje ograniczona zamiast obalić sąsiada. Jeśli nie skonfigurowałeś limitów, pominąłeś najbardziej podstawową rzecz, do której służą cgroups.

Weź prostą aplikację PHP w kontenerze A. Widzi ona własny system plików, własny interfejs sieciowy, własny PID 1. Kontener B ma to samo, ale inny widok. To są przestrzenie nazw. Teraz odejdź i pomin limity pamięci: kontener A może wypełnić pamięć RAM hosta i sprawić, że kontener B będzie się wlec. To właśnie mają zapobiegać cgroups. Ale dwa kontenery mogą być odizolowane od siebie przez przestrzenie nazw i nadal współdzielić jądro hosta, co jest sednem każdej historii o ucieczce z kontenera. Exploit, który dotrze do jądra, może potencjalnie dotrzeć do każdego najemcy na tym hoście.

„Docker jest izolowany” to zdanie w połowie prawdziwe. Dokładna wersja brzmi: „Docker izoluje za pomocą przestrzeni nazw i cgroups, a podatność jądra jest promieniem rażenia”. Zanim zaufasz najemcy, że uruchomi niezaufany kod, zastanów się nad tym przez chwilę. Odpowiedzią nie jest „nigdy nie używaj kontenerów” — to łatwa panika. Odpowiedzią jest system poziomów.

Dlaczego więc niektórzy klienci potrzebują czegoś więcej niż przestrzeni nazw?

Szczera odpowiedź jest taka, że izolacja nie jest przełącznikiem, to spektrum. Na jednym końcu masz w pełni współdzielony kontener, gdzie wszyscy są efektywnie w jednej aplikacji. Na drugim końcu masz osobną maszynę wirtualną dla każdego najemcy z własnym jądrem. Większość pracy agencji żyje w niewygodnym środku, a środek nie jest binarnym wyborem między „Docker jest w porządku” a „uruchom maszynę wirtualną dla wszystkich”. To, co przesuwa klienta w prawo, to nie jego rozmiar. To cztery pytania:

  • Czy przechowują dane podlegające regulacjom? Dokumentacja medyczna, dane kart płatniczych, cokolwiek, co organ regulacyjny uznałby za wrażliwe.
  • Czy naruszenie bezpieczeństwa ich najemcy ma realistyczną ścieżkę do innego najemcy? Jeśli mogą uruchomić dowolny kod, tak.
  • Czy ufasz kodowi i ludziom, którzy go wdrażają? Klient, który zatrudnia najtańszego freelancera, to nie ten sam poziom zaufania, co klient, którego zespół deweloperski znasz.
  • Czy ich umowa mówi „dedykowany”, „izolowany” lub „prywatny”? Jeśli tak, już obiecałeś poziom; jedyne zadanie to wybrać odpowiedni.

Jeśli nie możesz jeszcze odpowiedzieć na te pytania, umieść klienta w podstawowym poziomie i zapisz założenia. To nie jest audyt bezpieczeństwa; to test rozsądku, który powtarzasz przy każdym onboardingu.

Jak zdecydować o każdym kliencie bez przeprowadzania audytu bezpieczeństwa za każdym razem?

Stwórz małą tabelę i trzymaj się jej. Nie potrzebujesz macierzy z czterdziestoma komórkami. Cztery poziomy pokryją prawie każdego klienta, jakiego widzi agencja.

Pozycja klientaCo faktycznie ich rozdzielaUżyj, gdy
Poziom 1: Współdzielona aplikacja/kontenerTylko logika aplikacjiWewnętrzne narzędzia, dane niskiego ryzyka, projekty, w których wszyscy są jawnie w jednym systemie logowania
Poziom 2: Ten sam host, osobne konteneryPrzestrzenie nazw i cgroupsWiększość witryn marketingowych, formularze kontaktowe, brak wrażliwych danych
Poziom 3: Utwardzony kontenerPoziom 2 + nie-root, odebrane uprawnienia, seccomp, root w trybie tylko do odczytu, segmentacja sieciE-commerce, dane osobowe (PII), niestandardowy kod, któremu nie do końca ufasz
Poziom 4: Maszyna wirtualna na najemcęHiperwizor i osobne jądroOpieka zdrowotna, finanse, dokumentacja zgodności, niezaufany kod, hałaśliwi sąsiedzi

Oto jak to wygląda w praktyce. Klient z piekarni z formularzem kontaktowym i linkiem do Instagrama trafia do Poziomu 2: jeden kontener na współdzielonym hoście, domyślna sieć Dockera, limity zasobów, gotowe. Sklep internetowy, który przechowuje imiona i nazwiska klientów, adresy i przekierowania płatności, trafia do Poziomu 3: ten sam współdzielony host, ale kontener działa jako użytkownik nie-root, nie ma dodatkowych uprawnień jądra, używa profilu seccomp i udostępnia tylko port 443. Portal przyjęć medycznych, który przechowuje chronione informacje zdrowotne, trafia do Poziomu 4: maszyna wirtualna na najemcę, ponieważ koszt naruszenia bezpieczeństwa to nie „posprzątamy to”, ale „nie możemy pokazać klientowi, że traktujemy ich poważnie”.

Cały trik polega na tym, że nie przemyślisz na nowo architektury dla każdego klienta. Wybierasz wiersz z tabeli, na którą już się zgodziłeś. W ten sposób pięcioosobowa agencja może prowadzić sto witryn bez stu osobnych obsesji bezpieczeństwa. Oznacza to również, że następny klient nie otrzyma odpowiedzi zależnej od tego, który członek zespołu odebrał telefon. W przypadku głębszej debaty architektonicznej stojącej za tymi wyborami, ten przewodnik na temat projektowania poziomów izolacji w środowisku wielonajemnym omawia kompromisy bardziej szczegółowo.

Jak właściwie wygląda utwardzony kontener?

Przestańmy mówić „utwardzony” i przejdźmy do konkretów. Oto, co Poziom 3 oznacza dla typowego klienta WordPress lub PHP.

Po pierwsze, zmień użytkownika. Większość oficjalnych obrazów nadal domyślnie działa jako root; w swoim Dockerfile utwórz użytkownika nie-root i uruchom aplikację jako ten użytkownik. To natychmiast usuwa najczęstszy sposób, w jaki kompromitacja kontenera staje się kompromitacją hosta. Po drugie, usuń uprawnienia, których nie potrzebujesz. Uruchom z --cap-drop ALL i dodaj z powrotem tylko jedno, zwykle NET_BIND_SERVICE, aby aplikacja mogła nasłuchiwać na porcie 80. To samo w sobie jest większą zmianą, niż większość ludzi oczekuje. Po trzecie, ustaw root filesystem na tryb tylko do odczytu za pomocą --read-only i zamontuj katalogi do zapisu (uploady, katalog danych bazy danych) jako woluminy lub tmpfs. Po czwarte, zastosuj profil seccomp, a jeśli Twój host to obsługuje, AppArmor lub SELinux. Na koniec umieść kontener w dedykowanej sieci Dockera i udostępnij tylko te porty, które faktycznie muszą być osiągalne.

Przejdźmy przez przykład WordPressa. Obraz bazowy prawdopodobnie działa jako root, więc dodajesz krok useradd i dyrektywę USER. Uruchamiasz kontener z limitem pamięci i limitem CPU, aby skok ruchu spowodowany przez wtyczki nie zaszkodził sąsiadowi. Montujesz /var/www/html/wp-content/uploads jako wolumin zapisywalny. Ustawiasz --read-only. Podłączasz go do sieci, która nie ma w pobliżu flagi --privileged. Rezultat to kontener, który kiedyś był „stroną WordPress”, a teraz jest „stroną WordPress, która jest bardziej utwardzona niż większość wirtualnych serwerów prywatnych”.

Jeśli ręczne konfigurowanie tego wszystkiego wydaje się kruche, istnieje prostsza droga środkowa: Enhanced Container Isolation od Dockera, która wykorzystuje izolację przestrzeni nazw użytkownika i bezpieczne środowisko uruchomieniowe kontenerów. To prawowity skrót, ale nie jest to darmowa przepustka do pomijania nie-root lub usuwania uprawnień. Najemca nadal potrzebuje rozsądnego obrazu. Różnica polega na tym, że powierzchnia ataku skierowana na jądro staje się mniejsza bez potrzeby stawania się ekspertem seccomp z dnia na dzień. Jeśli chcesz poznać dokładną sekwencję dla pojedynczego najemcy, przewodnik utwardzania izolacji krok po kroku zamienia tę sekcję w polecenia do skopiowania i wklejenia.

Kiedy przestaję dodawać warstwy i po prostu daję im maszynę wirtualną?

Oto przekorna część: więcej izolacji nie jest automatycznie lepsze. Maszyny wirtualne dają izolację na poziomie sprzętu, osobne jądro i znacznie mniejszą powierzchnię ataku, jeśli jądro gościa upadnie. To dokładnie to, czego oczekują klienci z opieki zdrowotnej i finansów, kiedy mówią „chcemy być odizolowani”. Ale każda maszyna wirtualna dodaje koszty łatania, tworzenia kopii zapasowych i obliczeń oraz mnoży pracę związaną z utrzymaniem floty aktualnej. Jeśli dajesz każdemu klientowi maszynę wirtualną, ponieważ jeden klient powiedział ci kiedyś, że Docker go przeraża, kupiłeś teatr bezpieczeństwa za prawdziwe pieniądze.

Maszyna wirtualna jest właściwą odpowiedzią, gdy ryzyko na najemcę jest wyższe niż koszt operacyjny maszyny wirtualnej na najemcę. To oznacza dane podlegające regulacjom, pisemne wymagania zgodności, niezaufany kod osób trzecich lub klienta, który potrzebuje usunięcia hałaśliwego sąsiada. To także właściwa odpowiedź, gdy umowa klienta dosłownie obiecuje dedykowane środowisko, ponieważ „kontener” to nie to, co sobie wyobrażają, podpisując „dedykowany”.

Ale maszyna wirtualna nie usprawiedliwia niechlujnego kontenera. Częstą pułapką jest umieszczenie klienta w maszynie wirtualnej, a następnie pominięcie utwardzania, ponieważ „maszyna wirtualna ich chroni”. Maszyna wirtualna chroni hosta przed najemcą, a nie najemcę przed jego własnym złym obrazem. Nadal chcesz nie-root, odebranych uprawnień i seccomp wewnątrz tej maszyny wirtualnej. Hybrydowe podejście — kontenery wewnątrz maszyny wirtualnej — jest często najlepszym punktem: maszyna wirtualna zapewnia granicę dla rozmów o zgodności, a kontener daje ci znany już przepływ wdrażania. Dłuższa wersja tej debaty znajduje się w Czy każdy najemca powinien mieć własną maszynę wirtualną?, ale krótka odpowiedź brzmi: maszyna wirtualna jest dla umowy, nie dla strachu.

Jak sprawić, by było to powtarzalne dla każdego klienta?

Uczynisz to powtarzalnym, czyniąc system poziomów szablonem, a nie pamięcią. Trzymaj katalog plików Compose, po jednym na poziom: tier2-baseline, tier3-locked, tier4-vm-hybrid. Gdy pojawi się nowy klient, skopiuj szablon, zmień zmienne środowiskowe i już znasz kształt izolacji, zanim napiszesz linijkę nowej infrastruktury.

Następnie zapisz decyzję. Nie 400-stronicowy raport bezpieczeństwa, ale krótki akapit w repozytorium klienta: jakie dane przechowują, na którym poziomie są, dlaczego i co przeniesie ich na wyższy poziom. Ten akapit jest wart więcej niż sto reguł zapory, ponieważ to rzecz, którą możesz pokazać następnemu audytorowi lub następnemu zaniepokojonemu klientowi. Zapobiega też konieczności pamiętania, dlaczego piekarnia dostała Poziom 2, a sklep e-commerce Poziom 3, gdy pierwotna rozmowa sprzedażowa już zbladła.

Zautomatyzuj nudne kontrole. Niech Twój CI skanuje każdy obraz klienta i niech budowa nie przejdzie, jeśli działa jako root, jeśli ma wszystkie uprawnienia lub jeśli próbuje opublikować port inny niż te, które pozwala poziom. Nic z tego nie jest egzotyczne; to tylko upewnienie się, że szablon nie został przypadkowo zepsuty przez dobrze myślącego programistę. Jeśli i tak budujesz otaczający przepływ pracy hostingu, artykuł o strategiach hostingu Docker gotowych do produkcji obejmuje część, która następuje po zdefiniowaniu kontenerów.

Nic z tego nie jest efektowne. Żaden wpis na blogu nie sprawi, że „izolacja najemcy” zabrzmi tak ekscytująco, jak diagram architektury greenfield. Ale to jest różnica między agencją, która odpowiada „jak bardzo jesteśmy odizolowani?” z trzymanymi kciukami „całkowicie”, a taką, która może pokazać poziom, konfigurację i powód. Kontenery to nie magiczna ściana. Maszyny wirtualne to nie magiczna kula. System poziomów to po prostu decyzja, którą zapisujesz i wykorzystujesz ponownie — a dla agencji powtarzalność to cała gra.

Sources (5)