Docker Security #3
Pięćdziesiąt twarzy bezpieczeństwa. Może jest to i śmieszne zdanie, ale trochę znajdziemy w nim prawdy. Każdy z nas bowiem inaczej słowo bezpieczeństwo interpretuje i rozumie. Jedni z nas będą wierzyć, że bezpieczeństwo dadzą nam ograniczenia, polityki i audyty. Dla innych liczy się tylko warstwa kontroli i nieprzerwanych testów środowiska. Natomiast ktoś może też wierzyć, że jedyną drogą jest kuloodporna architektura. Poniekąd wszyscy mają trochę racji i chyba właśnie w całym tym temacie chodzi o to, aby osiągnąć balans pomiędzy ograniczeniami kontrolą i architekturą. Przesadzenie w którymkolwiek ze wspomnianych aspektów rodzi patologię, którą ktoś posiadający mniejszą lub większą wiedzę jest w stanie ominąć w bardziej, lub mniej zaawansowany sposób.
Poprzednia część cyklu mówiła o tym, jak podejść do zabezpieczenia kontenera. Jako bytu – jednej sztuki, a nie całego środowiska. To ważne, aby zrozumieć aspekt, że bezpieczeństwo bierze swój początek od szczegółów. Te małe problemy i ich trywialne zabezpieczenia to małe cegiełki, które budują podstawy bezpiecznego środowiska. Co jednak począć, gdy rośniemy? Gdy nasza mała piaskownica zmienia się w pustynie, a kontenerów zaczynamy mieć tyle ile ziarenek piasku?
Na początek jednak przypomnijmy sobie słowniczek pojęć, aby mówić tym samym językiem. Host to dla nas serwer fizyczny lub maszyna wirtualna, generalnie niema to znaczenia liczy się system operacyjny w kontekście konteneryzacji i nazywany jest on Hardware Land, w nim uruchamiany jest program nazywany Docker Engine lub Docker Daemon silnik ten umożliwia uruchamianie Docker Image – obrazów zawierających aplikację, które gdy się już uruchomią, nazywane są Kontenerami. Zestaw Docker Engine działających w grupie nazywamy Klastrem.
Zacznijmy od tego, że różowe lata siedemdziesiąte bezpowrotnie minęły i wiadomo, że jak nie ześlemy podstawowych zasad w dekalog naszej infrastruktury to po krótkim czasie przerodzi się ona ze sformalizowanego, chodzącego jak kałasznikow środowiska w smerfną wioskę. Będzie się tam działo wszystko wszędzie, naraz i bez kontroli. Dodatkowo wdrożony system CI/CD tylko podkręci hipisowską atmosferę. Jak się bronić przed tą eskalacją hardcoru? Otóż sprawa jest relatywnie prosta – należy postawić pewne zasady od samego początku.
Brak dostępu do komendy docker na węźle Docker Engine. Takie odcięcie pępowiny pozwoli nam na zachowanie porządku. Jedynym sposobem wówczas na uruchamianie kontenerów będzie ścieżka zaprojektowana przez nas w procesie CI/CD. Pętla tego procesu będzie zapewne inicjowana przez aktualizację którejś z gałęzi naszego repozytorium, co finalnie stworzy zestaw kontenerów na infrastrukturze. Jest to jedyna droga, każda inna to półśrodek i wystawianie się na problemy. Warto bowiem zauważyć co implikuje taki ruch – nikt poza wyznaczonymi, twardo zdefiniowanymi osobami nie ma dostępu do klastra. Wówczas dostęp ten można porządnie zabezpieczyć – 2FA, klucze (być może nawet sprzętowe), dedykowane podsieci znajdujące się za VPN/Firewall. Sposobów jest wiele, a co ważne można je zaimplementować w harmonii nie powodując paraliżu działu developerskiego, który nieświadomy tego kręci pętlami CI/CD wymieniając wersje aplikacji. Finalnie nikt tego dostępu tak naprawdę nie potrzebuje, każdy kto go jednak ma, nie może nawet pomyśleć o jego braku. Należy tę obawę ubić w zarodku i od samego początku odciąć wszystkim wjazd do klastra pozostawiając ścieżkę CI/CD.
Koleją zasadą ograniczającą hipisówę w infrastrukturze jest postawienie wszystkich dostawców aplikacji w kontenerach przed faktem, że obraz kontenera tzw. Docker Image budowany jest wewnętrznie, a oni mają jedynie dostarczyć Dockerfile. Plik ten musi się znaleźć w repozytorium, gdzie będzie on podlegał wersjonowaniu i opiniowaniu. Dzięki Dockerfile jesteśmy w stanie zbudować kontener. Plik ten zawiera w sobie szereg reguł, które pozwolą nam na zbudowanie bazowego kontenera, który może działać wszędzie, jak również nie będzie działać nigdzie bez odpowiedniego pliku konfiguracyjnego, ale o tym za chwilę… na razie przykładowy Dockerfile:
[gdlr_core_code style=”light” ]
FROM alpine
LABEL MAINTAINER=”XYZ <xyz@cmentarnapolka>”
LABEL APP=”mysql v. XYZ”
ENV TIMEZONE Europe/CET
ENV MYSQL_ROOT_PASSWORD root
ENV MYSQL_DATABASE app
ENV MYSQL_USER app
ENV MYSQL_PASSWORD app
RUN apk add –no-cache mysql
RUN addgroup mysql mysql
WORKDIR /entry-scripts
COPY entry-scripts/init.sh init.sh
EXPOSE 3306
ENTRYPOINT [ “./init.sh” ]
[/gdlr_core_code]
Zacznijmy od pierwszej linii gdzie FROM alpine oznacza, że obraz będzie stworzony na podstawie dystrybucji Linux nazywanej Apline. Jest to niezwykle mała, bo waży około 5MB, dystrybucja linuxa, której głównym założeniem jest utrzymanie niskiej wagi, wysokiego bezpieczeństwa i prostoty. Sam fakt tych 5MB pokazuje już, że płaszczyzna ataku jest znacząco mniejsza od takiego np. Ubuntu, gdzie startujemy od grubo ponad 100MB… Jakieś min. 95 milionów miejsc, gdzie mogą być błędy. Budując obrazy warto bazować na Alpine z jednej strony ze względu na wspomniane bezpieczeństwo a z drugiej na szybkość uruchamiania – 5MB vs 100MB to tak lekko licząc 20 razy szybciej.
Kolejne dwie linie LABEL oznaczają informacje o tym, kto zajmuje się obrazem i zdają się one mało ważne, ale diabeł tkwi w linii drugiej. Która to jest tagiem. Etykieta ta jest super istotna, aby nie pozwolić nam popaść w chaos. Tagiem może być np. nr. wersji kontenera, typ aplikacji np. web lub db i szereg wersji. Za pomocą właśnie tych tagów będziemy mogli później filtrować naszą infrastrukturę, skanować i robić milion innych super przydatnych rzeczy. Zaniedbamy to i popadniemy w obłęd przedstawiony w filmie “Być jak John Malkovich”, gdzie w sławnej scenie wszystkie osoby będące Malkovichem powtarzają swoje nazwisko… Tak my w infrastrukturze będziemy mieli wszystko w tej samej nazwie.
O czasie i jego utrzymaniu nie muszę się chyba rozpisywać, ale za to o ENV, czyli zmiennych środowiskowych już by wypadało. Generalnie pomysł wydaje się dobry, bo nie składujemy haseł czystym tekstem w Dockerfile… jednak leżą w czystym tekście gdzie indziej. Niema co jednak demonizować, ponieważ naszym Key-Value Store (czyli w tym przypadku miejscem przechowywania haseł, nazw, adresów ip) może być repozytorium lub dedykowane oprogramowanie Vault, a nie plik hasła.txt. W celu wytłumaczenia, o co chodzi i co nam to daje zacznijmy od najprostszej wersji, czyli zdefiniowanych zmiennych środowiskowych, w naszym przypadku na warsztat idzie ENV MYSQL_ROOT_PASSWORD root gdzie pod zmienną środowiskową w kontenerze MYSQL_ROOT_PASSWORD podstawiana jest wartość zmiennej z pliku systemu CI/CD zdefiniowana przez nas, a nie znana dla dewelopera. Wyśmienicie nieprawdaż? Oznacza to, że hasła i loginy, nazwy hostów lub ich IP możemy wymieniać w locie podczas uruchamiania kontenerów… a to ni mniej ni więcej oznacza tyle, że nikt poza osobami do tego wyznaczonymi ich nie zna. Mogą one być wymieniane na różne wartości w zależności od środowiska np. Dev, Test lub QA, gdzie najprawdopodobniej są różne adresacje i poświadczania. Jeżeli zastosujemy oprogramowanie typu Vault sprawy mają się jeszcze lepiej, oprogramowanie to za pomocą klienta może nam wymieniać wartości (ip, loginy/hasła) nie tylko w zależności od środowiska, na jakim kontenery działają, ale również np. wymienić tę wartość w cyklu co 72h. Bezpieczeństwo level turbo – nikt nic nie wie, a poświadczenia same zmieniane są co 3 dni.
Komenda RUN wykonuje wymagane przez nas komendy a WORKDIR, COPY i ENTRYPOINT pozwalają nam na uruchomienie skryptu, który skonfiguruje obraz. EXPOSE wystawi port 3306, na który będą się łączyć klienci.
Należy wypracować swój własny standard budowy podstawowego obrazu. Dockerfile, a przynajmniej jego część powinna być narzucona dla dostawców aplikacji. Pozwoli to na uzyskanie pewnej kontroli i ustandaryzowanie wdrażanych kontenerów. Do rzeczonego pliku Dockerfile można dopisać wszystko to, o czym mówiliśmy w pierwszej części naszego cyklu i jeszcze więcej. Więcej informacji w tym poradnik z opisem każdego z parametrów można znaleźć na oficjalnej stronie Dockera (https://docs.docker.com/engine/reference/builder/) Oczywiście finalnie wszystko musi być dostarczane w kodzie do odpowiedniego repozytorium, gdzie przechodzi przegląd kodu (code review) i dopiero po akceptacji aplikowane jest na środowisko produkcyjne. Nie może istnieć nawet cień możliwości zmiany czegokolwiek poza repozytorium.
Artykuł został opublikowany na łamach IT Professional.
Poniżej znajdą Państwo linki do wcześniejszych artykułów:
What I recommend
Cloud Field Day 21: Too many clouds