Budowanie obrazów wiąże się z różnymi wyzwaniami, jednym z nich jest bezpieczeństwo trwale zapisanych w nich danych. Jak uniknąć upublicznienia czegoś, co powinno zostać prywatne?

Nie mówię tu o kodzie jako takim, bo jeśli budujemy obrazy przeznaczone do użycia wewnętrznego, to musimy zadbać o ich bezpieczne przechowywanie i użytkowanie, co jest zupełnie inną historią. Wyobraźmy sobie jednak, że budujemy obraz zawierający publicznie dostępne narzędzie, pomagające w codziennej pracy, a jego kod jest dostępny publicznie (np. composer). Gdzie w takim razie w całym procesie wydawniczym jest miejsce na sekrety, bo o nich właśnie mowa?

Czym są sekrety?

Sekrety to dane, które jak sama nazwa wskazuje, chcielibyśmy zachować dla siebie. Są to wszelakie hasła, tokeny, certyfikaty, na których opiera się np. komunikacja z zewnętrznymi usługami. Dotychczas Docker umożliwiał użycie sekretów w stackach Swarm, również opartych o docker compose. Dzięki temu da się w prosty sposób dostarczyć do aplikacji różnego rodzaju poufne informacje, ale dopiero w tzw. runtime — nie są one dostępne ani w procesie budowania, ani nie są trwale przechowywane w obrazie (a więc nie można ich wyciągnąć, przeglądając historię warstw), można z nich korzystać dopiero po uruchomieniu kontenera.

Poufne dane w procesie budowania

Wspomniany #Composer, czy wiele innych narzędzi, w procesie budowania obrazu musi pobrać swoje zależności. W przypadku projektów Open Source zazwyczaj te zależności również są dostępne w publicznych repozytoriach GitHub, Gitlab czy innych. Jednak dostawcy tych usług zabezpieczają się przed nieuprawnionym użyciem, zatem przy masowym pobieraniu może pojawić się konieczność uwierzytelniania, a żeby to obejść potrzebujemy tokenów już w trakcie budowania obrazu.

Budując obrazy korzystające z paczek pobieranych z wszelakich rejestrów (Composer, NPM, PYPI), możemy też potrzebować integracji z prywatnym rejestrem (Private Packagist, Artifactory, Verdaccio). O ile obecność samych paczek nie musi być dla nas problemem, o tyle danymi do uwierzytelniania już niekoniecznie chcielibyśmy się dzielić z innymi 😉

Zresztą nie tylko o paczki chodzi — możemy przecież pobierać jakieś artefakty: definicje tłumaczeń, statyczne pliki… Źródłem mogą być inne systemy zintegrowane przez API, w którym również musimy się uwierzytelnić.

Sekrety w momencie budowania

Kilka dni temu w repozytorium docker/compose do głównej gałęzi v2 dołączone zostały zmiany dostarczające długo wyczekiwaną (issue z 2018 roku!) funkcjonalność — sekrety dostępne na etapie budowania obrazu 🎉. Gdy zostanie wydana w oficjalnej wersji, będzie można z niej korzystać następująco:

services:
  ssh:
    image: build-test-secret
    build:
      context: .
      secrets:
        - mysecret

secrets:
  mysecret:
    file: ./secret.txt
FROM alpine

RUN echo "foo" > /tmp/expected
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret > /tmp/actual
RUN diff /tmp/expected /tmp/actual

Mając tak zdefiniowany Dockerfile oraz stack #Docker Compose możemy w bezpieczny sposób przekazać sekret do kontekstu Dockera i użyć go w cyklu budowania. Oczywiście zapisywanie go w jakimś pliku nie jest najlepszym pomysłem, to jedynie przykład obrazujący przekazanie i odczyt sekretu 😉. Jeśli poprawnie go użyjemy, to nie będzie on dostępny w wynikowym obrazie, ani w jego historii warstw (które można podejrzeć wieloma narzędziami).

To umożliwi w końcu spójny proces budowania obrazu z wykorzystaniem sekretów zarówno dla #CI, jak i lokalnego użycia na potrzeby rozwoju aplikacji.

Pozostaje czekać na nową wersję Docker Compose 😁