DiscourseConnect generates HTTP redirects even with force_https on

I’m running Discourse behind Traefik in a custom setup - giving Discourse its own VM is not an option here.

My Discourse doesn’t have SSL/Let’sEncrypt templates enabled, since Traefik won’t let plain HTTP requests reach the container - it’s set to redirect HTTP requests to HTTPs.

I’m having issues setting up DiscourseConnect, because, since the Traefik -> nginx[Discourse] request is sent over plain-text HTTP (because nginx doesn’t have SSL set up), the rule in /etc/nginx/conf.d/discourse.conf that tries to preserve the proto, must be in http context makes Discourse (the Rails app) to receive a plain-text HTTP request, thus returning a plain-text HTTP redirect to /session/sso - even if I have force_https enabled.

I think that’s the bug: regardless of my setup, with force_https enabled, Discourse should always generate HTTPs URLs - which it’s not doing.

I think the offending code is application_controller#redirect_to_login, but I haven’t dug that much into Discourse source code to be sure.

Is this solvable in the code itself?

As a workaround, I’m trying to add a rule patching the nginx’s discourse.conf to remove that rule.

We should have some support for forwarded for headers you would use them to signal to NGINX what the origin is

Did you set

proxy_set_header X-Forwarded-Proto https;

What was easiest for me was to set an extra label in Discourse’s app.yml to tell my Traefik to add an X-Forwarded-Proto: https header, but then nginx would override that parameter with it’s own version.

And Discourse’s nginx config plays a role here:

There Discourse tries to guess the protocol from the original request (which, in my setup, is always plain-text since that’s what Traefik sends). And then uses that to set the X-Forwarded-Proto multiple times.

In the end, I edited my containers/app.yml to hard-code those headers to https:

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='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"

Once again, I think if there’s a force_https setting, Discourse-the-rails-app should honor it, regardless of what the reverse proxy or other parties handle or not.

This is how we do it on our hosting platform; we have a load balancer layer that sets X-Forwarded-Proto for the downstream nginx+Discourse to consume.

We don’t need any additional shenanigans to make it work - I’m not sure what’s going wrong for you here.

This is indeed what happens:

  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

and base_url comes from:

  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
1 Like