Blogg

Ska varje klient ha en egen VM?

Välj mellan containrar per klient, virtuella maskiner och hybridlösningar med ett riskbaserat beslutsramverk och de förstärkningssteg som gör varje alternativ försvarbart.

Summary

Multi-tenant-hostning tvingar dig att välja hur långt klienter kan nå in i varandra. Containers använder Linux-namespaces och cgroups för att isolera processer och resurser, men de delar värdkärnan. Virtuella maskiner lägger till en gräns på hårdvarunivå, till priset av hastighet och operativ belastning. En hybridansats – containrar i virtuella maskiner – kan ge dig båda, men den fördubblar ytan du måste patch:a. Den här artikeln leder dig genom ett riskbaserat beslut, en jämförelse sida vid sida och de Docker-förstärkningssteg som spelar roll även i en virtuell maskin. I slutet vet du vilken isoleringsmodell som passar dina klienter och vad du ska konfigurera innan lansering.

Din multi-tenant-app är nästan klar. Du har en Docker Compose-fil som startar en stack per kund, och den är snabb. Sedan frågar en vän som driver ett hostingföretag: 'Ger du varje klient en egen VM?' Du fryser till. Du hade inte planerat för den frågan. Den här artikeln ger dig ett sätt att svara på den idag, utan ett säkerhetsteam. Du gör det här ensam, så beslutet måste vara enkelt nog att försvara klockan 2 på natten.

Sluta försöka hitta den 'bästa' modellen. Börja med att skriva ner vad som händer om en klients kod tar över din värd. Definiera sprängradien innan du väljer något verktyg. Den övningen säger dig mer än något benchmark någonsin kommer att göra.

Kärnan är rumskamraten du inte kan vräka

Containers är effektiva eftersom de delar värdkärnan. Det delandet är hela tricket, och hela risken. Linux-namespaces ger varje container sin egen vy över processer, nätverk och filsystem. Kontrollgrupper (cgroups) låter dig begränsa CPU, minne och disk-I/O så att en klient inte kan svälta ut de andra. Men ingen av dem skapar en hårdvaruvägg.

Tänk på en container som en process med ett riktigt bra falskt ID. Den tror att den är på sin egen maskin. Kärnan är dock en kopia av Linux som körs på din värd. Om en klient utnyttjar en kärnsårbarhet blir namespaces bara metadata och inget mer. En angripare som kan anropa kärnfunktioner kan nå andra namespaces på samma kärna. Det är den containerflykt du hela tiden hör talas om.

Säg att du hostar ett litet B2B-verktyg med en container per kund. En kund installerar ett skumt plugin med en bugg för fjärrkörning av kod. Med standardinställningar för Docker körs den processen som root i containern. Root i en container är fortfarande UID 0, och kärnan skiljer inte det UID:t från värdens root om du inte uttryckligen mappar användare. Angriparen kan försöka ta sig ut, och den delade kärnan är deras mål.

Felet behöver inte vara dramatiskt. En enda klient som läcker minne kan trycka värden till swap, vilket saktar ner alla andra klienter. Utan cgroup-begränsningar är en enda felaktig loop en tillgänglighetsattack. Med dem är det en blockerad process och en avisering.

Betyder det att containrar är osäkra? Nej. Det betyder att du måste behandla kärnan som en delad förtroendezon. Innan du väljer, skriv ett riskutlåtande på ett stycke: 'Om en klients container äventyras kan angriparen komma åt: [lista]. Affärskostnaden skulle vara: [belopp eller påverkan].' Om det stycket skrämmer dig är du inte paranoid. Du är ärlig.

För en djupare titt på isoleringsspektrumet, från delade containrar till helt separata stackar, se vår guide om att designa en multi-tenant Docker-arkitektur.

Tre sätt att dela upp det (välj ett innan du lanserar)

Det finns egentligen tre arkitekturer för multi-tenant-isolering. Varje 'bästa praxis' är en kombination av dessa.

MetodIsoleringsbarriärBäst närSvåraste förbehållet
Containrar per klientKernel-namespaces + cgroupsMånga små klienter, låg risk per klient, behov av densitetEn enda kärnexploatering kan bryta varje klient på den värden
En VM per klientHypervisor/hårdvaruvirtualiseringReglerad data, fientliga klienter, högt värde per klientTyngre, långsammare att provisionera, du uppdaterar ett OS per klient
Containrar i VM:arVM-gräns runt containeriserade arbetsbelastningarDensitet plus ett hårt skal mellan grupperKostnader och operativ overhead nästan fördubblas

Containrar per klient. Detta är standard för de flesta SaaS-grundare. Varje klient får sin egen container eller lilla Compose-stack. Provisionering är omedelbar, imagen är liten, CI/CD är enkel. Resursbegränsningar hindrar stökiga grannar från att äta upp servern. Avvägningen är den delade kärnan. Om du kan hålla arbetsbelastningarna icke-privilegierade och uppdatera värden regelbundet, är detta ofta det rätta första steget.

Sätt inte två klienter i samma container. Det är en delad kärna plus en delad runtime plus ett delat filsystem. Om en klient laddar upp en fil som skapar en process, är den andra klienten redan i samma processtabell. En container är din isoleringsenhet; gör det till en klient per container.

Vad med databasen? Om varje klient ansluter till en MongoDB- eller PostgreSQL-instans med samma autentiseringsuppgifter har du redan lagt till en enorm delad komponent. Ge varje klient separata autentiseringsuppgifter, och helst en separat databas eller ett separat schema. Containrar isolerar appen; databasen är ofta det första läckaget en angripare testar.

En VM per klient. Ge varje klient en fullständig virtuell maskin. Hypervisorn lägger till en gräns på hårdvarunivå, vilket är exakt vad en kärnexploatering måste korsa för att nå värden. Detta spelar roll för reglerade miljöer eller när klienter inte är betrodda. Kostnaden är densitet och tid. Du hanterar nu en flotta av operativsystem, inte bara containrar. Varje VM behöver uppdateringar, säkerhetsagenter och övervakning. För en ensam grundare är det riktigt arbete.

Mönster som fungerar på den här nivån: använd infrastruktur-som-kod för att skapa en VM från samma basavbild, baka in uppdateringar i nya avbilder istället för att uppdatera live-system, och avsluta arbetsbelastningar du inte känner igen. Håll VM:ens hanteringsport stängd mot internet.

Containrar i VM:ar. Den här hybriden diskuteras sällan i nybörjarhandledningar. Du sätter en liten VM runt varje klient (eller liten grupp av klienter) och kör sedan containrar inuti den VM:en. VM:en är en sprängradiebehållare; containrarna är bara distribuerbara enheter. Detta ger dig den hårda kanten av virtualisering och reproducerbarheten hos avbilder. Det kostar mer, eftersom du betalar för virtualiseringsoverhead och container-flexibilitet, men det kan vara den sundaste långsiktiga modellen när du inte fullt ut kan lita på klienter.

Ett vanligt mikroexempel: en klient kör ett Node-API och en bakgrundsarbetare. Istället för en enorm container med båda processerna, använd en VM, sedan två containrar med olika resursbegränsningar, ett delat nätverk och ingen direkt internettexponering för arbetaren. VM:en ger den hårda kanten; containrarna ger struktur.

Vilken ska du välja? Tabellen är din korta lista. Nästa avsnitt gör beslutet konkret.

Om du väljer containrar, gör dessa sex saker eller strunta i det

Containrar per klient är bra om du behandlar varje container som en potentiell angripare. Det börjar med konfiguration, inte önsketänkande.

0. Begränsa resurser innan du litar på någon. Cgroups är en rättvisemekanism och ett försvar mot tillgänglighetsproblem. Ställ in --memory och --cpus per container. En klient som läcker minne ska träffa sin egen gräns, inte din servers. Detta är ingen säkerhetsgräns, men en stökig granne är en attack utan en enda rad kod. En praktisk start: --memory 512m --cpus 0.5. För en arbetsprocess, börja lägre och skala upp.

1. Kör som en icke-root-användare. Låt aldrig containerprocessen använda UID 0 om du inte absolut måste. Sätt en användare i Dockerfile och skicka --user som extra skydd. En exploatering som körs som en oprivilegierad användare har mycket färre vägar till kärnan. I din Dockerfile, skapa en användare: RUN useradd -u 10001 app och USER app. Hoppa inte över detta för att spara tid.

2. Ta bort alla kapabiliteter du inte behöver. Linux-kapabiliteter delar upp root:ens makt i små bitar. De flesta webbappar behöver nästan inga. Börja med --cap-drop=ALL och lägg bara tillbaka det du vet att du behöver. En container utan CAP_SYS_ADMIN är mycket svårare att använda för namespace-tricks. Om din app försöker binda en privilegierad port, kör den på en hög port och sätt en proxy framför istället för att bevilja NET_BIND_SERVICE.

3. Gör filsystemet skrivskyddat. Din app ska inte skriva till sitt eget containerlager. Montera en tmpfs för tillstånd. En angripare som inte kan skriva till disk har mycket svårare att plantera beständighet. En komprometterad PHP-app som försöker skriva ett webskal misslyckas när rotfilsystemet är skrivskyddat. Du kan montera en namngiven volym för en skrivbar katalog som din app verkligen behöver.

4. Använd seccomp och AppArmor eller SELinux. Dessa skickar riskfyllda syscalls till papperskorgen. Docker levererar en standardprofil för seccomp; använd den. Lägg till en AppArmor-profil för ytterligare ett lager. Du behöver inte behärska varje syscall. Du behöver neka det som en vanlig web worker aldrig kräver. Kör aldrig med --privileged. Den flaggan inaktiverar nästan alla försvar du just satt upp.

5. Segmentera nätverket. Ge inte varje container en rutt till varje annan container. Standard neka, öppna sedan bara de portar du behöver. En komprometterad databascontainer ska inte kunna skanna din adminpanel. Om klienter är i separata nätverk kan ett intrång i ett nätverk inte sprida sig lateralt.

En praktisk start:

docker run --user 10001 --cap-drop=ALL --security-opt no-new-privileges --read-only --tmpfs /tmp:rw,size=64M --security-opt seccomp=default.json --memory 512m --cpus 0.5 myimage

Sätt samma flaggor i en Compose-fil och tillämpa dem på varje klient. Detta är inte komplett, men det är en mycket starkare standard än vad docker run ger dig direkt.

För en djupare genomgång, använd vår steg-för-steg-guide för förstärkning av Docker-containrar i multi-tenant-hostning.

Dockers förbättrade containerisolering är undantaget du bör känna till

Om du kör i en hanterad Docker-miljö, leta efter Dockers förbättrade containerisolering (ECI). Den använder användarnamespace-isolering och en säker containerruntime under huven. Root i en container mappas till en oprivilegierad användare på värden, så även en container som kör som root får inte root-privilegier på värden. Den blockerar också farliga kapabiliteter och syscalls som standard. Detta är inget du kan återskapa med några få flaggor på vanlig Docker. Om din plattform stöder det, slå på det. Det tar inte bort behovet av icke-root-användare och resursbegränsningar, men det ändrar riskmatematiken.

Du kan approximera delar av detta med användarnamespace-omapping (userns-remap) i Docker-daemonen. Det är inte lika komplett som en säker runtime, men det är bättre än ingenting. Om du använder det, verifiera att UID-mappningen fungerar innan du litar på det.

VM-felslutet: Att flytta till virtuella maskiner är inte förstärkning

Här är den konträra delen, och det är den del de flesta hoppar över. Om du flyttar till en VM per klient och sedan distribuerar dina vanliga containrar inuti den, har du inte tagit bort ditt containersäkerhetsproblem. Du har lagt till en bred bur. Containerflykten fungerar fortfarande; angriparen hamnar bara i VM:en istället för på värden. Det är en verklig förbättring, men du behöver fortfarande de sex stegen.

Den andra fällan är att anta att själva VM:en är säker. En standardavbild med ett svagt SSH-lösenord, ouppdaterade baspaket eller en öppen hanteringsport är en gåva. Hypervisor-gränsen spelar bara roll om gästen är förstärkt och uppdaterad. Annars är din 'säkra VM' en snabbare väg till kompromiss eftersom du känner dig trygg och slutar kontrollera.

Det en VM ger dig är en reducerbar sprängradie. En klients katastrof stannar i en VM. Vad den kostar dig är din tid. Du blir systemadministratör för lika många operativsystem som du har klienter. Om du är en ensam grundare som levererar en produkt, fråga dig själv om du har timmarna att uppdatera och övervaka en flotta. Om ja, kan VM per klient vara rätt val. Om nej, kan containrar med stark förstärkning vara mer ärligt.

Kom också ihåg att din hypervisor-värd är ett kritiskt mål. En komprometterad hypervisor kan se alla gäster. Uppdatera värden, inte bara gästerna. VM:en ursäktar dig inte från att uppdatera värden; den höjer insatsen för att missa det.

En varning om hybriden: anta inte att containrar i en VM ger dig 'två säkerhetslager' gratis. VM:en lägger till en gräns; containern behöver fortfarande icke-root, kapabiliteter och seccomp. Annars är det första lagret bara så starkt som den svagaste containern.

Fyra frågor som avgör debatten på tio minuter

Optimera inte i det abstrakta. Ställ dig själv dessa fyra frågor i ordning. Skriv ner svaren.

1. Vad har min klient tillgång till? Om en klient bara kan nå sin egen webbapp och databas, är containrar per klient med strikta nätverksregler försvarbara. Om en klients data är reglerad eller ekonomiskt känslig, rör dig mot VM:ar.

2. Hur mycket skulle en klients kompromiss kosta mig? Lägg ihop förlorade kunder, juridisk exponering och förtroende. Om siffran är större än kostnaden för att köra VM:ar, lägg pengarna. Om inte, är containrar ett rationellt val.

3. Hur många klienter har jag och hur mycket betalar de? Många små prenumeranter: containerdensitet spelar roll. En handfull stora konton: ge var och en en VM och fakturera därefter. Klienter som betalar dig mindre än en kaffe borde inte var och en kräva ett OS att hantera.

4. Kan jag uppdatera saker enligt ett schema? Containers delar en värdkärna, så att uppdatera värden skyddar alla. VM:ar multiplicerar dina uppdateringsmål. Om du vet att du kommer att hoppa över uppdateringar, välj arkitekturen med färre rörliga delar och hårdare standardinställningar.

Dina svar kommer att gruppera sig. Två eller fler VM-fokuserade svar innebär att du inte bör som standard använda containrar per klient. Tre eller fler container-fokuserade svar innebär att VM:ar är för tidigt. Ett kontraintuitivt resultat: en klient med låga intäkter som har tillgång till känslig data behöver fortfarande VM:en, eftersom den regulatoriska kostnaden inte har något att göra med hur mycket de betalar.

Leverera det minsta du kan lita på, förtjäna sedan mer isolering

Din första arkitektur behöver inte vara din sista. Börja med den stramaste installation du faktiskt kan underhålla, lägg sedan till isolering när din klientbas motiverar det. För de flesta solo-operatörer innebär det containrar per klient med icke-root, begränsade kapabiliteter, skrivskyddade filsystem, seccomp och nätverkssegmentering. För reglerade eller högvärdiga klienter, hoppa direkt till en VM per klient, med containrar bara som ett paketeringslager inuti.

Vad du än väljer, skriv ner beslutet och återkom till det kvartalsvis. När du får din första 'ska vi flytta den här klienten till en VM?'-fråga, kommer du att ha ett svar, och du kommer att ha checklistan som stödjer det. Det är vad isolering faktiskt innebär: en avvägning du hanterar, inte en teknik du köper.

Innan lansering, gå igenom vår praktiska checklista för Docker-isolering – den förvandlar dessa beslut till en lista du kan verifiera innan du visar en sida för en kund.

Sources (5)