OpenVPN în Docker: contractul de execuție de care are nevoie un client minim
O imagine revizuită, un singur profil protejat, acces TUN și NET_ADMIN. Ce primește containerul, ce nu trebuie să primească și cum se demonstrează că tunelul funcționează.
Puneți un client VPN într-un container și obțineți un singur spațiu de nume cu un tunel în interior, nu o gazdă care își dirijează tot traficul printr-un VPN. Acestea sunt proiecte separate, iar tratarea lor ca unul singur este exact modul în care containerele ajung privilegiate din greșeală. Acest ghid acoperă doar primul caz: o imagine revizuită, un profil protejat și un proces de client limitat la propriul spațiu de nume de rețea.
Începeți cu un artefact pe care cineva l-a aprobat deja. Pașii de mai jos presupun o imagine care conține executabilul openvpn și un profil complet de client cu certificatul și materialul cheii deja rezolvate în interiorul montării. Ei nu construiesc software, nu emit acreditări și nu acoperă medii fără root sau containere Windows native, fiecare dintre acestea schimbând răspunsul.
Ce primește containerul și ce nu
Păstrați profilul și tot la ce face referire într-un director local cu permisiuni restricționate, montat doar pentru citire. Căile relative din profil trebuie rezolvate în interiorul acelei montări; acesta este un detaliu pe care oamenii îl ratează când un certificat se află cu un director deasupra profilului și containerul nu îl vede niciodată.
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"]
Fișierul respectiv oferă trei lucruri și nu cere nimic altceva. Referința imaginii ar trebui fixată prin digest dacă politica dumneavoastră de implementare spune asta, iar profilul ar trebui să fie cel revizuit, nu o copie pe care cineva a editat-o pe loc pe gazdă.
Doar pentru citire înseamnă aici că containerul nu poate modifica fișierele dumneavoastră de pe gazdă; nu înseamnă că containerul nu poate citi cheia privată pe care ați montat-o. Acesta este tot rostul montării, și de aceea permisiunile fișierelor, proprietatea și modul în care secretul a ajuns pe gazdă contează în continuare. Nu transmiteți niciodată o parolă pe linia de comandă și nu o coaceți într-un strat de imagine, pentru că ambele supraviețuiesc în locuri pe care nu le veți putea curăța mai târziu.
Capabilități, entrypoint și capcana dintre ele
Sunt necesare două setări și ele fac lucruri nelegate între ele. NET_ADMIN permite manipularea interfețelor și rutelor în spațiul de nume al containerului. Intrarea de dispozitiv /dev/net/tun permite procesului să deschidă dispozitivul de tunel. A acorda una fără cealaltă produce o eroare clară, ceea ce este convenabil: un dispozitiv TUN lipsă eșuează la deschidere, în timp ce o capabilitate lipsă eșuează când clientul încearcă să configureze interfața.
Niciuna dintre ele nu justifică privileged: true. Un container privilegiat primește toate capabilitățile plus acces la dispozitive, iar aceasta este o concesie mult mai mare decât are nevoie această sarcină. referința despre privilegii și capabilități de execuție explică până unde ajunge acea concesie. Dacă un chart sau o postare de blog o cere, verificați mai întâi dacă cerința nu era de fapt o cale de dispozitiv lipsă sau o capabilitate lipsă.
Suprascrierea entrypoint este partea care mușcă. Apelarea directă a openvpn este corectă pentru o imagine obișnuită și greșită pentru orice imagine de produs al cărei script de pornire pregătește reguli de paravan de foc, fișiere de resolver sau acreditări înainte de a executa demonul. Suprascrierea acelui script sare peste pregătire și vă lasă să depanați un tunel care a pornit într-un spațiu de nume pe jumătate configurat.
Rulați clientul în prim-plan și lăsați-l să fie procesul principal al containerului. Un proces care se demonizează iese imediat, containerul repornește în buclă, iar jurnalele de care aveți nevoie sunt împrăștiate între reporniri care arată toate la fel.
Verificați din interiorul spațiului de nume, nu de pe gazdă
Ridicați stiva, confirmați în jurnal că clientul a atins o stare conectată, apoi inspectați spațiul de nume care ține de fapt tunelul.
docker compose up -d
docker compose logs --tail 40 vpn
docker compose exec vpn ip -br addr
docker compose exec vpn ip route
Lista de adrese ar trebui să arate o interfață de tunel cu o adresă din pool, iar tabelul de rute ar trebui să arate destinațiile împinse care indică spre ea. Dacă interfața lipsește, clientul nu a ajuns niciodată să o creeze. Dacă există dar nicio rută nu o folosește, locul de căutat sunt push sau opțiunile de rută.
Apoi testați destinația care vă interesează, din spațiul de nume care ar trebui să folosească tunelul, cu aplicația și nu doar cu un ping. Rezolvarea numelor este o defecțiune separată care merită testată explicit: o adresă DNS împinsă nu actualizează singură resolverul unui container, iar un client independent care ignoră push-ul va rezolva nume prin ceea ce a configurat imaginea.
Starea deconectată face parte din contract
Comportamentul de reconectare are nevoie de propria verificare, pentru că o politică de repornire dovedește că procesul repornește și nu spune nimic despre ce face traficul între timp. Opriți tunelul, încercați destinația dorită și urmăriți dacă conexiunea este refuzată, se blochează sau reușește în tăcere pe ruta obișnuită a containerului. Ultimul rezultat este cel care contează și este invizibil într-un jurnal sănătos.
Dacă sarcina nu trebuie să folosească niciodată calea obișnuită, proiectați asta explicit cu reguli de paravan de foc și un semnal de pregătire în spațiul de nume. Kill switch este o politică pe care o scrieți și o testați, nu o proprietate moștenită rulând un client într-un container.
În final, țineți minte granița: aceste rute trăiesc în spațiul de nume al containerului și nu înlocuiesc tabelul de rutare al gazdei. Regulile de redirecționare, traducere și paravan de foc ale Docker rămân parte a căii exterioare, iar un port publicat nu este o interfață VPN.