Blog

De vertrouwensvalkuil: hoe onze multi-tenant Docker-opstelling gegevens lekte (en hoe we het oplosten)

Ontdek hoe een naïeve Docker-opstelling van een team leidde tot een cross-tenant datalek en de gelaagde isolatiestrategie die het voorkwam.

Samenvatting

Docker-containers zijn standaard niet geïsoleerd: ze delen de hostkernel en zonder bewuste configuratie kunnen tenants elkaar hinderen. Dit artikel beschrijft een reëel scenario waarin een multi-tenant hostingprovider ontdekte dat clientcontainers elkaars databases konden benaderen vanwege gedeelde netwerken en zwakke beveiligingsinstellingen. We tonen de stapsgewijze wijzigingen die de inbreuk herstelden: per-tenant door de gebruiker gedefinieerde netwerken, niet-rootgebruikers, verwijderde mogelijkheden, alleen-lezen bestandssystemen en seccomp-profielen. Een veelgemaakte aanname is dat containers inherent sterke isolatie bieden; we dagen dat uit door uit te leggen waarom VM's nog steeds een sterkere grens bieden en wanneer een hybride aanpak te overwegen. De conclusie benadrukt dat isolatie een gelaagde oefening is, geen enkele vinkje.

Het incident: wanneer containers te veel praten

Je hebt Docker op een enkele host gezet om meerdere clientwebsites te draaien. Elke client heeft zijn eigen container—een nette, geïsoleerde omgeving, toch? Dat dachten we ook. Tot een routinematige beveiligingsaudit onthulde dat de container van Client A de MySQL-socket van Client B's container op dezelfde host las. Ze deelden het standaard bridge-netwerk. Ergerniswekkend was dat de containers als root draaiden, dus een aanvaller die er één compromitteerde, kon rommelen met de Docker-socket van de host of het bestandssysteem van een andere container. De inbreuk was geen geavanceerde exploit; het was een eenvoudige verkeerde configuratie. Gegevens lekten. Vertrouwen verdween.

Het foutscenario is niet ongewoon. Veel teams nemen aan dat Docker's namespaces en cgroups automatisch tenants afschermen, maar ze onderschatten hoeveel ontsnappingsluiken standaard open blijven. Standaard bridge-netwerken bieden geen netwerkisolatie tussen containers. Draaien als root geeft de container meer macht dan nodig. En zonder expliciete resource-limieten kan één lawaaierige buur anderen uithongeren van CPU of geheugen.

Stap 1: stop met het delen van één netwerk

Onze eerste oplossing was om elke tenant zijn eigen door de gebruiker gedefinieerde Docker-netwerk te geven. Dit voorkomt dat containers elkaar bereiken tenzij je ze expliciet verbindt. We maakten een script dat voor elke tenant een dedicated netwerk opzet en hun applicatiecontainer eraan koppelt. De database-container leeft in hetzelfde tenantnetwerk, maar we voegden ook een intern netwerk toe voor alleen intra-tenantcommunicatie. Geen cross-tenant gespioneer meer.

We isoleerden ook de databases door ze in aparte containers op hetzelfde tenantnetwerk te draaien, met aparte datavolumes. Dit zorgde ervoor dat zelfs als een aanvaller de app-container binnendrong, ze geen databaseverkeer van een andere tenant konden onderscheppen.

Voor een diepere duik in netwerkisolatiestrategieën, zie de Een praktische Docker-isolatie-checklist voor multi-tenant hosting.

Stap 2: verwijder onnodige privileges

Standaard draaien Docker-containers met een beperkte set Linux-capabilities, maar ze hebben nog steeds meer dan de meeste applicaties nodig. Onze containers draaiden als root, waardoor processen binnenin acties konden uitvoeren zoals het mounten van bestandssystemen of het wijzigen van kernelparameters. We schakelden over naar het draaien van de applicatie als een niet-rootgebruiker in de container (met de USER-richtlijn in het Dockerfile) en verwijderden alle capabilities behalve de absoluut noodzakelijke. Voor een typische web-app zijn dat mogelijk alleen NET_BIND_SERVICE (voor het binden aan poorten onder 1024) en CHOWN (voor schrijven naar mappen). We voegden ook --security-opt no-new-privileges toe om privilege-escalatie te voorkomen.

Deze stap alleen al elimineerde veel voorkomende container-escape vectoren. Een aanvaller die de webserver compromitteert, kan geen pakketten installeren, systeembinaries wijzigen of de Docker-socket van de host benaderen omdat het proces de CAP_SYS_ADMIN- of CAP_DAC_OVERRIDE-capabilities mist.

Stap 3: vergrendel het bestandssysteem

Beschrijfbare bestandssystemen zijn een veelvoorkomend aanvalsoppervlak. We maakten het rootbestandssysteem alleen-lezen (--read-only) voor alle containers, en mountten vervolgens tijdelijke bestandssystemen (tmpfs) voor mappen die schrijftoegang nodig hebben, zoals /tmp en de cache-directory van de applicatie. Dit voorkomt dat een aanvaller applicatiecode wijzigt of kwaadaardige binaries aanhoudt.

Daarnaast gebruikten we Docker's --mount-optie om gevoelige mappen zoals de Docker-socket alleen te bind-mounten wanneer absoluut noodzakelijk—en nooit op productiecontainers. Het principe: als de container niet naar een pad hoeft te schrijven, maak het dan alleen-lezen.

Stap 4: pas Seccomp- en AppArmor-profielen toe

Standaard seccomp-profielen blokkeren al veel gevaarlijke syscalls, maar we hebben ze verder aangepast om alleen de syscalls whitelisten die onze applicatie echt nodig heeft. Dit is een afweging omdat het profilering van de applicatie vereist. Een eenvoudigere benadering is om Docker's standaard seccomp-profiel te gebruiken en dan --security-opt seccomp=pad/naar/profiel.json toe te voegen als je strengere regels nodig hebt. Evenzo kunnen AppArmor-profielen containerprocessen beperken tot specifieke bestandspaden en capabilities. We schakelden AppArmor in en gebruikten een aangepast profiel dat de toegang beperkte tot alleen de datamappen van de applicatie.

Voor een uitgebreide handleiding over deze versterkingsstappen, raadpleeg Docker-containers hardenen voor multi-tenant hosting: een stapsgewijze isolatiegids.

Het tegengeluid: soms heb je VM's nodig

Hoe hard ook, containers delen de kernel van de host. Een kernelkwetsbaarheid kan alle isolatie in één keer doorbreken. Daarom draaien veel beveiligingsbewuste platforms containers in lichte VM's—elke tenant krijgt zijn eigen kernel. Dit voegt overhead toe maar biedt een grens op hardwareniveau die containers alleen niet kunnen. Als je tenants creditcardgegevens of medische dossiers verwerken, kan een hybride aanpak (containers in VM's) de juiste keuze zijn. Neem niet aan dat containerisolatie voldoende is voor je bedreigingsmodel; evalueer de gevoeligheid van de gegevens en de wettelijke vereisten.

Voor een diepere vergelijking van isolatieniveaus, lees Een multi-tenant Docker-architectuur ontwerpen: het juiste isolatieniveau kiezen.

Conclusie: isolatie is een stack, geen schakelaar

De oplossing was geen enkele wijziging—het was gelaagdheid: netwerkisolatie, beperkte privileges, alleen-lezen bestandssystemen en syscall-filtering. Zelfs toen accepteerden we dat perfecte isolatie onmogelijk is met gedeelde kernel-containers. Voor onze meest beveiligingsgevoelige tenants verplaatsten we ze naar dedicated hosts. De les: vertrouw geen standaardinstellingen. Audit je Docker-opstelling alsof er al een inbreuk heeft plaatsgevonden. De tijd om te vergrendelen is vóór het lek, niet erna.

Sources (5)