Insecure content from markdown-it-bundle when using https redux


(Lee_Ars) #1

Having the exact same issue as this locked thread, which annoyingly has no solution specified.

At some point in the past ~10 days, the preview window stopped working. The problem seems to be due to the markdown-it-bundle JS file failing to load due to a mixed content warning:

_ember_jquery-a8dcbd325e04410f036f2a791d66d8316c48c5387acdd914de99a5dd6afb3cd3.js:9248 
Mixed Content: The page at 'https://discourse.bigdinosaur.org/t/testing-messagebus-updating/1345/3'
was loaded over HTTPS, but requested an insecure script
'http://discourse.bigdinosaur.org/assets/markdown-it-bundle-95eab67cdea904c1edf468e5ed93ba3d0ed27da63e4428828e8a566506f74391.js'.
This request has been blocked; the content must be served over HTTPS.

OP posted here indicating that they fixed the issue by specifying proxy_set_header Host $http_host; in the reverse proxy in front of discourse; unfortunately, I already have that header set and have had it set for literally years.

I do not have force_https set to on, because that makes Discourse break horribly (no one can log in via any method and the login window responds to everything with “UNKNOWN ERROR”). I suspect this is because that setting makes a lot of assumptions about what’s in front of discourse and it doesn’t make the right assumptions for my setup, which is:

Internet <-https-> HAProxy SSL termination <-http-> Varnish cache <-http-> nginx master reverse proxy <-http-> Discourse

It’s apparently impossible to copy the full contents of chrome’s console without doing stupid shit, so here’s a screenshot of the error.

Anyone interested in taking a look shoudl be able to trigger it themselves—just go to discourse.bigdinosaur.org, create an account, and try to post something. You don’t even have to actually create a message—just invoking the composer will trigger the error.

There don’t appear to be any entries in Logster.

Is there a setting I can change, or something? Not really sure how to proceed to fix.


(Rafael dos Santos Silva) #2

Do you set X-Forwarded-Proto on your SSL termination proxy? I found that force_https without that fails bad.


(Lee_Ars) #4

I do indeed—I’m setting X-Forwarded-Proto at the SSL termination layer in the applicable haproxy backend statement:

backend tovarnish
        mode tcp
        http-request add-header X-Forwarded-Proto https
        server local 127.0.0.1:6081 send-proxy-v2

(Matt Palmer) #5

I have a sneaking suspicion this may be to do with force_https, and the solution is probably to get that working. It might be possible to fix whatever’s causing an explicit http://discourse.bigdinosaur.org (rather than //discourse.bigdinosaur.org) but the asset URL generation is a black box to me, so someone else will be needed for that adventure. On the other hand, I know proxies…

(Addendum: further posts made while I was writing up the below wall-of-text suggest you’ve at least tried to get this header forwarding working; I’ll leave it here for future posterity, and in case any of it’s useful. You’ll still want to verify whether the X-Forwarded-Proto header is, in fact, making it all the way to Discourse, because without explicit config many proxies will strip it as an untrusted header)

The reason why everything goes terribad when you turn on force_https is because Discourse doesn’t know the connection’s HTTPS. The SSL termination happened so long ago, that by the time the connection gets to Discourse, nobody remembers, and if Discourse doesn’t get told, “hey, this is actually a HTTPS connection!” it assumes HTTP and tries for force the matter.

Thankfully, the solution is fairly simple: just make sure each link in your proxy chain sets a X-Forwarded-Proto: https header if it knows (or was told) the connection was HTTPS. So in HAProxy, you’d do something like

 reqidel ^X-Forwarded-Proto:.*
 reqadd X-Forwarded-Proto:\ https if is_ssl

(You strip any incoming X-F-P header to stop anyone spoofing it)

The remaining links in your chain you’ll have to figure out for yourself, because I don’t have example configs to hand, but the logic is pretty similar in each case: if the request came from a trusted upstream proxy (by IP address), pass on any X-F-P header, otherwise strip it. Once you can see (via pcap or similar) that requests originally made by HTTPS have that header when passed into the Discourse container, you should be able to safely turn on force_https in the config.


(Lee_Ars) #6

Rest of the config, because why not at this point:

HAProxy goes to Varnish, which does the following stuff:

sub vcl_recv {

        ...

        # Cache only the static assets in Discourse's "assets" dir and pass everything else
        if (req.http.host ~"discourse.bigdinosaur.org") {
                if (!(req.url ~ "(^/uploads/|^/assets/|^/user_avatar/)" )) {
                        return (pass);
                }
        }

...
}

sub vcl_deliver {
        # HTTP headers for all sites
        set resp.http.X-Are-Dinosaurs-Awesome = "HELL YES";
        set resp.http.Server = "on fire";
        set resp.http.X-Hack = "don't hack me bro";
        set resp.http.Referrer-Policy = "strict-origin-when-cross-origin";
        set resp.http.Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload;";
        set resp.http.X-Content-Type-Options = "nosniff";
        set resp.http.X-XSS-Protection = "1; mode=block";
        set resp.http.X-Frame-Options = "DENY";
        set resp.http.Expect-CT = {"Expect-CT: max-age=0; report-uri="https://bigdino.report-uri.io/r/default/ct/reportOnly""};

...

        if (req.http.host ~ "discourse.bigdinosaur.org" ) {
                set resp.http.Public-Key-Pins = [long pin hashes here, redacted for space];
        }

}

…and, finally, the Nginx reverse proxy vhost config:

server {
	server_name discourse.bigdinosaur.org;
	listen 8881;
	listen 8882 http2;

	sendfile on;

	location / {
		access_log off;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header Host $http_host;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_redirect off;
		proxy_pass http://172.17.0.1:7998;
	}

(Lee_Ars) #7

Thanks, @mpalmer - I’ve chased this down before, though it has been like I said literally years, and the result was the (until recently) working configuration I had. But I’ll go back in and make sure Varnish and Nginx both aren’t stripping the header haproxy is adding.

Thanks!!


(Lee_Ars) #8

OK, rather than try to troubleshoot where in the stack the problem was happening, I wedged a quick fix in by just editing the nginx reverse proxy configuration to force that header right before it goes to discourse:

server {
	server_name discourse.bigdinosaur.org;
	listen 8881;
	listen 8882 http2;

	sendfile on;

	location / {
		access_log off;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header Host $http_host;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Proto https;
		proxy_redirect off;
		proxy_pass http://172.17.0.1:7998;
	}
}

After inelegantly stuffing in the x-forwaded-proto line and setting it manually to https, that script loads fine and the composer window functions! hooray!!

Now I need to go back in and fix it the right way and make sure that header is added properly and not stripped…but that sounds like a problem for Future Lee. Thanks for the shove in the right direction, @mpalmer!!


(Jeff Atwood) #9

If you are using https this must be set otherwise you are way into unsupported territory.

Also, I am curious why you need such a complex forwarding setup? Some of this pain feels self inflicted to me.


(Lee_Ars) #10

As with the decision to package Discourse inside of Docker, the apparent complexity is a result of a bunch of design decisions that flowed from the server’s requirements. For me, I’m running discourse as a tenant on a dedicated server that hosts 8 domains, including a Houston-area weather site that’s pulling ~20k visitors a day on a slow day. It’s a setup that has withstood more than one reddit hug from the front page of /r/all without a dip in response times.

The design goals were 1) HSTS for all hosted sites; 2) varnish cache servicing all hosted sites; 3) the flexibility to run a slew of web apps, from good old WordPress via PHP to Discourse on Docker.

I could have done the ol’ nginx-varnish-nginx sandwich, but that seemed silly; haproxy is way lighter and it’s perfectly happy doing fast SSL termination. Plus, haproxy can function as a TCP proxy and speaks proxy protocol, so i can forward from it to varnish and keep my ability to serve, cache, and proxy HTTP/2 traffic.

And even though Varnish doesn’t do much more for Discourse than cache the static stuff, it helps massively with the other hosts on the box, which are primarily static sites; anecdotally it does a far faster job of serving static assets than nginx does with its cache enabled.

But pretty much regardless of what server stack you want to adopt, caching + HSTS dictates a three-app pile of SSL termination, caching, and then the real web server (which also for me reverse-proxies to Discourse).

So, tl;dr, the stack is the result of the combined requirements of 8 sites running on a do-it-all dedicated server.

edit - also, force_https set to on now works, so I should be good :smiley:


(Jeff Atwood) #11

More computers = mo’ betta! I’ve taken to racking these things and I fucking love them

https://blog.codinghorror.com/the-scooter-computer/

Right now discourse.codinghorror.com is running on one of three colocated kaby lake i7-7500u / 16gb / 500gb SSD boxes at https://endoffice.com/ for about $15/month each (I got a special deal in exchange for promotion).

They are also blazing fast compared to rando cloud: How much is Discourse affected by a faster CPU?


#12

That’s what fixed it for me, but I was unable to add the solution since that other thread was closed.


(Fuse Takanobu) #13

I have experienced the same issue of the preview function.

Preview is reappeared after the following procedure.

./launcher enter your_discourse_container_name

edit /etc/nginx/conf.d/discourse.conf

change

proxy_set_header X-Forwarded-Proto $thescheme

to

proxy_set_header X-Forwarded-Proto https;

in the following position at the tail of the file;

location @discourse {
  limit_conn connperip 20;
  limit_req zone=flood burst=12 nodelay;
  limit_req zone=bot burst=100 nodelay;
    add_header Referrer-Policy 'no-referrer-when-downgrade';
    proxy_set_header Host $http_host;
    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;
    proxy_pass http://discourse;
  }

(Ian C) #14

This worked for me, thanks!

I was getting an insecure script warning when creating a new topic. I have ssl and have force_ssl enabled.


(Fuse Takanobu) #15

Additional comment

1.how to access discourse.conf

$cd /var/discourse

in /var/discource you will find files like xxx.yml, probably discourse container named app.yml

so, you can access discourse container (Note: not including .yml)

$sudo ./launcher enter app

After that you are in discourse container.

Discourse container only includes “vi” text editor, so you should use it for editing a file.
how to use “vi” editor, you check at other sites finding by google search.
or in discourse container, install another text editor(I don’t try it.).

#vi /etc/nginx/conf.d/discourse.conf

After saving, reload the nginx config file. (no need to reboot)

#service nginx reload

exit container

#exit

You might check also the following,

2. If disabled login, preview and so on after setting “force_https” active (please don’t active “force_https” again).

Again repeats above steps, access into the discourse container. and implements the rails command.

#rails c

```
[1] pry(main)> SiteSetting.enable_local_logins = true
=> true
[2] pry(main)> SiteSetting.force_https = false
=> false
[3] pry(main)> exit
```

(Daniel Nevoigt) #16

Thank you very much, for me that worked to get the preview working again, additionally the force https in admin too.

Edit: After creating a subdomain in Plesk, gained funtionallity stopped again :slight_smile: