DiscourseConnect генерирует HTTP-редиректы даже при включённом force_https

Я запускаю Discourse за Traefik в кастомной конфигурации — выделение отдельной виртуальной машины под Discourse здесь невозможно.

В моём Discourse не включены шаблоны SSL/Let’sEncrypt, так как Traefik не пропускает обычные HTTP-запросы к контейнеру — он настроен на перенаправление HTTP-запросов на HTTPS.

У меня возникают проблемы с настройкой DiscourseConnect. Поскольку запрос Traefik -> nginx[Discourse] отправляется по незашифрованному HTTP (так как в nginx не настроен SSL), правило в /etc/nginx/conf.d/discourse.conf, которое пытается «сохранить протокол и должно находиться в контексте http», заставляет Discourse (Rails-приложение) получать обычный HTTP-запрос. В результате возвращается перенаправление по HTTP на /session/sso, даже если у меня включена опция force_https.

Думаю, это баг: независимо от конфигурации, при включённой опции force_https Discourse должен всегда генерировать HTTPS-URLы — а этого не происходит.

Полагаю, проблема в коде application_controller#redirect_to_login, но я пока не углублялся достаточно в исходный код Discourse, чтобы быть уверенным.

Можно ли это исправить в самом коде?

В качестве обходного пути я пытаюсь добавить правило, которое патчит discourse.conf в nginx, чтобы удалить это правило.

Мы должны добавить поддержку заголовков Forwarded, которые вы будете использовать для передачи NGINX информации об источнике запроса

Вы установили

proxy_set_header X-Forwarded-Proto https;?

Самым простым для меня было добавить дополнительный лейбл в app.yml в Discourse, чтобы указать Traefik добавлять заголовок X-Forwarded-Proto: https, однако nginx затем перезаписывал этот параметр своей собственной версией.

Здесь также играет роль конфигурация nginx в Discourse:

В этом месте Discourse пытается определить протокол исходя из исходного запроса (в моей конфигурации это всегда обычный текст, так как именно такой запрос отправляет Traefik). Затем, основываясь на этом, он несколько раз устанавливает заголовок X-Forwarded-Proto.

В итоге я отредактировал свой файл containers/app.yml, чтобы жестко задать эти заголовки значением https:

run:
  - exec: echo "Beginning of custom commands"
  ## Если вы хотите установить адрес электронной почты 'From' для вашей первой регистрации, раскомментируйте и измените:
  ## После получения первого письма о регистрации раскомментируйте строку обратно. Выполнять это нужно только один раз.
  # - exec: rails r "SiteSetting.notification_email='no-reply@forum.cabana.network'"
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /# attempt to preserve the proto, must be in http context\nmap \$http_x_forwarded_proto \$thescheme {\n  default \$scheme;\n  "~https\$" https;\n\}/
     to: |
       # force https scheme so Discourse generates HTTPs links and redirects (ie, `/login`)
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: "$thescheme"
     global: "true"
     to: "https"
  - exec: echo "End of custom commands"

Еще раз подчеркну: я считаю, что если существует настройка force_https, то само приложение Discourse (Rails) должно её учитывать, независимо от того, как обрабатывают это обратный прокси или другие компоненты.

Вот как мы это делаем на нашей платформе хостинга: у нас есть слой балансировщика нагрузки, который устанавливает X-Forwarded-Proto для последующего nginx+Discourse.

Нам не нужно никаких дополнительных ухищрений, чтобы это работало — не совсем понятно, что у вас идёт не так.

Это именно то, что происходит:

  def self.generate_sso(return_path = "/", secure_session:)
    sso = new(secure_session: secure_session)
    sso.nonce = SecureRandom.hex
    sso.register_nonce(return_path)
    sso.return_sso_url = Discourse.base_url + "/session/sso_login"
    sso
  end

а base_url определяется следующим образом:

  def self.base_protocol
    SiteSetting.force_https? ? "https" : "http"
  end

  def self.base_url_no_prefix
    "#{base_protocol}://#{current_hostname_with_port}"
  end

  def self.base_url
    base_url_no_prefix + base_path
  end