خطأ تسجيل الدخول CSRF بعد الترقية إلى 2.5.0.beta4

بعد الترقية إلى الإصدار 2.5.0.beta4، ألاحظ أخطاء CSRF في سجل الإنتاج:

Processing by SessionController#csrf as JSON
Completed 200 OK in 1ms (Views: 0.1ms | ActiveRecord: 0.0ms | Allocations: 351)
Started POST "/session" for 127.0.0.1 at 2020-05-05 09:25:17 +0000
Processing by SessionController#create as */*
  Parameters: {"login"=>"admin", "password"=>"[FILTERED]", "second_factor_method"=>"1", "timezone"=>"Europe/Berlin"}
Can't verify CSRF token authenticity.
  Rendering text template
  Rendered text template (Duration: 0.0ms | Allocations: 1)
Filter chain halted as :verify_authenticity_token rendered or redirected
Completed 403 Forbidden in 2ms (Views: 0.7ms | Allocations: 1100)

ويُظهر أداة فحص Discourse ما يلي:

========================================
Discourse 2.5.0.beta4
Discourse version at forum.netzwissen.de: Discourse 2.5.0.beta4
Discourse version at localhost: NOT FOUND
==================== DNS PROBLEM ====================
This server reports NOT FOUND, but forum.netzwissen.de reports Discourse 2.5.0.beta4 .
This suggests that you have a DNS problem or that an intermediate proxy is to blame.
If you are using Cloudflare, or a CDN, it may be improperly configured.

السؤال: يستضيف الخادم نفسه عدة خدمات بأسماء DNS مختلفة. أمام Discourse لدينا خادم haproxy للتعامل مع إنهاء SSL. لا أفهم رسالة الخطأ

“Discourse version at localhost: NOT FOUND”

هل من الممكن أن يكون خطأ CSRF مرتبطًا برسالة الخطأ هذه؟

لا يدّعي Discourse-doctor قدرته على تشخيص إعداد معقد مثل إعدادك. فهو يقارن فقط ما إذا كان المضيف المحلي و DNS يعيدان نفس القيمة. بالنسبة لإعدادك، من المتوقع أن يكونا مختلفين.

ومع ذلك، ليس لدي أي تلميحات حول مشكلتك الفعلية. عذراً.

مرحبًا،
حسنًا، جربت باستخدام حساب آخر وحصلت على رسالة الخطأ نفسها، ويبدو أن تسجيلات الدخول محظورة تمامًا الآن، وقد يكون خطأ CSRF هو السبب الجذري…

هل لديك أي أفكار لمزيد من التنقيح؟ ملف app.yml الخاص بي قياسي تمامًا باستثناء:

expose:
  - "127.0.0.1:884:80"   # http

يتم توجيه الطلبات الواردة من خادم haproxy إلى حاوية discourse على المنفذ 884. يتم تنفيذ ssl/https بواسطة haproxy.

عند تسجيل مستخدم جديد عبر oauth2 (Google)، أحصل أيضًا على خطأ CSRF:

 تم عرض common/_discourse_stylesheet.html.erb (المدة: 0.4ms | التخصيصات: 206)
  تم عرض application/_header.html.erb (المدة: 0.3ms | التخصيصات: 142)
اكتمل الطلب بـ 200 OK خلال 23ms (العرض: 20.4ms | ActiveRecord: 0.0ms | التخصيصات: 4636)
تم بدء طلب GET "/latest.json?order=default" من 127.0.0.1 في 2020-05-05 11:43:08 +0000
المعالجة بواسطة ListController#latest بصيغة JSON
  المعاملات: {"order"=>"default"}
اكتمل الطلب بـ 200 OK خلال 30ms (العرض: 0.1ms | ActiveRecord: 0.0ms | التخصيصات: 10224)
تم بدء طلب GET "/u/hp.json" من 127.0.0.1 في 2020-05-05 11:43:08 +0000
المعالجة بواسطة UsersController#get_honeypot_value بصيغة JSON
اكتمل الطلب بـ 200 OK خلال 3ms (العرض: 0.1ms | ActiveRecord: 0.0ms | التخصيصات: 1049)
تم بدء طلب GET "/session/csrf" من 127.0.0.1 في 2020-05-05 11:43:38 +0000
المعالجة بواسطة SessionController#csrf بصيغة JSON
اكتمل الطلب بـ 200 OK خلال 1ms (العرض: 0.2ms | ActiveRecord: 0.0ms | التخصيصات: 355)
تم بدء طلب POST "/auth/google_oauth2" من 127.0.0.1 في 2020-05-05 11:43:38 +0000
(google_oauth2) تم اكتشاف نقطة نهاية الإعداد، ويتم التشغيل الآن.
(google_oauth2) تم بدء مرحلة الطلب.
تم بدء طلب GET "/auth/failure?message=csrf_detected" من 127.0.0.1 في 2020-05-05 11:43:38 +0000
المعالجة بواسطة Users::OmniauthCallbacksController#failure بصيغة HTML
  المعاملات: {"message"=>"csrf_detected"}
يتم عرض users/omniauth_callbacks/failure.html.erb ضمن layouts/no_ember
  تم عرض users/omniauth_callbacks/failure.html.erb ضمن layouts/no_ember (المدة: 0.1ms | التخصيصات: 20)
  تم عرض layouts/_head.html.erb (المدة: 11.7ms | التخصيصات: 3551)
  تم عرض common/_discourse_stylesheet.html.erb (المدة: 0.5ms | التخصيصات: 213)
  تم عرض application/_header.html.erb (المدة: 0.9ms | التخصيصات: 555)
اكتمل الطلب بـ 200 OK خلال 19ms (العرض: 16.4ms | ActiveRecord: 0.0ms | التخصيصات: 7652)

لقد واجهتُ نفس المشكلة تمامًا بعد الترقية إلى الإصدار 2.5.0.beta4 (Moved site behind proxy, favicon and header not using https anymore - #7 by rossierd).

هل تمكّنت من حل المشكلة؟ أتخيّل أن الترقية جاءت مع إصدار جديد من nginx (أو إعداداته) مما أدّى إلى هذه المشكلة (ولكن هذا افتراضي بحت ;-))
حاولتُ العثور على طريقة لتعطيل CSRF في nginx (GitHub - gartnera/nginx_csrf_prevent: Prevent CSRF with nginx · GitHub)، لكنني أعتقد أن nginx يجب إعادة تجميعه، ولا أعرف ما إذا كنا بحاجة إلى بيئة التطوير الكاملة لـ Discourse للقيام بذلك.

للأسف، المشكلة غير محلولة حتى الآن. يفشل تسجيل الدخول بـ “خطأ غير معروف”، وفي كل محاولة أرى ما يلي في السجل:

root@develd:/var/discourse# tail -f /var/log/discourse-rails/production.log
Processing by SessionController#csrf as JSON
Completed 200 OK in 1ms (Views: 0.1ms | Allocations: 351)
Started POST "/session" for 127.0.0.1 at 2020-06-07 06:58:19 +0000
Processing by SessionController#create as */*
  Parameters: {"login"="admin", "password"="[FILTERED]", "second_factor_method"="1", "timezone"="Europe/Berlin"}
Can't verify CSRF token authenticity.
  Rendering text template
  Rendered text template (Duration: 0.0ms | Allocations: 1)
Filter chain halted as :verify_authenticity_token rendered or redirected
Completed 403 Forbidden in 2ms (Views: 0.8ms | ActiveRecord: 0.0ms | Allocations: 1100)
Started GET "/session/csrf" for 127.0.0.1 at 2020-06-07 07:00:45 +0000
Processing by SessionController#csrf as JSON
Completed 200 OK in 1ms (Views: 0.2ms | Allocations: 351)
Started POST "/session" for 127.0.0.1 at 2020-06-07 07:00:45 +0000
Processing by SessionController#create as */*
  Parameters: {"login"="admin", "password"="[FILTERED]", "second_factor_method"="1", "timezone"="Europe/Berlin"}
Can't verify CSRF token authenticity.
  Rendering text template
  Rendered text template (Duration: 0.0ms | Allocations: 1)
Filter chain halted as :verify_authenticity_token rendered or redirected
Completed 403 Forbidden in 2ms (Views: 0.9ms | Allocations: 1100)

يحتوي ملف app.yml على:

## أي أوامر مخصصة للتشغيل بعد البناء
run:
  - exec: echo "Beginning of custom commands"
  ## إذا كنت تريد تعيين عنوان البريد الإلكتروني 'من' للتسجيل الأول، قم بإزالة التعليق وتغيير:
  ## بعد الحصول على أول بريد إلكتروني للتسجيل، أعد إضافة التعليق للسطر. فهو يحتاج للتشغيل مرة واحدة فقط.
  ## - exec: rails r "SiteSetting.notification_email='noreply-discourse@netzwissen.de'"
  - replace:
      filename: /etc/nginx/conf.d/discourse.conf
      from: "types {"
      to: |
        set_real_ip_from 127.0.0.0/24;
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;
        types {
  - exec: echo "End of custom commands"

كما تم التوصية بذلك في https://meta.discourse.org/t/haproxy-and-discourse-ip-issue/92387

جرب هذا: Moved site behind proxy, favicon and header not using https anymore - #12 by rossierd

تم تسجيل الدخول بنجاح مع ذلك، لكن انتبه إلى المكان الذي تقوم فيه بتعديل أمر proxy_pass. إنه يعمل فقط داخل location @discourse { .. }

حسناً، فقط للتوضيح: هل نتحدث عن nginx داخل “حديقة الحاويات”؟ لأنّه، قبل التحديث إلى الإصدار 2.5.0beta4، لم يكن هناك حاجة لتطبيق أيّ تصحيحات عليه، بل كان يعمل بسلاسة تامة.

نعم، إذا كنت تقصد بالحيوان الزوّاري الحاوية التي تشغّل Discourse. يمكنك الدخول إلى الحاوية باستخدام الأمر “rails enter app”.

قمنا بإجراء المزيد من التصحيح هنا: تبدأ المشكلة في خادم nginx داخل الحاوية. فهو لا يفهم توجيه proxy_pass ويبدو أنه ينهار، لكن لماذا:

root@develd:/var/discourse# docker ps -a
CONTAINER ID        IMAGE                              COMMAND                  CREATED             STATUS                     PORTS                   NAMES
f8f6103a036d        local_discourse/app                "/sbin/boot"             35 seconds ago      Up 32 seconds              127.0.0.1:884->80/tcp   app
43406c37f403        discourse/base:2.0.20200512-1735   "ruby -e 'require 'y…"   2 hours ago         Created


docker exec -it f8f6103a036d /bin/bash

root@forum:/# tail -f /var/log/nginx/error.log
2020/06/08 19:05:03 [emerg] 288#288: "proxy_pass" directive is not allowed here in /etc/nginx/conf.d/discourse.conf:10

أنا أستخدم هذا التكوين لـ nginx في ملف app.yml:

## أي أوامر مخصصة للتشغيل بعد البناء
run:
  - exec: echo "Beginning of custom commands"
  ## إذا كنت تريد تعيين عنوان البريد الإلكتروني 'From' لأول تسجيل، قم بإزالة التعليق وتغييره:
  ## بعد الحصول على بريد التسجيل الأول، أعد وضع التعليق على السطر. فهو يحتاج إلى التشغيل مرة واحدة فقط.
  ## - exec: rails r "SiteSetting.notification_email='noreply-discourse@netzwissen.de'"
  - replace:
      filename: /etc/nginx/conf.d/discourse.conf
      from: "types {"
      to: |
        set_real_ip_from 127.0.0.0/24;
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;
        proxy_set_header Host $http_host;
        proxy_set_header X-Request-Start "t=${msec}";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https; # $thescheme; <-- ما قمت بتعديله
        proxy_pass http://discourse;
        types {
  - exec: echo "End of custom commands"

أعتقد أنك لا تحتاج إلى proxy_pass هناك في ملف app.yml. تبدو قسّمي (stanza) الخاص بي على هذا النحو:

  after_bundle_exec:
    # هذا هو السحر لنقل أرقام عناوين IP إلى Discourse
    # انظر https://meta.discourse.org/t/last-ip-address-and-action-dispatch-trusted-proxies/50098/3?u=pfaffman
    - replace:
        filename: /etc/nginx/conf.d/discourse.conf
        from: "types {"
        to: |
          set_real_ip_from 192.168.1.0/24;
          set_real_ip_from 172.18.0.0/24;
          set_real_ip_from 172.17.0.0/24;
          real_ip_recursive on;
          real_ip_header X-Forwarded-For;
          types {

لكن قد يكون كودي من فترة ما قبل الانتقال إلى Debian في الحاويات.

يمكنك تجربة تعديل ذلك الملف مباشرة داخل الحاوية وإعادة تشغيل nginx.

للتأكد من ذلك، داخل الحاوية نفسها، قم بتعديل ملف /etc/nginx/conf.d/discourse.conf وقم بتطبيق التصحيح المذكور أعلاه.
بعد ذلك، أعد تشغيل nginx بالطريقة التالية:

$ service nginx stop
$ service nginx start

ثم راقب ما يحدث…

كانت تلميح جيز صحيحًا: لقد قمت فقط بإزالة proxy_pass وعمل الأمر، وإليك التكوين النهائي في ملف app.yml:

## أي أوامر مخصصة للتشغيل بعد البناء
run:
  - exec: echo "بداية الأوامر المخصصة"
  ## إذا كنت ترغب في تعيين عنوان البريد الإلكتروني 'من' للتسجيل الأول، قم بإزالة التعليق وتغييره:
  ## بعد استلام أول بريد تسجيل، أعد وضع التعليق على السطر. فهو يحتاج إلى التشغيل مرة واحدة فقط.
  ## - exec: rails r "SiteSetting.notification_email='noreply-discourse@netzwissen.de'"
  - replace:
      filename: /etc/nginx/conf.d/discourse.conf
      from: "types {"
      to: |
        set_real_ip_from 127.0.0.1/24;
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;
        proxy_set_header Host $http_host;
        proxy_set_header X-Request-Start "t=${msec}";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https; # $thescheme; <-- ما قمت بتعديله
        types {
  - exec: echo "نهاية الأوامر المخصصة"