Containrar

Containersäkerhet

Härdningsarbetssätt som minskar attackytan i containerbyggen, images och runtime-inställningar.

Containersäkerhet hanterar hot som finns i hela livscykeln , från paketen som ingår i imagen till de privilegier som beviljas vid körning till tilliten till externa registrys. Angripare som komprometterar en container strävar efter att bryta sig ut ur isoleringen, komma åt hemligheter eller röra sig lateralt till andra arbetslaster. Bra containersäkerhet begränsar vad de kan göra även om de tar sig in. Modellen börjar vid byggtid och sträcker sig hela vägen genom driftsättning och körtidsdrift.

Lärandemål

Det här ska du kunna efter genomläsning.
  • Identifiera de viktigaste säkerhetskontrollerna för containrar vid build och i runtime.
  • Förklara varför imagestorlek, build-struktur och privilegienivå spelar roll.
  • Känna igen de vanligaste sätten som containrar blir felkonfigurerade på.

I korthet

En snabb mental modell innan du går på djupet.
Build-hygien
  • Små basimages
  • Multi-stage builds
  • SBOM
Begränsningar i runtime
  • Icke-root
  • Rootless
  • Snäva mounts
Bevis i leveranskedjan
  • Image-scanning
  • Image-signering
  • Betrodda registries

Kärnidén

En säker container är smal, explicit och enkel att inspektera. Smal innebär att imagen bara innehåller vad applikationen behöver för att köra. Explicit innebär att körtidsprivilegier, capabilities och monteringar är deklarerade och motiverade. Enkel att inspektera innebär inga dolda lager, inga odokumenterade beroenden och inget oväntat beteende när containern startar.

Principen om minsta privilegium gäller på varje lager. Basimagen ska ha minimala paket, processen ska köra som en lågprivilegierad användare, containern ska bara ha de Linux-capabilities den verkligen behöver, och monteringar och nätverksvägar ska begränsas till vad arbetslasten faktiskt använder.

Djupförsvar för containrar innebär att inget enskilt kontrollmedel förlitas på exklusivt. Imageskanning, signering, körtidssäkerhetspolicyer och admissionskontroller är oberoende lager. Om ett kringgås eller misslyckas begränsar de andra fortfarande skadan.

Det viktigaste mentala skiftet är att behandla säkerhet som en byggegenskap, inte en operativ eftertanke. En container som når produktion osäker kan inte göras säker enbart med övervakning. De kontroller som spelar mest roll, imagehygien, byggdisciplin och körtidspolicy, måste finnas på plats innan containern driftsätts.

Kontroller vid byggtid

  • Börja från en minimal basimage (slim, alpine, distroless) och lägg bara till det som verkligen behövs. Varje extra paket är en potentiell sårbarhet.
  • Använd flerstegsbyggen för att kompilera eller förbereda artefakter i en byggmiljö och kopiera bara slutresultatet till körtidsimagen, vilket håller byggverktyg utanför produktion.
  • Generera och behåll en SBOM (programvarubillsförteckning) för varje image så att du vet exakt vilka paket och versioner som finns inuti.
  • Skriv aldrig hemligheter, tokens eller autentiseringsuppgifter i imagelager. Inte i ENV, ARG, RUN eller COPY-instruktioner.

Kontroller vid körning

  • Kör som en icke-root-användare inne i containern om det inte finns en specifik, dokumenterad anledning att inte göra det.
  • Använd rootless containerkörningar (som Podman eller rootless Docker) där plattformen stöder det, för att minska privilegierna för själva körningen.
  • Sätt ett skrivskyddat rotfilsystem och montera skrivbara sökvägar bara där applikationen explicit behöver skriva.
  • Ta bort alla Linux-capabilities som inte krävs av applikationen och använd aldrig privileged: true i produktion om inte arbetslasten verkligen behöver åtkomst på kärnivå.

Signaler att bevaka

Mönster som är värda att undersöka vidare.
  • Hemligheter eller credentials kopieras in i image-lager.
  • En container kan skriva till mer av filsystemet än den behöver.
  • Images hämtas från obetrodda eller ogranskade källor.
  • Build-steg lämnar kvar kompilatorer, pakethanterare eller debugverktyg i produktionsimages.

FÖRDJUPNING

Minimala images

Varje paket i en containerimage är en potentiell sårbarhet. En fullständig Ubuntu- eller Debian-image levereras med ett skal, pakethanterare, nätverksverktyg och hundratals andra binärer som en typisk applikation aldrig använder. Vilken som helst av dessa kan utnyttjas av en angripare som uppnår kodexekvering för att eskalera privilegier, etablera uthållighet eller röra sig lateralt.

Spektrumet sträcker sig från fullstora images (Ubuntu, Debian) via slimmade distro-images (debian-slim, alpine) till distroless-images (som bara innehåller språkkörtiden och standardbibliotek) och slutligen till scratch-baserade images (som bara innehåller applikationsbinären och dess direkta beroenden). Varje steg minskar både imagestorleken och attackytan.

Ett praktiskt verktyg för att förstå vad som finns i en image är 'dive', som låter dig inspektera varje lager och identifiera filer som är oväntat stora, dolda av senare lager eller helt enkelt onödiga. Många team förvånas av att ett lager djupt i deras image innehåller hela källkodscheckouten eller en cachadfil med autentiseringsuppgifter.

Avvägningen mot minimala images är felsökningsmöjlighet. Det moderna svaret på detta är efemerala felsökningscontainrar (kubectl debug i Kubernetes) som fäster en separat felsökningsaktiverad container till en körande minimal container utan att ändra produktionsimagen.

Flerstegsbyggen

Ett flerstegsbygge använder flera FROM-instruktioner i en enda Dockerfile. Varje FROM startar ett nytt byggsteg med ett eget filsystem. Du kompilerar eller förbereder applikationen i ett tidigt steg (med en fullständig kompilatorverktygskedja och byggberoenden), sedan kopierar du bara slutartefakten till ett rent körtidssteg. Körtidssteget har ingen kompilator, ingen pakethanterare och inget byggcache.

Detta spelar roll för säkerheten eftersom en angripare som uppnår kodexekvering inne i containern inte kan använda verktyg som inte finns där. I en Go-applikation har till exempel byggsteget Go-kompilatorn och dussintals byggberoenden. Körtidssteget har bara den kompilerade binären. Om applikationen komprometteras kan angriparen inte använda apt-get för att installera verktyg.

Flerstegsbyggen förhindrar också oavsiktlig läckage av hemligheter. Byggsteg behöver ofta autentiseringsuppgifter för att komma åt privata paketregistryn. I ett flerstegsbygge är det bara explicit COPY-ade filer som passerar stegsgränsen. Autentiseringsuppgifter som skickas till byggsteget visas inte automatiskt i körtidsimagen.

Mönstret är standardpraxis för kompilerade språk (Go, Rust, Java, .NET) men är lika användbart för tolkade språk. Ett Node.js flerstegsbygge kan köra npm install i ett steg med hela npm-verktygskedjan, sedan kopiera bara node_modules och applikationskällan till en node:alpine-körtidsimage.

Icke-root och rootless

Att köra en process som root inne i en container innebär att processen har UID 0 med en bred uppsättning Linux-capabilities inom sin namnrymd. Även om containerisolering förhindrar att detta är direkt likvärdigt med värdroot, minskar det avsevärt angriparens arbete om isoleringen bryts. Många containerflyktsattacker är enklare eller bara möjliga när containerprocessen körs som root.

Att ange en icke-root-användare är enkelt i en Dockerfile. Använd USER-instruktionen för att växla till en namngiven användare eller UID. Applikationen måste kunna köras som den användaren, vilket innebär att filbehörigheter i imagen måste vara korrekt inställda och att processen inte kan binda till portar under 1024 utan ytterligare capability-beviljanden.

Rootless-läge går längre än att köra som icke-root inne i containern. I rootless Docker eller Podman körs själva containerkörningen som en oprivilegierad användare på värden. Det innebär att även om körningen komprometteras, börjar angriparen från en icke-privilegierad position på värden.

En vanlig missuppfattning är att icke-root inne i containern gör värden säker från kompromitering. Det minskar risken avsevärt men är inte en fullständig gräns. Icke-root är ett lager i försvarsmodellen, inte hela modellen.

Hemligheter i containrar

Den farligaste platsen att lägga en hemlighet är i ett imagelager, eftersom imagelager är oföränderliga, brett cachade och ofta hämtas av många system. Även om en hemlighet ställs in i ett tidigt lager och ett senare lager försöker ta bort den, finns hemligheten kvar i imagehistoriken och är synlig via 'docker history'.

Bygghemligheter (autentiseringsuppgifter som behövs under bygget men inte vid körning) bör skickas med Docker BuildKits --secret-flagga, som monterar hemligheten som en tillfällig fil som bara är tillgänglig under den specifika RUN-instruktionen och aldrig läggs till i något imagelager. Körtidshemligheter bör injiceras som miljövariabler av orkestratören, monteras från en Kubernetes Secret, eller hämtas dynamiskt från en hemlighetshanterare.

ENV-instruktionen är beständig. Dess värden visas i imagemetadata och är synliga för alla som kan inspektera eller hämta imagen. ARG-instruktionen är något bättre (värden är inte i slutlig imagemetadata som standard) men visas fortfarande i bygghistoriken. Ingen av dem ska användas för riktiga autentiseringsuppgifter.

En vanlig källa till läckage är CI/CD-pipelines där hemligheter eko:as i byggloggar, fångas i artefaktuppladdningar eller exponeras i felmeddelanden. Efter exponering, i en logg, i ett imagelager, i ett commit, måste hemligheten roteras omedelbart.

Imageskanning

Imageskannrar inspekterar innehållet i en containerimage mot databaser med kända sårbarheter. De flesta skannrar tittar på OS-nivåpaket och språknivåpaket och matchar deras versioner mot CVE-databaser som National Vulnerability Database (NVD). Vissa skannrar kontrollerar också felkonfigurationsmönster och exponerade hemligheter.

Ett skanningsresultat är en signal, inte ett utlåtande. Ett fynd som säger 'kritisk CVE' innebär inte automatiskt att applikationen är exploaterbar. CVE:n kan finnas i en bibliotekskomponent som aldrig anropas av applikationen, eller den påverkade kodsökvägen kanske inte är nåbar från användarindata.

Basimageålder är en av de viktigaste faktorerna i skanningsresultat. En image byggd på Ubuntu 20.04 för sex månader sedan har ackumulerat alla paketproblem som rapporterats under den tiden, även om applikationskoden inte har ändrats. Att regelbundet bygga om images från uppdaterade basimages är ett av de mest effektiva sätten att hålla skanningsresultat hanterbara.

Skanning bör ske vid flera punkter. Under bygget (för att fånga problem innan de når ett registry), i registryt (för att fånga nyupptäckta sårbarheter i images som var rena när de byggdes) och vid driftsättning (för att tillämpa en policy innan en container startar).

Imagesignering

Imagesignering använder kryptografiska signaturer för att bevisa att en specifik image producerades av en specifik part och inte har modifierats sedan den signerades. Utan signering kan ett driftsättningssystem som hämtar 'myapp:2.4.1' inte verifiera om det är imagen som byggpipelinen producerade, eller en som ersattes efteråt.

Cosign (från Sigstore-projektet) är den moderna standarden för containerimagesignering. Det lagrar signaturer i samma OCI-registry som imagen och integreras med verktyg som Rekor för en transparent, append-only offentlig logg. Signeringsnyckeln kan vara ett traditionellt nyckelpar eller en kortlivad identitet från en OIDC-leverantör som GitHub Actions.

Värdet av signering realiseras vid tillämpningstillfället. En admissionskontroller i Kubernetes kan verifiera att varje image som driftsätts har en giltig signatur från den betrodda pipelinen innan containern tillåts starta. Detta förhindrar driftsättning av osignerade images eller images vars signaturer inte matchar den förväntade nyckeln.

En vanlig missuppfattning är att förväxla signering med skanning. Signering svarar på 'vem byggde denna image och är den omodifierad?'. Skanning svarar på 'innehåller denna image kända sårbarheter?'. Båda behövs och ingen ersätter den andre.

SBOM

En programvarubillsförteckning (SBOM) är ett maskinläsbart lager av varje komponent i en programvarartefakt. För en containerimage innebär detta alla OS-paket, språkbibliotek och deras exakta versioner. SBOM-format inkluderar SPDX och CycloneDX. Verktyg som Syft kan generera en SBOM från vilken containerimage som helst.

SBOM:er möjliggör snabbare incidentrespons när en ny sårbarhet avslöjas. När Log4Shell tillkännagas i december 2021 kunde organisationer med SBOM:er för alla sina images omedelbart fråga vilka containrar som innehöll log4j. På minuter snarare än dagar. Organisationer utan SBOM:er var tvungna att manuellt inspektera images under tidspress.

Utöver incidentrespons stöder SBOM:er licensefterlevnadsgranskning, revisionsförfrågningar från kunder eller regulatorer och krav på transparens i leveranskedjan, inklusive krav i Executive Order 14028 om förbättring av USA:s cybersäkerhet.

En SBOM är bara så användbar som den process som håller den aktuell och tillgänglig. En SBOM genererad vid byggtid och lagrad med imagen i registryt kan hämtas av vilket auktoriserat system som helst. SBOM:en bör vara en förstaklassig artefakt i byggpipelinen, kopplad till varje publicerad image.

Vanliga felkonfigurationer

Privilegierade containrar (privileged: true i Kubernetes) ger containern nästan alla Linux-capabilities på värden och tar bort de flesta kärnsskydd. De är sällan nödvändiga, vanligtvis räcker en specifik capability eller enhetsåtkomst, men läggs ofta till som en genväg för att snabbt lösa ett behörighetsproblem. En privilegierad container är i praktiken på värden, om den komprometteras har angriparen åtkomst på värdnivå.

Breda hostPath-monteringar monterar en katalog från värdnoden direkt in i containern. Vanliga mönster som missbrukas inkluderar montering av /var/run/docker.sock (som ger containern full kontroll över containerkörningen på den noden), /proc, /sys, eller till och med / (hela värdfilsystemet). Vilket som helst av dessa ger en komprometterad container en väg att fly isoleringen.

hostNetwork: true gör att containern delar värdens nätverksnamnrymd. Containern kan se all nätverkstrafik på värden, lyssna på värdportar och kringgå nätverkspolicyer utformade för att isolera arbetslaster från varandra.

Rörliga imagetaggar (att använda 'latest' utan digest-pinning) innebär att en driftsättning tyst kan hämta en annan image än avsett. Kombinerat med ett osäkert registry skapar detta en väg för imageersättningsattacker. Att pinna images via digest eliminerar denna risk. Att köra containrar från obetrott eller overifierade registrys lämnar hela imagesupplykedjan som en öppen attackvektor.