OpenVPN در Docker: قرارداد اجرایی که یک کلاینت حداقلی به آن نیاز دارد
یک ایمیج بازبینیشده، یک پروفایل محافظتشده، دسترسی TUN و NET_ADMIN. کانتینر چه چیزی میگیرد، چه چیزی نباید بگیرد و چگونه ثابت کنیم تونل کار میکند.
یک کلاینت VPN را در یک کانتینر بگذارید و یک فضای نام با یک تونل درون آن میگیرید، نه یک میزبان که همه ترافیکش را از VPN عبور میدهد. اینها پروژههای جداگانهاند و برخورد با آنها بهعنوان یک پروژه همان راهی است که کانتینرها بهطور تصادفی ممتاز میشوند. این راهنما فقط اولی را پوشش میدهد: یک ایمیج بازبینیشده، یک پروفایل محافظتشده و یک فرایند کلاینت محدود به فضای نام شبکه خودش.
از یک مصنوع شروع کنید که کسی پیشتر تأییدش کرده است. مراحل زیر ایمیجی را فرض میکنند که فایل اجرایی openvpn و پروفایل کامل کلاینت را با گواهی و مواد کلید که پیشتر درون مانت حل شدهاند دارد. آنها نرمافزار نمیسازند، اعتبارنامه صادر نمیکنند و محیطهای بدون root و کانتینرهای بومی Windows را پوشش نمیدهند که هر یک پاسخ را تغییر میدهد.
کانتینر چه چیزی میگیرد و چه چیزی نمیگیرد
پروفایل و هر چیزی که به آن ارجاع میدهد را در یک پوشه محلی با مجوزهای محدود، بهصورت فقطخواندنی مانت کنید. مسیرهای نسبی درون پروفایل باید درون همان مانت حل شوند؛ این جزئیاتی است که مردم از دست میدهند وقتی گواهی یک پوشه بالاتر از پروفایل است و کانتینر هرگز آن را نمیبیند.
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"]
آن فایل سه چیز میدهد و چیز دیگری نمیخواهد. ارجاع ایمیج باید با digest پین شود اگر سیاست استقرارتان این را میگوید، و پروفایل باید همان بازبینیشده باشد نه نسخهای که کسی روی میزبان ویرایش کرده است.
فقطخواندنی در اینجا یعنی کانتینر نمیتواند فایلهای میزبان شما را تغییر دهد؛ یعنی نمیتواند کلید خصوصیای را که مانت کردهاید بخواند، نه. این تمام هدف مانت است و به همین دلیل مجوزهای فایل، مالکیت و نحوه رسیدن راز به میزبان هنوز مهماند. هرگز رمز عبور را در خط فرمان ندهید و آن را در لایه ایمیج نپزید، چون هر دو در جاهایی زنده میمانند که بعداً نمیتوانید پاک کنید.
قابلیتها، entrypoint و تله میان آنها
دو تنظیم لازم است و کارهای بیربطی انجام میدهند. NET_ADMIN دستکاری واسطها و مسیرها را درون فضای نام کانتینر مجاز میکند. ورودی دستگاه /dev/net/tun به فرایند اجازه میدهد اصلاً دستگاه تونل را باز کند. دادن یکی بدون دیگری شکست روشنی تولید میکند که راحت است: دستگاه TUN غایب هنگام باز شدن شکست میخورد، در حالی که قابلیت غایب وقتی شکست میخورد که کلاینت میخواهد واسط را پیکربندی کند.
هیچکدام از این دو privileged: true را توجیه نمیکند. یک کانتینر ممتاز همه قابلیتها بهعلاوه دسترسی به دستگاهها را میگیرد و این بسیار بزرگتر از آن است که این بار کاری نیاز دارد. مرجع امتیازات و قابلیتهای محیط اجرا توضیح میدهد این اعطا تا کجا میرسد. اگر یک chart یا نوشته وبلاگ آن را میخواهد، ابتدا بررسی کنید که آیا نیاز واقعاً یک مسیر دستگاه غایب بود یا یک قابلیت غایب.
بازنویسی entrypoint بخشی است که گاز میگیرد. فراخوانی مستقیم openvpn برای ایمیج معمولی درست است و برای هر ایمیج محصولی که اسکریپت شروعش قواعد فایروال، فایلهای resolver یا اعتبارنامهها را پیش از اجرای دیمن آماده میکند غلط است. بازنویسی آن اسکریپت آمادهسازی را رد میکند و شما را با تونلی تنها میگذارد که در فضای نام نیمهپیکربندیشده بالا آمده است.
کلاینت را در پیشزمینه اجرا کنید و بگذارید فرایند اصلی کانتینر باشد. فرایندی که دیمن میشود فوراً خارج میشود، کانتینر در حلقه راهاندازی مجدد میشود و گزارشهایی که لازم دارید میان راهاندازیهای مجددی که همه یکسان به نظر میرسند پخش میشوند.
از درون فضای نام بررسی کنید، نه از میزبان
پشته را بالا بیاورید، در گزارش تأیید کنید کلاینت به وضعیت متصل رسیده است و سپس فضای نامی را که واقعاً تونل را نگه میدارد بررسی کنید.
docker compose up -d
docker compose logs --tail 40 vpn
docker compose exec vpn ip -br addr
docker compose exec vpn ip route
فهرست نشانیها باید یک واسط تونل با نشانی از استخر نشان دهد و جدول مسیرها باید مقصدهای فرستادهشده را نشان دهد که به آن اشاره میکنند. اگر واسط نیست، کلاینت هرگز به نقطه ساختنش نرسیده است. اگر هست اما هیچ مسیری از آن استفاده نمیکند، جای جستوجو push یا گزینههای مسیر است.
سپس مقصدی که برایتان مهم است را از فضای نامی که قرار است از تونل استفاده کند، با خود برنامه و نه فقط با ping آزمایش کنید. حل نام یک شکست جداگانه است که ارزش آزمون صریح دارد: نشانی DNS فرستادهشده بهخودیخود resolver کانتینر را بهروز نمیکند و کلاینت مستقلی که push را نادیده میگیرد نامها را از طریق آنچه ایمیج پیکربندی کرده حل میکند.
وضعیت قطعشده بخشی از قرارداد است
رفتار اتصال مجدد بررسی خودش را لازم دارد، چون سیاست راهاندازی مجدد ثابت میکند فرایند دوباره شروع میشود و چیزی درباره کاری که ترافیک در این میان میکند نمیگوید. تونل را متوقف کنید، مقصد موردنظر را امتحان کنید و ببینید اتصال رد میشود، معلق میماند یا از مسیر معمولی کانتینر بیصدا موفق میشود. نتیجه آخر همان چیزی است که مهم است و در گزارش سالم نامرئی است.
اگر بار کاری هرگز نباید از مسیر معمولی استفاده کند، آن را بهصراحت با قواعد فایروال و یک سیگنال آمادگی درون فضای نام طراحی کنید. Kill switch سیاستی است که مینویسید و آزمایش میکنید، نه ویژگیای که از اجرای کلاینت در کانتینر به ارث میبرید.
در پایان، مرز را به یاد داشته باشید: این مسیرها در فضای نام کانتینر زندگی میکنند و جدول مسیریابی میزبان را جایگزین نمیشوند. قواعد ارسال، ترجمه و فایروال در Docker بخشی از مسیر بیرونی میمانند و پورت منتشرشده یک واسط VPN نیست.