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 مع سطر سجل واضح نتيجة جيدة. صفحة مخزّنة من أصل غير مقصود ليست كذلك.