Sæt en VPN-klient i en container, og du får ét navnerum med en tunnel i, ikke en vært der router al sin trafik gennem en VPN. Det er separate projekter, og det er netop ved at behandle dem som ét, at containere utilsigtet ender med privilegier. Denne kørebog dækker kun det første: ét gennemgået image, én beskyttet profil og en klientproces begrænset til sit eget netværksnavnerum.

Begynd med et artefakt, som nogen allerede har godkendt. Trinene nedenfor forudsætter et image, der indeholder den kørbare fil openvpn og en komplet klientprofil med certifikat og nøglemateriale allerede opløst inde i mountet. De bygger ikke software, udsteder ikke legitimationsoplysninger og dækker ikke rootless-runtimes og native Windows-containere, som hver især ændrer svaret.

Hvad containeren får, og hvad den ikke får

Hold profilen og alt, hvad den refererer til, i en lokal mappe med begrænsede rettigheder, monteret skrivebeskyttet. Relative stier i profilen skal opløses inden for det mount; det er en detalje, folk overser, når et certifikat ligger en mappe over profilen, og containeren aldrig ser det.

services:
  vpn:
    image: ${VPN_CLIENT_IMAGE:?set the reviewed image reference}
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    volumes:
      - ./vpn:/vpn:ro
    working_dir: /vpn
    entrypoint: ["openvpn"]
    command: ["--config", "/vpn/client.ovpn"]

Den fil giver tre ting og beder ikke om andet. Image-referencen bør fastgøres med digest, hvis din udrulningspolitik siger det, og profilen bør være den gennemgåede frem for en kopi, som nogen har redigeret på plads på værten.

Skrivebeskyttet betyder her, at containeren ikke kan ændre dine filer på værten; det betyder ikke, at containeren ikke kan læse den private nøgle, du har mounted. Det er hele pointen med mountet, og det er også derfor filrettigheder, ejerskab og måden, hemmeligheden kom ind på værten, stadig betyder noget. Giv aldrig en adgangskode på kommandolinjen, og bag den aldrig ind i et image-lag, for begge overlever på steder, du ikke kan rydde op i senere.

Kapabiliteter, entrypoint og fælden imellem dem

Der skal to indstillinger til, og de løser uafhængige opgaver. NET_ADMIN tillader manipulation af interfaces og ruter inde i containerens navnerum. Enhedsposten /dev/net/tun tillader processen overhovedet at åbne tunneleenheden. Giver man den ene uden den anden, får man en klar fejl, hvilket er praktisk: en manglende TUN-enhed fejler ved åbning, mens en manglende kapabilitet fejler, når klienten forsøger at konfigurere interfacet.

Ingen af dem retfærdiggør privileged: true. En privilegeret container får alle kapabiliteter plus enhedsadgang, og det er en langt større bevilling, end denne arbejdsbyrde har brug for. referencen for runtime-privilegier og kapabiliteter beskriver, hvor langt den bevilling rækker. Hvis et chart eller et blogindlæg beder om det, så tjek først, om kravet i virkeligheden var en manglende enhedssti eller en manglende kapabilitet.

Tilsidesættelsen af entrypoint er den del, der bider. At kalde openvpn direkte er korrekt for et almindeligt image og forkert for ethvert produktimage, hvis startscript forbereder firewallregler, resolverfiler eller legitimationsoplysninger, før daemonen startes. Tilsidesætter man det script, springes forberedelsen over, og du står tilbage med at fejlfinde en tunnel, der kom op i et halvkonfigureret navnerum.

Kør klienten i forgrunden, og lad den være containerens hovedproces. En proces, der daemoniserer, afsluttes straks, containeren genstarter i en løkke, og de logs, du har brug for, er spredt over genstarter, der alle ser ens ud.

Verificér indefra navnerummet, ikke fra værten

Rejs stacken, bekræft i loggen, at klienten nåede en forbundet tilstand, og undersøg derefter det navnerum, der faktisk holder tunnelen.

docker compose up -d
docker compose logs --tail 40 vpn
docker compose exec vpn ip -br addr
docker compose exec vpn ip route

Adresselisten bør vise et tunnelinterface med en adresse fra poolen, og rutetabellen bør vise de push'ede destinationer, der peger på det. Hvis interfacet mangler, nåede klienten aldrig til at oprette det. Hvis det findes, men ingen rute bruger det, er stedet at lede push eller rutevalgene.

Test derefter den destination, du har brug for, fra det navnerum, der skal bruge tunnelen, med applikationen og ikke kun med ping. Navneopløsning er en separat fejl, der er værd at teste eksplicit: en push'et DNS-adresse opdaterer ikke containerens resolver af sig selv, og en selvstændig klient, der ignorerer pushet, vil opløse navne gennem det, image'et har konfigureret.

Den afbrudte tilstand er en del af kontrakten

Genopkoblingsadfærd kræver sin egen kontrol, for en genstartsstrategi beviser, at processen genstarter, og siger intet om, hvad trafikken gør imellemtiden. Stop tunnelen, prøv den tilsigtede destination, og se, om forbindelsen afvises, hænger eller stille lykkes gennem containerens almindelige rute. Det sidste udfald er det, der betyder noget, og det er usynligt i en sund log.

Hvis arbejdsbyrden aldrig må bruge den almindelige vej, så design det eksplicit med firewallregler og et parathedssignal inde i navnerummet. En kill switch er en politik, du skriver og tester, ikke en egenskab, du arver ved at køre en klient i en container.

Husk til sidst grænsen: disse ruter lever i containerens navnerum og erstatter ikke værtens rutetabel. Dockers videresendelses-, oversættelses- og firewallregler er fortsat en del af den ydre vej, og en offentliggjort port er ikke et VPN-interface.

Relaterede artikler