إدارة "سلسلة الثقة" لعنوان IP الحقيقي للمستخدم النهائي

الخلفية

يجب أن يكون نظام Discourse على دراية بعنوان IP الحقيقي للمستخدم النهائي.

ومع ذلك، لا يتصل المستخدم النهائي مباشرةً بـ Discourse لأن هناك دائمًا خادم ويب واحد أو أكثر في المراحل السابقة (nginx يعمل داخل حاوية Discourse). لذلك، نحتاج إلى طريقة لنقل هذه المعلومات إلى Discourse بطريقة موثوقة.

تُعدّ رسالة x-forwarded-for هي الحل. في هذا الموضوع، سأصف الآليات المحددة للتعامل مع هذه المعلومات بشكل صحيح وكيفية توقعنا لنشرها.

القوالب

تم تحديث القوالب المختلفة للثقة في الوكلاء الوسيدين (مثل cloudflare.template.yml أو fastly.template.yml) لاستخدام أسماء ملفات متوقعة في المنافذ بدلاً من الاعتماد على استبدال النصوص (وهو أمر هش).

أسماء الملفات

server/real-ip-header.conf

يحتوي هذا الملف على الرأس الذي سيستخدمه nginx الذي يعمل داخل الحاوية كمصدر للحقيقة، على سبيل المثال:

real_ip_header x-forwarded-for;

أو كما هو مُحدد في قالب Cloudflare:

real_ip_header cf-connecting-ip;

server/real-ip-recursive.conf

إذا كان هذا الملف موجودًا، فإنه يتحكم في التكرار أثناء معالجة رأس “عنوان IP الحقيقي”. ستحتاج إلى تمكين هذا الخيار إذا كان هناك أكثر من وكيل وسيدي أمام حاوية Discourse.

real_ip_recursive on;

مثال:

Cloudflare → موازن التحميل → حاوية Discourse (التي تتكون من nginx وDiscourse نفسه)

مع هذا الإعداد، سيتلقى nginx رسالة x-forwarded-for تبدو كالتالي:

x-forwarded-for: real_end_user_ip, cloudflare_ip

عند اتصال من عنوان IP لموازن التحميل.

لمعالجة ذلك، يحدد nginx أولاً ما إذا كان عنوان المصدر للاتصال (عنوان IP لموازن التحميل) موثوقًا به (انظر set_real_ip_from)، وإذا كان كذلك، سيعالج آخر عنوان IP في رأس x-forwarded-for.

بما أن عنوان IP هذا هو cloudflare_ip، يحتاج nginx بعد ذلك إلى تكرار العملية مرة أخرى، للتحقق مما إذا كان cloudflare_ip موثوقًا به، واستخدام عنوان IP التالي وهو real_end_user_ip.

server/set-real-ip-from-ENVIRONMENT.conf

يحتوي هذا الملف على تعليمات تخبر nginx عناوين IP الموثوقة، ويمكننا إنشاء العديد من الملفات والتعليمات حسب الحاجة.

تقوم القوالب في discourse_docker بإنشاء هذه الملفات عند الحاجة (مثل set-real-ip-from-cloudflare.conf)، وإذا كانت لديك احتياجات إضافية، يمكنك إضافة ملفاتك الخاصة.

مثال:

إذا كنت تعمل في AWS ولديك موازن تحميل تطبيق (ALB) أمام حاوية Discourse، يمكنك إضافة ملف إضافي عن طريق إضافة الكود التالي إلى تعريف الحاوية (معدلاً ليناسب بيئتك):

run:
  - file:
      path: /etc/nginx/conf.d/outlets/server/set-real-ip-from-aws.conf
      chmod: 644
      # شبكة VPC الخاصة بـ AWS هي 10.42.0.0/16، وثق بأي اتصالات من شبكات ALB
      contents: |
        set_real_ip_from 10.42.66.0/24;
        set_real_ip_from 10.42.67.0/24;
  - file:
      path: /etc/nginx/conf.d/outlets/server/real-ip-header.conf
      chmod: 644
      contents: |
        real_ip_header x-forwarded-for;
5 إعجابات

لدي ملف /data/lc-manager-playbook/discourse/docker-templates/allow-local-proxy.template.yml يحتوي على

after_bundle_exec:
  - replace:
    filename: /etc/nginx/conf.d/discourse.conf
    from: "types {"
    to: |
      set_real_ip_from 192.168.1.0/24;
      set_real_ip_from 192.168.11.0/24;
      set_real_ip_from 172.16.0.0/12;
      set_real_ip_from 10.0.0.0/8;
      real_ip_recursive on;
      real_ip_header X-Forwarded-For;
      types {

يبدو أن هذا لا يزال يعمل حتى اليوم.

هل هناك سبب لعدم توزيع قالب من هذا النوع؟

إعجاب واحد (1)

هذا القالب جيد تمامًا، وسيعمل اليوم وفي المستقبل المنظور.

بشكل أساسي، ضمان التوافق مع المستقبل والمرونة.

إذا تغير الملف في أي وقت، أي إذا أزلنا أو غيرنا السلسلة النصية types { التي تبحث عنها، سيتوقف عن العمل ببساطة.

إذا قمت بتغييره لاستخدام منفذ before-server أو server، فسيستمر في العمل حتى في تلك الحالة.

إعجابَين (2)

ما هو الغرض من تعيين X-Forwarded-For أصلاً الآن؟ من المفترض/الشائع أن يحتوي ليس فقط على عنوان IP للعميل (بقدر ما هو معروف) ولكن أيضاً سلسلة الوكلاء (proxy chain).

في رسالة الالتزام (commit message) تكتب:

وقد ينتهي الأمر باستخدام `client_ip` أو `proxyA_ip` اعتماداً على مسار الكود.

أليس هذا خطأً في Discourse، بعدم تحليل/استخدام القيمة الشائعة لتلك الرأس (header) بشكل متسق (أو بشكل صحيح للمهمة التي يتم فيها تحليلها)، بدلاً من أن تكون قيمة الرأس (وإضافة Nginx كوكيل بشكل شائع إليه) هي المشكلة؟

إذا لم يكن Discourse بحاجة لمعرفة سلسلة الوكلاء، والإعداد المقصود هو بدلاً من ذلك تمرير عنوان IP الحقيقي للعميل فقط (بقدر ما هو معروف)، فإن X-Forwarded-For يفقد غرضه. تم تعيين رأس X-Real-IP بالفعل، بما يتوافق مع الغرض، مما يجعل X-Forwarded-For زائداً عن الحاجة.

هذا منطقي، يمكننا ببساطة حذفه والوصول إلى نفس النتيجة (باستثناء… انظر أدناه)

حدود التطبيق هي بالفعل nginx نفسه، وليس Discourse أو rails. وبالتالي، فإن القرار بشأن أي الوكلاء البعيدين يجب الوثوق بهم يتم اتخاذه عند نقطة دخول التطبيق، وهو nginx. ثم يمكنه تمرير هذا القرار إلى Discourse.

بشكل افتراضي، يثق Rails فقط بالعناوين المحلية عند معالجة x-f-f، لذلك نفعل ذلك في مكان مختلف يمكننا التحكم فيه بسهولة.

في الواقع، يتبين أن Rails لا ينظر حتى إلى رأس x-real-ip… الرؤوس التي ينظر إليها هي:

  • forwarded
  • client-ip
  • x-forwarded-for

كيف انتهى الأمر بذلك منذ البداية

commit 21b562852885f883be43032e03c709241e8e6d4f (tag: v0.8.0)
Author: Robin Ward
Date:   Tue Feb 5 14:16:51 2013 -0500

    الإصدار الأولي من Discourse

diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf
new file mode 100644
index 00000000..62fabf4a
--- /dev/null
+++ b/config/nginx.sample.conf
…
+    proxy_set_header  X-Real-IP  $remote_addr;

سنضطر إلى القيام ببعض البحث، ولكن الإجابة الآن هي “إنه يعمل”. أعتقد أن هذا هو السبب في أننا وصلنا إلى هذه الحالة في المقام الأول.

يبدو أن هناك جيمًا ربما يستخدمه؟

إعجاب واحد (1)

أفهم، إذن الأمر يتعلق أكثر بما تفعله Rails أو الجواهر (gems) الأخرى مع الرؤوس، وأقل بكثير عما تفعله أكواد Discourse.

من المثير للاهتمام أن Rails لا تستخدم X-Real-IP، والتي ربما تكون أقل استخدامًا من X-Forwarded-For، لكنها بالتأكيد أكثر شهرة من Forwarded وClient-IP :thinking:.

ربما تكون X-Real-IP عفا عليها الزمن في إعدادات Nginx. Discourse تستخدمها جنبًا إلى جنب مع X-Forwarded-For في السجلات، إذا كنت أفسر ذلك بشكل صحيح؟ لم أتمكن من العثور على أي استخدام أو ذكر صريح آخر في الكود:

ما يلي بدا لي خاطئًا بطريقتين عندما رأيته أثناء تصحيح أخطاء تحديد المعدل المشترك (shared rate limiting) وتسجيل أخطاء حول عنوان IP غير صالح للعميل “unix:” بعد ترقيتنا لـ Discourse (نستخدم وكيل مسمار UNIX أمام الحاوية ونعتمد على X-Forwarded-For).

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;

لكنني أفهم أن “يعمل” لجعل $remote_addr هو نقطة الحقيقة الوحيدة بشكل موثوق، وreal_ip_header هو الطريقة القياسية للمديرين للتحكم في عنوان IP الوحيد الذي يحصل عليه Discourse/Rails. أرى أنه تمت إضافته بالفعل إلى Serve Discourse from a subfolder (path prefix) instead of a subdomain :+1:.