Przewiń do głównej treści

apko i Wolfi: niezależna od rejestru alternatywa dla Docker Hardened Images

·1015 słów·5 min
Autor
Emil Grużalski
Infrastruktura & Automatyzacja Chmury

Po co w ogóle hardenowane obrazy bazowe
#

Argumentacja za hardenowanymi obrazami bazowymi jest prosta. Typowy obraz node:20 czy python:3.12 wozi ze sobą całą dystrybucję: powłokę, menedżer pakietów, dziesiątki bibliotek, których nigdy nie wywołujesz, a wraz z nimi długi ogon CVE, które Twój skaner będzie sumiennie raportował co tydzień. Większość tych CVE nie jest w Twojej aplikacji exploitowalna, ale i tak kosztują czas na triage, i tak wywracają restrykcyjne bramki, a każdy dodatkowy binarny plik to kolejna rzecz, przez którą atakujący może się przesunąć.

Docker Hardened Images (DHI) to odpowiedź Dockera na ten problem: minimalne, niemal pozbawione CVE obrazy bazowe i runtime’owe, podpisane, z dołączonym SBOM-em i provenance, często w wariantach distroless i FIPS. Są naprawdę dobre. Jeśli już siedzisz w ekosystemie Dockera, to łatwa wygrana.

Dlaczego u mnie się nie wpasowały
#

Moim blokerem była operacyjność, nie technologia. Organizacja standaryzuje się na GitHub Container Registry (GHCR) — to tam obrazy są budowane, skanowane, podpisywane i z tamtąd pobierane w całym CI i na produkcji. Przyjęcie DHI oznaczałoby pobieranie obrazów bazowych z własnego rejestru Dockera, za subskrypcją Docker Hardened Images, z odpowiednim logowaniem do rejestru i uprawnieniem wpiętym w każdy build i każdy node.

To druga zależność od rejestru, drugi zestaw poświadczeń i komercyjne uprawnienie do zarządzania — wszystko po to, by zdobyć obrazy bazowe, które i tak przebudowujemy i wypychamy do GHCR. Dla zespołu, który świadomie skonsolidował się na GHCR, wzięcie dhi.io jako twardej zależności na krytycznej ścieżce każdego builda nie było czymś, na co chciałem się pisać — przynajmniej jeszcze nie teraz.

Poszedłem więc poszukać czegoś, co daje te same właściwości bezpieczeństwa, ale pozwala posiadać artefakt i hostować go tam, gdzie chcę. To apko i Wolfi.

Czym jest Wolfi
#

Wolfi to społecznościowa linuksowa undystrybucja od ludzi z Chainguard. Określenie „undystrybucja” jest celowe: nie ma instalatora, nie ma kernela, nie ma systemu init. Istnieje wyłącznie po to, by być przestrzenią użytkownika wewnątrz kontenera.

Kilka rzeczy czyni ją dobrze dopasowaną do hardenowanych obrazów:

  • Oparta na glibc, nie na musl — więc w przeciwieństwie do Alpine nie wpadasz w subtelne problemy zgodności z prekompilowanymi binarkami, natywnymi modułami Node czy wheelami Pythona.
  • Granularne pakiety budowane specjalnie pod kontenery, dzięki czemu dokładasz dokładnie to, czego potrzebujesz, i nic ponadto.
  • Rolling release z szybkim łataniem CVE — pakiety są szybko przebudowywane i publikowane, więc opóźnienie „napraw CVE” jest krótkie.
  • Podpisane pakiety i SBOM-y domyślnie, co jest tu sednem sprawy.

Co kluczowe, repozytorium pakietów Wolfi (packages.wolfi.dev) jest publiczne i darmowe do pobierania. Nie potrzebujesz konta ani subskrypcji, żeby konsumować pakiety — i to właśnie sprawiło, że pasowało do mojego ograniczenia.

Czym jest apko
#

apko to narzędzie do budowania. Zamiast Dockerfile z imperatywnymi krokami RUN, opisujesz obraz deklaratywnie w YAML-u: z których repozytoriów pobierać pakiety, które pakiety zainstalować, jako jaki użytkownik uruchamiać i jaki jest entrypoint. apko składa następnie obraz OCI bezpośrednio z tych pakietów apk — bez pośredniego docker build, bez warstw pełnych buildowego śmiecia.

Co z tego masz:

  • Powtarzalne, często identyczne co do bitu buildy — ta sama konfiguracja produkuje ten sam obraz, a to realna właściwość supply chain, nie slogan.
  • SBOM generowany automatycznie dla każdego budowanego obrazu.
  • Obrazy multi-arch (x86_64, aarch64) z jednej konfiguracji.
  • Maleńkie obrazy — ponieważ dokładasz tylko wymienione pakiety, w wyniku nie ma powłoki ani menedżera pakietów, dopóki o nie jawnie nie poprosisz. To distroless z założenia.
  • Może wypychać bezpośrednio do dowolnego rejestru OCI — w tym do GHCR.

apko bywa łączone z melange — bliźniaczym narzędziem, które buduje pakiety apk ze źródeł (również powtarzalnie, z SBOM-ami). Dla większości obrazów aplikacyjnych melange nie jest potrzebne — konsumujesz istniejące pakiety Wolfi za pomocą apko i dokładasz własną binarkę aplikacji.

Jak to wygląda
#

Minimalna konfiguracja apko dla statycznej binarki w Go jest mniej więcej tak mała:

contents:
  repositories:
    - https://packages.wolfi.dev/os
  keyring:
    - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
  packages:
    - ca-certificates-bundle

accounts:
  groups:
    - groupname: nonroot
      gid: 65532
  users:
    - username: nonroot
      uid: 65532
      gid: 65532
  run-as: 65532

entrypoint:
  command: /usr/bin/myapp

archs:
  - x86_64
  - aarch64

Nie ma linijki z obrazem bazowym, bo nie ma obrazu bazowego w sensie Dockerfile — apko składa obraz z pakietów. W wyniku nie ma powłoki ani menedżera pakietów, tylko ca-certificates, Twoja binarka i użytkownik nieroot.

Zbudowanie i wypchnięcie prosto do GHCR to jedno polecenie:

apko publish apko.yaml ghcr.io/myorg/myapp:1.0.0

To buduje obraz multi-arch, generuje SBOM i wypycha wynik do mojego rejestru. Nie ma drugiego rejestru do uwierzytelnienia, nie ma uprawnienia subskrypcyjnego na krytycznej ścieżce — jedyną zewnętrzną zależnością jest publiczne repo pakietów Wolfi, a artefakt ląduje w GHCR dokładnie jak każdy inny obraz w organizacji.

W CI wpina się to czysto: jest oficjalna GitHub Action dla apko, a wypchnięty obraz podpisujesz cosign-em i tożsamością OIDC workflow — tak samo, jak podpisałbyś cokolwiek innego w GHCR.

Co to w praktyce daje
#

Zestawione ze standardowym bazowym node/python/debian, obraz apko + Wolfi daje Ci:

  • Drastycznie mniejszą powierzchnię ataku — bez powłoki, bez menedżera pakietów, bez bibliotek, o które nie prosiłeś.
  • Znacznie mniej CVE do triage’u i szybsze poprawki upstream, gdy jakieś się pojawi.
  • SBOM i powtarzalny build od ręki, co upraszcza weryfikację provenance i polityk.
  • Deklaratywną, recenzowalną definicję obrazu — diff YAML-a mówi Ci dokładnie, co zmieniło się w obrazie, w przeciwieństwie do stosu linijek RUN.

A względem samego DHI wymiana jest jasna: DHI wręcza Ci wyselekcjonowany, wspierany, gotowy obraz, ale wiąże Cię z rejestrem i subskrypcją Dockera. apko + Wolfi prosi, byś (niewielkie) składanie wykonał sam, a w zamian obraz jest Twój — zbudowany w Twoim CI, hostowany w Twoim rejestrze, bez zewnętrznego uprawnienia na ścieżce pobierania. Dla organizacji wystandaryzowanej na GHCR ta niezależność od rejestru była czynnikiem przesądzającym.

Kiedy DHI nadal jest lepszym wyborem
#

To nie jest argument, że DHI jest złe. Jeśli chcesz obrazu wspieranego przez dostawcę z SLA, łatanego i certyfikowanego za Ciebie, a subskrypcja i rejestr Dockera są już częścią Twojego świata, DHI jest ścieżką mniejszego wysiłku i całkowicie dobrą. Chodzi tylko o to, że hardenowane obrazy nie są czymś, co da się wyłącznie kupić — z apko i Wolfi możesz zbudować je sam i hostować tam, gdzie Twoja organizacja już mieszka. U mnie było to GHCR, i to zrobiło całą różnicę.

Oba narzędzia są open source: apko i Wolfi.