W tym artykule poruszymy kwestię nowych trendów dotyczących architektury systemów informatycznych i przyjrzymy się ich bezpieczeństwu i możliwościom dodatkowego zabezpieczenia naszej aplikacji.
Chodzi oczywiście o kontenery oraz o najpopularniejszą platformę służącą do konteneryzacji, czyli Docker.

Powszechna opinia głosi, że kontenery są niezwykle pomoce w zapewnieniu bezpieczeństwa, skalowalności i odtwarzalności tworzonego oprogramowania. Wzrost ich popularności jest obecnie jednym z najważniejszych trendów w informatyce.
Docker możemy traktować jako synonim konteneryzacji, gdyż praktycznie nie mają żadnej konkurencji.

Zaczniemy od krótkiego wprowadzenia, aby nakreślić jak rozumieć kontenery, jaką spełniają rolę i jak pomagają w tworzeniu oprogramowania.


Co to ten Docker?


Koncepcja kontenerów opiera się na metaforach i tak też jest tłumaczona. Dockerowy kontener można wyobrazić sobie jako fizyczny, przenośny pojemnik na drobiazgi z pokrywą, którą można zamykać i otwierać w razie potrzeby. Taki pojemnik ma następujące cechy:

  1. Przechowuje rzeczy – coś znajduje się w jego środku, a coś może się znajdować na zewnątrz.
  2. Jest przenośny – można go używać na komputerze osobistym, na komputerze współpracownika czy na serwerze w chmurze.
  3. Ma przejrzysty interfejs – można bez problemu wkładać oraz wyjmować z niego rzeczy. Ma wiele mechanizmów pozwalających na łatwą interakcje ze światem zewnętrznym. Otwierać określone porty komunikacyjne itp.
  4. Można go pozyskać zdalnie – puste kontenery można kupować i dostosowywać właśnie poprzez platformę Docker. Gdy potrzebujemy pudełka o określonych parametrach, możemy stworzyć go z przygotowanego wcześniej obrazu.

Drugą metaforą, w kontekście której można myśleć o kontenerze to żywa instancja. Kontener jest czymś w rodzaju narzędzia do tworzenia oraz niszczenia bytów. Nie jest tylko kodem. To kod, który po wykonaniu powołuje do działania jakiś system oraz analogicznie może to działanie zakończyć. Według definicji, Docker Container to ożywiony Docker Image.

Oprócz przenośni pudełka i żywej instancji o kontenerach dockerowych należy myśleć jak o oprogramowaniu. W końcu jest to przecież zestaw instrukcji, które manipulują innymi podprogramami. Kiedy kontener jest uruchomiony, ma w sobie uruchomione inne programy, które wykonują swoje normalne działania. Dzięki Dockerowi możemy uruchomić wiele kontenerów jednocześnie na jednej maszynie. Podobnie jak zwykłe programy, kontenery można uruchamiać, przeglądać, zatrzymywać i usuwać.
Zdecydowanie widać analogię kontenerów do maszyn wirtualnych, a bardziej samą analogię konteneryzacji do wirtualizacji. W rzeczywistości maszyny wirtualne są prekursorami kontenerów Dockerowych. Także izolują środowisko oraz jego zależności. Kontenery są jednak lepsze, ponieważ zużywają znacznie mniej zasobów oraz są w 100% przenośne. Zamknięty kontener można wziąć i uruchomić na każdym hoście z systemem Linux. Poszczególne kontenery nie potrzebują też własnej instancji systemu operacyjnego, tak jak jest w przypadku maszyn wirtualnych. Dlatego właśnie są bardziej wydajne i prostsze w utrzymaniu i obsłudze.

Możemy wyszczególnić następujące zalety konteneryzacji:

  • Szybkość i elastyczność – obraz kontenera buduje się o wiele łatwiej niż maszyny wirtualnej,
  • Rozdzielenie zadań DEV oraz OPS – obrazy kontenerów powstają w fazie build/release, oddzielając w ten sposób aplikację od infrastruktury,
  • Spójność środowiska – uruchomiony kontener zadziała tak samo na laptopie jak w chmurze,
  • Rozproszone mikro-serwisy – aplikacje podzielone są na mniejsze, niezależne komponenty, które mogą być dynamicznie uruchamiane i zarządzane,
  • Izolacja zasobów – wydajność aplikacji jest możliwa do przewidzenia,
  • Pełne wykorzystanie zasobów – wysoka wydajność i upakowanie.

Przed przejściem do omówienia bezpieczeństwa Dockera, należy jeszcze wytłumaczyć pojęcie obrazu dockerowego. Jest to coś w rodzaju planu, przepisu, z którego tworzony jest kontener. Zawiera informacje o wszystkich jego parametrach, wykorzystywanych zasobach, udostępnianych interfejsach itp.


Główne aspekty bezpieczeństwa Dockera


Zacznijmy od tego, że Docker sam w sobie wyposażony jest w wiele rozsądnych funkcjonalności zwiększających jego bezpieczeństwo. Jeśli używamy oficjalnych obrazów i nie wchodzimy w interakcję z innymi maszynami, to nie musimy się o wiele martwić.
Jeśli jednak stosujemy w naszym systemie nieoficjalne obrazy dockerowe (stworzone przez innych użytkowników), udostępniamy pliki czy uruchamiamy produkcyjne aplikacje, to warto znać podstawowe zasady zabezpieczenia takiego środowiska.

Głównym celem bezpieczeństwa jest, jak wiemy uniemożliwienie złośliwemu aktorowi uzyskania cennych informacji lub spowodowania kompromitacji środowiska. Dlatego przyjrzymy się trzem obszarom, które bezpośrednio wpływają na te problemy:

  • Zarządzanie dostępem
  • Bezpieczeństwo obrazów
  • Zarządzanie sekretami

Zarządzanie dostępem

1. Unikaj uruchamiania poleceń jako root.
Jak w każdym środowisku, w Dockerze również zalecana jest zasada najmniejszych wymaganych uprawnień. Kontener powinien mieć możliwość wykonania tylko zadań, które powinien wykonywać i nic po za tym. Trudność polega na tym, że gdy zaczniemy odbierać uprawnienia, proces może przestać wykonywać czynności, do których został stworzony.

2. Obierz wszystkie domyślne uprawnienia a następnie dodaj niezbędne.
Uprawnienia w Dockerze nazywają się Capabilities i są zestawem dozwolonych procesów analogicznych do tych z systemu Linux. Uprawnienia te można odbierać i nadawać w czasie rzeczywistym, gdy kontener jest uruchomiony, za pomocą poleceń –cap-drop oraz –cap¬-add. Dobrą praktyką jest tutaj odebranie wszystkich uprawnień na początku rozruchu kontenera poleceniem –cap-drop all, a następnie przydzielanie tylko wymaganych.
Warto unikać przydzielenia uprawnień SYS_ADMIN oraz SETUID, ponieważ jest to analogiczne do nadania procesowi mocy użytkownika root.

3. Rozważ używanie AppArmor do zarządzania uprawnieniami.
Jest to moduł Linuksowy, który pozwala na tworzenie własnych profili użytkowników i przydzielanie im określonego zestawu uprawnień, aby potem korzystać z nich podczas tworzenia kontenerów. Można to porównać do tworzenia polityki dla użytkowników. Z pewnością pozwala na prostsze zarządzanie dostępem w rozbudowanych środowiskach.

4. Ogranicz dostępne zasoby.
Chodzi o ograniczenie maksymalnej ilości pamięci RAM oraz CPU jakie może wykorzystać kontener. Bez ustawienia limitu, proces w kontenerze może zmusić system Linux do zużycia całej dostępnej pamięci, co spowoduje błąd krytyczny i crash. Nietrudno sobie wyobrazić, jak cyberprzestępcy mogą to wykorzystać to położenia całej aplikacji. Dlatego, przy uruchomieniu kontenera należy ograniczyć maksymalne dostępne zasoby, które może wysycić z maszyny.


Bezpieczeństwo obrazów

1. Używaj oficjalnych, popularnych i minimalistycznych obrazów.
Pobieranie gotowego obrazu z GitHuba jest jak zaproszenie kogoś do swojego domu. Nie powinniśmy tego robić bez sprawdzenia. Jeśli już chcemy wykorzystać przygotowane wcześniej obrazy, to warto pobrać je z repozytorium Docker Hub. Specjaliści Dockera sprawdzają je w miarę możliwości pod kątem podatności, wydajności i ewentualnych niezgodności. Tak więc, możemy śmiało korzystać z tych z dobrą opinią i pobraniami na poziomie 10 milionów.
Warto unikać korzystania z obrazów, które składają się z tysięcy linijek kodu, którego dodatkowo nie rozumiemy. Spójny i uporządkowany kod, który nie zawiera niepotrzebnych wpisów zmniejsza powierzchnię ewentualnych podatności i ataków.

2. Nie instaluj rzeczy, których nie potrzebujesz.
Nie będziemy wspominać tutaj o wydajności, bo to oczywiste, ale im więcej podprogramów, pluginów i bibliotek, tym większa szansa na niedopatrzenie i pozostawienie luki dla atakującego.

3. Wymagaj od obrazów podpisu cyfrowego.
Włączając wbudowaną funkcję Dockera, jaką jest Content Trust, jesteśmy w stanie sprawdzić podpis cyfrowy każdego oficjalnego obrazu z Docker Hub oraz obrazów od użytkowników z zaufanych źródeł. Opcję tę włączamy poleceniem export DOCKER_CONTENT_TRUST=1.


Zarządzanie sekretami

1. Używaj sekretów lub wolumenów.
Wolumeny są dobrym miejscem do przechowywanie wrażliwych danych. Są tymczasowym plikiem w pamięci systemu z ograniczonym dostępem dla zwykłych użytkowników oraz z zaawansowanym mechanizmem logowania. Jednak każdy root user wciąż ma do nich dostęp. Dlatego do poprawnego zabezpieczania danych powinno się używać tzw. sekretów. Są to obiekty typu klucz-wartości, do których dostęp jest zabezpieczony i które nie są przechowywane w ostatecznym obrazie.

2. Rozważ używanie sejfów.
Dla rozbudowanych środowisk warto rozważyć korzystanie z sejfów, które pomagają w zarządzaniu wieloma sekretami znajdującymi się na jednej maszynie. Jeśli zamierzasz korzystać z Kubernetes, to wsparcie dla sekretów masz zapewnione, jeśli nie to polecamy rozwiązanie HashiCorp lub AWS Secrets Manager.


Podsumowanie


Poniżej jeszcze kilka dodatkowych, bardziej oczywistych porad:

  • Aktualizuj na bieżąco wszystkie biblioteki i pluginy wykorzystywane w swoim kodzie. Instaluj najnowsze poprawki samego Dockera oraz hosta, na którym uruchamiasz kontenery.
  • Zainwestuj w Docker Enterprise. Zawiera on wiele przydatnych funkcjonalności, takich jak ustawianie polityki dla poszczególnych userów, monitoring logowań, dzienniki zdarzeń czy skaner podatności w kodzie obrazów.
  • Nigdy nie uruchamiaj kontenera jako –privileged dopóki nie jesteś pewien, że jest to potrzebne, na przykład do uruchomienia kontenera zagnieżdżonego w innym kontenerze.
  • Jeśli używasz serwera webowego i API do tworzenia kontenerów, upewnij się, że parametry są poprawne, tak aby nie utworzyć niechcianych bytów.
  • Zabezpiecz końcówki interfejsów za pomocą SSH lub HTTPS.
  • Przechowuj wrażliwe dane w wolumenach, nigdy w kontenerach.

Jak widzimy istnieje wiele możliwości dbania o bezpieczeństwo w Dockerze. Należy pamiętać, że wszystkie te aspekty nie są raz ustawione i całe security mamy z głowy. Trzeba stale mieć na nie oko, dostosowywać do sytuacji i modyfikować w razie potrzeby.
Sam Docker z pewnością jest krokiem w przód, jeśli chodzi technologię, ale również do zwiększonego bezpieczeństwa samych aplikacji. Jego izolacja i rozproszona architektura to z pewnością zalety i bezpieczeństwo.