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
- aarch64Nie 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.0To 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ę.