Sätt en VPN-klient i en container och du får ett namnrum med en tunnel i, inte en värd som dirigerar all sin trafik genom en VPN. Det är separata projekt, och att behandla dem som ett är just hur containrar råkar bli privilegierade. Den här handboken täcker bara det första: en granskad avbild, en skyddad profil och en klientprocess som är begränsad till sitt eget nätverksnamnrum.

Börja med en artefakt som någon redan har godkänt. Stegen nedan förutsätter en avbild som innehåller den körbara filen openvpn och en fullständig klientprofil med certifikat och nyckelmaterial redan lösta inuti monteringen. De bygger ingen programvara, utfärdar inga inloggningsuppgifter och täcker inte rootlösa körtidsmiljöer eller inbyggda Windows-containrar, vilka var och en ändrar svaret.

Vad containern får och inte får

Håll profilen och allt den refererar till i en lokal katalog med begränsade rättigheter, monterad skrivskyddad. Relativa sökvägar i profilen måste lösas inom den monteringen; det är en detalj folk missar när ett certifikat ligger en katalog ovanför profilen och containern 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 filen ger tre saker och begär inget annat. Avbildsreferensen bör fästas med digest om din distributionspolicy säger det, och profilen bör vara den granskade, inte en kopia som någon har redigerat på plats på värden.

Skrivskyddad här betyder att containern inte kan ändra dina filer på värden; det betyder inte att containern inte kan läsa den privata nyckel du monterade. Det är hela poängen med monteringen, och därför spelar filrättigheter, ägande och hur hemligheten kom in på värden fortfarande roll. Lämna aldrig ett lösenord på kommandoraden och baka aldrig in det i ett avbildslager, eftersom båda överlever på platser du inte kan städa senare.

Kapabiliteter, entrypoint och fällan mellan dem

Det behövs två inställningar och de gör orelaterat arbete. NET_ADMIN tillåter manipulation av gränssnitt och rutter i containerns namnrum. Enhetsposten /dev/net/tun tillåter processen att överhuvudtaget öppna tunneleenheten. Att ge den ena utan den andra ger ett tydligt fel, vilket är praktiskt: en saknad TUN-enhet misslyckas vid öppning, medan en saknad kapabilitet misslyckas när klienten försöker konfigurera gränssnittet.

Ingen av dem motiverar privileged: true. En privilegierad container får alla kapabiliteter plus enhetsåtkomst, och det är en mycket större beviljning än denna arbetsbelastning behöver. referensen om körtidsprivilegier och kapabiliteter förklarar hur långt den beviljningen sträcker sig. Om ett chart eller ett blogginlägg ber om det, kontrollera först om kravet egentligen var en saknad enhetssökväg eller en saknad kapabilitet.

Överskrivningen av entrypoint är den del som biter. Att anropa openvpn direkt är rätt för en vanlig avbild och fel för varje produktavbild vars startskript förbereder brandväggsregler, resolverfiler eller inloggningsuppgifter innan daemonen körs. Att skriva över det skriptet hoppar över förberedelsen och lämnar dig att felsöka en tunnel som kom upp i ett halvkonfigurerat namnrum.

Kör klienten i förgrunden och låt den vara containerns huvudprocess. En process som daemoniserar avslutas omedelbart, containern startar om i en loop, och loggarna du behöver är spridda över omstarter som alla ser likadana ut.

Verifiera inifrån namnrummet, inte från värden

Res stacken, bekräfta i loggen att klienten nådde ett anslutet tillstånd och inspektera sedan namnrummet som faktiskt håller tunneln.

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

Adresslistan bör visa ett tunnelgränssnitt med en adress från poolen, och rutttabellen bör visa de pushade destinationerna som pekar mot det. Om gränssnittet saknas nådde klienten aldrig fram till att skapa det. Om det finns men ingen rutt använder det, är stället att leta på push eller ruttalternativen.

Testa sedan destinationen du bryr dig om, från namnrummet som ska använda tunneln, med programmet och inte bara med ping. Namnuppslagning är ett separat fel som är värt att testa uttryckligen: en pushad DNS-adress uppdaterar inte en containers resolver av sig själv, och en fristående klient som ignorerar pushen löser namn genom det avbilden har konfigurerat.

Det frånkopplade tillståndet är en del av kontraktet

Återanslutningsbeteendet behöver sin egen kontroll, eftersom en omstartspolicy bevisar att processen startar om och inte säger något om vad trafiken gör under tiden. Stoppa tunneln, prova den avsedda destinationen och observera om anslutningen nekas, hänger eller lyckas tyst via containerns vanliga rutt. Det sista utfallet är det som spelar roll, och det är osynligt i en frisk logg.

Om arbetsbelastningen aldrig får använda den vanliga vägen, designa det uttryckligen med brandväggsregler och en beredskapssignal i namnrummet. En kill switch är en policy du skriver och testar, inte en egenskap du ärver genom att köra en klient i en container.

Till sist, kom ihåg gränsen: dessa rutter lever i containerns namnrum och ersätter inte värdens rutttabell. Dockers vidarebefordrings-, översättnings- och brandväggsregler är fortfarande en del av den yttre vägen, och en publicerad port är inte ett VPN-gränssnitt.

Relaterade artiklar