升级到 2.5.0.beta4 后出现 CSRF 登录错误

升级到 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 Doctor 显示:

========================================
Discourse 2.5.0.beta4
Discourse version at forum.netzwissen.de: Discourse 2.5.0.beta4
Discourse version at localhost: NOT FOUND
==================== DNS PROBLEM ====================
此服务器报告 NOT FOUND,但 forum.netzwissen.de 报告 Discourse 2.5.0.beta4。
这表明您存在 DNS 问题,或者某个中间代理是罪魁祸首。
如果您使用的是 Cloudflare 或 CDN,则可能是配置不当。

问题:服务器本身托管了多个具有不同 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)
在 23ms 内完成 200 OK(视图:20.4ms | ActiveRecord:0.0ms | 分配:4636)
于 2020-05-05 11:43:08 +0000 为 127.0.0.1 启动 GET "/latest.json?order=default"
由 ListController#latest 处理,格式为 JSON
  参数:{"order"=>"default"}
在 30ms 内完成 200 OK(视图:0.1ms | ActiveRecord:0.0ms | 分配:10224)
于 2020-05-05 11:43:08 +0000 为 127.0.0.1 启动 GET "/u/hp.json"
由 UsersController#get_honeypot_value 处理,格式为 JSON
在 3ms 内完成 200 OK(视图:0.1ms | ActiveRecord:0.0ms | 分配:1049)
于 2020-05-05 11:43:38 +0000 为 127.0.0.1 启动 GET "/session/csrf"
由 SessionController#csrf 处理,格式为 JSON
在 1ms 内完成 200 OK(视图:0.2ms | ActiveRecord:0.0ms | 分配:355)
于 2020-05-05 11:43:38 +0000 为 127.0.0.1 启动 POST "/auth/google_oauth2"
(google_oauth2) 检测到设置端点,正在运行。
(google_oauth2) 请求阶段已启动。
于 2020-05-05 11:43:38 +0000 为 127.0.0.1 启动 GET "/auth/failure?message=csrf_detected"
由 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)
在 19ms 内完成 200 OK(视图: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(或其配置),从而导致此问题(但这纯属假设 ;-))。

我尝试寻找在 nginx 中禁用 CSRF 的方法(https://github.com/gartnera/nginx_csrf_prevent),但认为可能需要重新编译 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 文件内容如下:

## Any custom commands to run after building
run:
  - exec: echo "Beginning of custom commands"
  ## If you want to set the 'From' email address for your first registration, uncomment and change:
  ## After getting the first signup email, re-comment the line. It only needs to run once.
  ## - 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

我用这个方法成功登录了,但请注意 patch 时 proxy_pass 命令的位置。它在 location @discourse { .. } 中有效。

好的,只是为了确认一下:我们讨论的是“容器动物园”内部的 nginx,对吗?因为在更新到 2.5.0beta4 之前,并不需要对此进行修补,它一直运行得非常顺畅。

是的,如果你所说的“zoo”是指运行 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 秒前               运行中 32 秒              127.0.0.1:884->80/tcp   app
43406c37f403        discourse/base:2.0.20200512-1735   "ruby -e 'require 'y…"   2 小时前             已创建


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" 指令在此处不允许,位于 /etc/nginx/conf.d/discourse.conf:10

我在 app.yml 中使用了以下 nginx 配置:

## 构建后运行的任何自定义命令
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.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 "结束自定义命令"

我认为你在 app.yml 中不需要那个 proxy_pass。我的配置片段如下:

  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

观察会发生什么情况…

Jays 的提示是正确的:我只需要移除 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 "自定义命令执行完毕"