OpenVPN sa Docker: ang runtime contract na kailangan ng isang minimal na client
Isang sinuring image, isang protektadong profile, TUN access at NET_ADMIN. Ano ang nakukuha ng container, ano ang hindi dapat makuha at paano patunayan na gumagana ang tunnel.
Maglagay ng VPN client sa isang container at makakakuha ka ng isang namespace na may tunnel sa loob, hindi isang host na nagruruta ng lahat ng trapiko nito sa VPN. Magkahiwalay na proyekto ang mga ito, at ang pakikitungo sa kanila bilang isa ay siyang paraan kung paano nagiging privileged ang mga container nang hindi sinasadya. Sinasaklaw lamang ng gabay na ito ang una: isang sinuring image, isang protektadong profile at isang proseso ng client na nakakulong sa sarili nitong namespace ng network.
Magsimula sa isang artifact na may nag-apruba na. Ipinapalagay ng mga hakbang sa ibaba ang isang image na naglalaman ng executable na openvpn at isang kumpletong profile ng client na ang sertipiko at key material ay nalutas na sa loob ng mount. Hindi sila gumagawa ng software, hindi naglalabas ng kredensyal at hindi sumasaklaw sa mga runtime na walang root o native na Windows container, na bawat isa ay nagbabago ng sagot.
Ano ang nakukuha ng container at ano ang hindi
Panatilihin ang profile at lahat ng tinutukoy nito sa isang lokal na direktoryo na may limitadong pahintulot, naka-mount bilang read-only. Ang mga relative na path sa loob ng profile ay dapat malutas sa loob ng mount na iyon; ito ay isang detalye na nakakaligtaan ng mga tao kapag ang sertipiko ay isang direktoryo sa itaas ng profile at hindi ito nakikita ng container.
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"]
Nagbibigay ang file na iyon ng tatlong bagay at wala nang hinihiling. Dapat i-pin ang reference ng image sa digest kung sabi ng patakaran ng deployment mo, at ang profile ay dapat ang sinuri, hindi isang kopya na in-edit ng isang tao sa host.
Ang read-only dito ay nangangahulugang hindi mababago ng container ang mga file mo sa host; hindi ito nangangahulugang hindi mababasa ng container ang pribadong key na na-mount mo. Iyon ang buong layunin ng mount, at dahil doon mahalaga pa rin ang pahintulot ng file, pagmamay-ari at kung paano nakarating ang sikreto sa host. Huwag kailanman magpasa ng password sa command line at huwag itong i-bake sa isang layer ng image, dahil pareho silang nabubuhay sa mga lugar na hindi mo malilinis mamaya.
Mga capability, entrypoint at ang bitag sa pagitan nila
Kailangan ang dalawang setting at gumagawa sila ng magkaibang trabaho. Pinapayagan ng NET_ADMIN ang manipulasyon ng interface at ruta sa loob ng namespace ng container. Pinapayagan ng device entry na /dev/net/tun ang proseso na buksan man lang ang tunnel device. Ang pagbibigay ng isa nang wala ang iba ay gumagawa ng malinaw na pagpalya, na maginhawa: ang nawawalang TUN device ay pumapalya sa pagbubukas, habang ang nawawalang capability ay pumapalya kapag sinusubukan ng client na i-configure ang interface.
Wala sa dalawa ang nagbibigay-katwiran sa privileged: true. Ang privileged na container ay nakakakuha ng lahat ng capability at access sa device, at mas malaki ito kaysa sa kailangan ng workload na ito. Ang reference sa runtime privilege at capability ay nagpapaliwanag kung gaano kalayo ang naaabot ng pagbibigay na iyon. Kung hinihiling ito ng isang chart o post sa blog, tingnan muna kung ang pangangailangan ay talagang nawawalang device path o nawawalang capability.
Ang pag-override sa entrypoint ang bahaging kumakagat. Ang direktang pagtawag sa openvpn ay tama para sa ordinaryong image at mali para sa anumang product image na ang startup script ay naghahanda ng firewall rule, resolver file o kredensyal bago patakbuhin ang daemon. Ang pag-override sa script na iyon ay lumalaktaw sa paghahanda at iniiwan kang nagde-debug ng tunnel na umangat sa kalahating na-configure na namespace.
Patakbuhin ang client sa foreground at hayaang maging pangunahing proseso ito ng container. Ang prosesong nagiging daemon ay agad na lumalabas, nagre-restart ang container sa loop, at ang mga log na kailangan mo ay nakakalat sa mga restart na magkakatulad ang itsura.
I-verify mula sa loob ng namespace, hindi mula sa host
Itaas ang stack, kumpirmahin sa log na naabot ng client ang konektadong estado, pagkatapos ay siyasatin ang namespace na talagang may hawak ng tunnel.
docker compose up -d
docker compose logs --tail 40 vpn
docker compose exec vpn ip -br addr
docker compose exec vpn ip route
Dapat ipakita ng listahan ng address ang interface ng tunnel na may address mula sa pool, at dapat ipakita ng route table ang mga na-push na destinasyong nakaturo roon. Kung wala ang interface, hindi kailanman nakarating ang client sa punto ng paggawa nito. Kung mayroon ito ngunit walang rutang gumagamit nito, ang dapat tingnan ay ang push o ang mga opsyon sa ruta.
Pagkatapos ay subukan ang destinasyong mahalaga sa iyo, mula sa namespace na dapat gumamit ng tunnel, gamit ang application at hindi lamang ping. Ang paglutas ng pangalan ay hiwalay na pagpalya na dapat tahasang subukan: ang na-push na DNS address ay hindi nag-a-update ng resolver ng container nang mag-isa, at ang standalone na client na hindi pinapansin ang push ay maglulutas ng mga pangalan sa pamamagitan ng kung ano ang na-configure ng image.
Ang disconnected na estado ay bahagi ng kontrata
Ang pag-uugali sa muling pagkonekta ay nangangailangan ng sariling pagsusuri, dahil ang restart policy ay nagpapatunay na nag-restart ang proseso at walang sinasabi tungkol sa ginagawa ng trapiko sa pagitan. Itigil ang tunnel, subukan ang nilalayong destinasyon at panoorin kung tinatanggihan ang koneksyon, bumabitin, o tahimik na nagtagumpay sa karaniwang ruta ng container. Ang huling resulta ang mahalaga, at hindi ito nakikita sa malusog na log.
Kung hindi dapat gamitin ng workload ang karaniwang landas kahit kailan, idisenyo ito nang tahasan gamit ang mga firewall rule at signal ng kahandaan sa loob ng namespace. Ang kill switch ay isang patakarang isinusulat at sinusubukan mo, hindi isang katangiang namamana mula sa pagpapatakbo ng client sa container.
Sa huli, tandaan ang hangganan: ang mga rutang ito ay nabubuhay sa namespace ng container at hindi pinapalitan ang routing table ng host. Ang mga tuntunin sa forwarding, pagsasalin at firewall ng Docker ay nananatiling bahagi ng panlabas na landas, at ang na-publish na port ay hindi isang VPN interface.