Docker Security #4
Kolejnym krokiem do uzyskania bezpieczeństwa środowiska konteneryzacyjnego jest zastosowanie metody Build, Ship, Run. Podczas stosowania się do tego założenia musimy popatrzeć na nasz proces CI/CD z boku i pomyśleć o tym gdzie postawić linie demarkacyjne – jakie mamy etapy tego procesu i co one tak naprawdę oznaczają. Zacznijmy od czegoś małego, niech nasze hipotetyczne środowisko ma 3 stage aplikacji – DEV, TEST i LIVE (Produkcja). Żeby być zgodnym z sztuką należy przyjąć, że każdy z tych etapów musi posiadać swój własny Rejster kontenerów. Docker Registry bo o nim mowa może występować w dwóch odmianach – Publiczny i Prywatny. Można go tendencyjnie przyrównać do bazy danych, ale to nuda. Pomyślmy o Docker Registry jako bibliotece podzielonej na szafki które w kontekście Dockera będą repozytoriami, na których układane są książki czyli nasze obrazy kontenerów Docker. Biblioteka może być publiczna i zawierać strefę publiczną z książkami dostępnymi dla każdego. Możesz wypożyczyć książkę, możesz też jakąś napisać i wysłać ją do biblioteki – będzie dostępna dla wszystkich, ale Ty będziesz autorem i tylko Ty będziesz ją mógł zmienić, a wszyscy będą mieli do niej dostęp. Biblioteka ta może mieć również strefy prywatne, gdzie tylko Ty lub wyznaczeni przez ciebie ludzie będą mieli dostęp, będą mogli czytać i pisać książki. Analogicznie jest z publicznym Docker Registry, nazywa się on Docker Hub, jest też możliwość powołania prywatnego rejestru w lokalnym Datacenter. Jednak którykolwiek byś nie wybrał należ rozdzielić go w zależności od stage, lub zmiany którą jest w nim wykonywana. Dla przykładu gdy budowany jest obraz kontenera przy pomocy Dockerfile należy go umieścić w Rejestrze przeznaczonym do fazy Build, następnie gdy dorzucamy do niego w kolejnym kroku (np. do testów) binaria aplikacji oraz plik konfiguracyjny (również z repozytorium) wraz z kluczami poświadczeń wrzucamy go do rejestru Test z którego wypychany jest na klaster Testowy, analogicznie w fazie QA. Jako Tag możemy doklejać kolejne informacje np. Apline:NazwaApp:WersjaKonfigu. Pozwoli nam to na opanowanie chaosu i umożliwi łatwy powrót do poprzedniej wersji gdy coś pójdzie nie tak. Ponieważ, zawsze mamy bazowy obraz z fazy Build. To ważne aby budować tylko raz, ponieważ gdy tworzymy obraz wykonywany jest szereg operacji w tym m.in. aktualizacja komponentów systemu… a to w chwili budowania kontenera w danej chili wygląda tak a później, nawet może po kilku chwilach może wyglądać zupełnie inaczej. Oznacza to ni mniej ni więcej tyle, że wersja kontenera do testów może pójść z innymi wersjami oprogramowania w systemie niż wersja która wyjdzie na produkcje. Testy wówczas nie mają sensu i nasza praca jest bezwartościowa. Dlatego należy budować obraz raz, na samym początku procesu i na końcu po wszystkich testach, których może być bez liku i mogą trwać od kilku chwil bo kilkanaście godzin np. podczas testów wykonywanych przez osoby, wdrażać aplikację na środowisko LIVE z tą samą wersją obrazu bazowego, nałożonym konfigiem z rejestru produkcyjnego. Czyli reasumując mamy min. trzy rejestry – dla stadium DEV, dla stadium TEST (QA/UAT) i dla stadium LIVE (Produkcja).
Dodatkowe elementy które zwiększą bezpieczeństwo naszej infrastruktury rozbudowują proces CI/CD i polegają na dołożeniu kroków w postaci skanowania kodu aplikacji oraz jej binariów. Patrząc na wszystko razem możemy ułożyć sobie taki podstawowy schemat architektury w oparciu o klaster Kubernetesa:
Powyższy rysunek przedstawia pokładową architekturę rozwiązania oraz korelacji między elementami. Zaczynając od góry rysunku założono, że nastąpi podział kompetencji zespołów na blok deweloperski oraz infrastrukturalny. Generale założenie, zgodnie z powyższym opisem jest takie, że obrazami kontenerów i środowiskiem middleware oraz backend platformy zajmuje się dział Infra – jest on odpowiedzialny za zapewnienie ciągłości działania i zarządzanie obrazami infrastruktury. Dział Dev odpowiedzialny jest za tworzenie aplikacji i produkcję kodu. Przyjmując taki podział można wysokopoziomowo podzielić to na działania poszczególnych zespołów, rozpoczęto od Infra i sytuacji gdy wymagana jest zmiana w obrazie kontenera:
- Zmiana wykonywana jest w kodzie i wrzucana przez pracownika działu Infra na rezpoytrium Git
- Git wysyła webhook do kontrolera CI/CD z informacją, że zmieniło się repozytorium (może być dedykowane dla Infry może być w repo kodu aplikacji)
- Kontroler procesu CI/CD buduje obraz kontenera na podstawie załączonych w repozytorium informacji o przekazuje zbudowany kontener do skanowania podatności binariów / bibliotek.
- Sprawdzony obraz trafia do zależnego od etapu (Bulid, Ship, Run) Rejestru kontenerów – może to być DTR/Docker Hub/Moduł Git lub inne.
Dział Dev ma podobny schemat, jednak różni się on krokami:
- Zmiana w kodzie aplikacji jest wrzucana do odpowiedniego branch i publikowana na Git.
- Git wysyła webhook do kontrolera CI/CD z informacją, że zmieniło się repozytorium.
- Kontroler CI/CD wrzuca kod do skanera kodu źródłowego i jeżeli przechodzi on pozytywnie skanowanie, budowany jest artefakt.
- Artefakt aplikacji wrzucany jest do repozytorium artefaktów.
Krok piąty jest istotny dla obu działów. W tym miejscu kod aplikacji i ustawienia obrazu łączą się w jedną ścieżkę i wdrażane są na następujących po sobie środowiskach, podczas gdy przy pomocy kontrolera CI/CD i Vault zmieniane są tokeny i hasła do serwisów zależnie od środowiska na, którym mają działać. Publikacja środowisk w zdefiniowanych krokach (na rzucie dla przykładu są trzy – DEV/UAT/PROD, ale w docelowym środowisku może być ich więcej lub mniej) odbywa się na klastrze zgodnie z zdefiniowaną w repozytorium formą. Sieć dla kontenerów jest kontrolowana przez kontroler sieci wirtualnej, który powinien zapewniać m.in. szyfrowanie połączeń miedzy nimi, dystrybucyjny firewall, load-balancing i ingress routing oraz mikrosegmentację sieci. Dzięki temu kontrola aspektów sieciowych może zostać w rękach zespołu sieciowego lub zostać przekazana do ogólnego działu infra – w jednym i drugim przypadku jest ona opisywane kodem utrzymywanym w repozytorium Git.
Bezpieczeństwo platformy Docker lub też platformy konteneryzacyjnej zależy zarówno od dopieszczenia samego obrazu poprzez kontrolę Dockerfile, jak również i zabezpieczenie procesu CI/CD oraz samej infrastruktury. Jest to balansowanie na trzech wspomnianych we wstępnie płaszczyznach. Balansowanie które musi się wydarzać rodząc pewne zasady, kompromisy i twarde wymagania. Ponieważ bez nich środowiska konteneryzacyjne, zresztą tak jak wszystkie inne popadną w ruinę.
Poniżej znajdą Państwo linki do wcześniejszych artykułów:
What I recommend
Cloud Field Day 21: Too many clouds