Nginx در جلو، بکاند خصوصی پشت VPN
مرورگر با Nginx صحبت میکند و Nginx اتصال دومی به داخل تونل باز میکند. هر دو را مسیردهی کن، هر دو گواهی را بررسی کن و تغییرمسیرهای عمومی را عمومی نگه دار.
یک درخواست را از تلفنی در قطار تا بکاندی که فقط داخل شبکه خصوصی وجود دارد دنبال کن. تلفن یک نام عمومی را حل میکند، به نشانی عمومی روی درگاه 443 وصل میشود و یک دستدهی TLS معمولی را کامل میکند. Nginx آن اتصال را میپذیرد و سپس اتصال دومی، کاملاً جدا، به نشانی باز میکند که فقط از طریق رابط VPN به آن میرسد. چیزی در تلفن تغییر نمیکند و تلفن هرگز نمیفهمد که تونلی در کار بوده است.
دنبال کردن یک درخواست از میان دو اتصال
بخش ورودی یک تراکنش HTTP عمومی است. نام عمومی، گواهی عمومی و مسیر پاسخی دارد که باید برای هر نشانیای که بازدیدکننده در آن لحظه دارد کار کند. هر تغییری که آن مسیر را مختل کند، همه بازدیدکنندگان را یکجا خراب میکند. به همین دلیل بخش عمومی همان بخشی است که کمترین علاقه را به آزمایش روی آن داری.
بخش خروجی خصوصی است. Nginx به کلاینت تبدیل میشود، یک نام داخلی را حل میکند یا از نشانی ثابت استفاده میکند و به سرویسی میرسد که اتصال از اینترنت را رد میکرد.
Public client -> frontend public address:443 (TLS, public name)
Frontend -> VPN private backend 10.40.0.20:8443 (TLS, private name)
آنها را در ذهنت جدا نگه دار تا پیکربندی بدیهی شود. اگر ترکیبشان کنی، سرانجام تلاش میکنی یک کلاینت عمومی را در تونل مشارکت دهی، و بیشتر سردرگمی این طراحی از همانجا میآید. بخش خصوصی میتواند شکننده باشد. بخش عمومی نه.
پیش از ساختن هر بخشی از این، تأیید کن که انتشار برنامه عمدی است. یک پروکسی کارآمد خودش یک افشای کارآمد است، و مدل احراز هویت باید یک تصمیم باشد نه یک عارضه جانبی.
نام دروازه باید از تونل جان سالم به در ببرد
در حل نامها تلهای ظریف وجود دارد. اگر DNS خصوصی همان نامی را بازنویسی کند که نمایه VPN برای دروازه خودش استفاده میکند، اتصال دوباره ممکن است در نهایت بخواهد از طریق همان تونلی که باید ایجاد کند به آن دروازه برسد. کلاینت به حل شدن آن نام پیش از وجود تونل نیاز دارد. وقتی نام داخل تونل حل شود، اتصال هرگز نمیتواند ساخته شود.
برای دروازه و بکاند از نامهای تأییدشده جداگانه استفاده کن و سیاست حلکننده را چنان محدود کن که هر نام به نشانیای حل شود که مصرفکنندهاش نیاز دارد. تغییر سرآیند Host یک برنامه اینجا کمکی نمیکند، چون جستوجو را کلاینت VPN انجام میدهد.
سپس به دامنه فکر کن. فرانتاند به مسیری به نشانی بکاند نیاز دارد و همچنین باید به پاسخ دادن به کلاینتهای عمومی ادامه دهد. یک تونل کامل روی آن میزبان میتواند شرط اول را برآورده و شرط دوم را خراب کند.
جایی که سیاست اجازه میدهد، تونل به مقصدی مشخص طراحی پاکیزهتری است. جایی که سیاست تونل کامل را الزامی میکند، فرانتاند به بافت شبکهای جداگانه تعلق دارد، نه اینکه با مسیرهای موردی برای بازدیدکنندگان شناختهشده وصله شود.
بلوک سرور را طوری بنویس که هر دو بخش بررسی کنند
فرض کن بکاند گواهیای برای این نام ارائه میدهد: backend.corp.example و روی نشانی خصوصی پاسخ میدهد. اتصال به یک نشانی در حالی که نامی بررسی میشود، دقیقاً همان کاری است که گزینههای SNI و نام در ماژول پروکسی برای آن هستند.
upstream private_app {
server 10.40.0.20:8443;
}
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate /etc/nginx/tls/app-fullchain.pem;
ssl_certificate_key /etc/nginx/tls/app-key.pem;
location / {
proxy_pass https://private_app;
proxy_ssl_server_name on;
proxy_ssl_name backend.corp.example;
proxy_ssl_trusted_certificate /etc/nginx/trust/backend-ca.pem;
proxy_ssl_verify on;
proxy_ssl_verify_depth 3;
proxy_set_header Host app.example.com;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
هر مسیر و نشانی بالا یک جاینگهدار است. بخشهای مهم همانهایی هستند که بررسی را روشن نگه میدارند: نامی برای بررسی، بسته اعتمادی برای بررسی در برابر آن، و عمقی که با زنجیره تأییدشده همخوان باشد.
proxy_set_header Host app.example.com عمدی است. بکاند باید طوری پیکربندی شود که آن مبدأ عمومی را بپذیرد و به سرآیندهای ارسالشده فقط از این فرانتاند اعتماد کند. بازنویسی X-Forwarded-For با نشانی کلاینت برای یک پروکسی درست و برای یک زنجیره نادرست است، پس استقرار چندپروکسی به ترتیب نشانیهای مورد اعتماد خودش نیاز دارد.
وا داشتن مبدأ عمومی به رفتار مانند یک مبدأ عمومی
برنامه باید URLهای مطلق، تغییرمسیرها و کوکیها را برای مبدأ عمومی منتشر کند. اگر بازدیدکنندهای را به این نشانی هدایت کند: backend.corp.example، آن بازدیدکننده به نامی فرستاده میشود که نمیتواند حل کند.
این را از طریق تنظیم URL خارجی پشتیبانیشده برنامه و یک سیاست تغییرمسیر بازبینیشده درست کن، نه با بازنویسی نام میزبانها در بدنه پاسخها. جایگزینی رشته در محتوای دلخواه بارهای امضاشده را میشکند و باگهایی میسازد که ردیابیشان دشوار است.
کوکیها همین اندازه مراقبت میخواهند. کوکی محدود به نام خصوصی هرگز توسط مرورگری که فقط نام عمومی را میشناسد فرستاده نمیشود.
همچنین تأیید کن که Nginx واقعاً میتواند مالک سوکت عمومی باشد. سرویس وب موجود روی 443 باید جابهجا شود یا فرانتاند باید جای دیگری زندگی کند، چون دو فرایند نمیتوانند به یک نشانی و درگاه یکسان متصل شوند.
بررسی هر بخش جایی که واقعاً اجرا میشود
پیکربندی را با تجزیهگر نصبشده پیش از هر بارگذاری دوباره اعتبارسنجی کن تا یک غلط تایپی شنوندهای را که همین حالا سرویس میدهد زمین نزند.
سپس بخش بکاند را از خود میزبان فرانتاند، در همان بافت شبکهای که Nginx استفاده میکند آزمایش کن. یک پینگ از میان تونل دسترسیپذیری را ثابت میکند و هیچ چیز درباره TLS، مجوزدهی یا برنامه.
در پایان یک درخواست عمومی تازه به نام میزبان واقعی با بررسی عادی گواهی بفرست و مسیر پاسخ را ببین، نه فقط بدنه پاسخ را.
هر دو مشاهده را همراه با پیکربندی مستقر نگه دار و ثبت کن چه اتفاقی میافتد وقتی تونل در دسترس نیست. فرانتاند باید یک خطای upstream کنترلشده برگرداند، نه اینکه بیصدا به بکاند دیگری که کسی مستند نکرده عقبنشینی کند. یک 502 با خط گزارش روشن نتیجه خوبی است. صفحهای ذخیرهشده از مبدأیی ناخواسته چنین نیست.