0
votes

I am using Cloudflare's flexible SSL to secure my website, and it works fine when running my vue.js frontend. Essentially what it is doing is sharing the SSL cert with 50 random customers, and it does so by piping my DNS through their edge network. Basically, it did the thing I needed and was fine, but now that I am trying to tie it to a phoenix/elixir backend it is breaking.

The problem is that you can't make an http request from inside an ssl page, because you'll get this error:

Blocked loading mixed active content

This makes sense - if it's ssl on load, it needs to be ssl all the way down. So now I need to add SSL to elixir.

This site (https://elixirforum.com/t/run-phoenix-https-behind-cloudflare-without-key-keyfile/12660/2) seemed to have the solution! Their answer was:

configs = Keyword.put(config, :http, [:inet6, port: "80"])
 |> Keyword.put(:url, [scheme: "https", host: hostname, port: "443"])

So I made my config like this:

config :albatross, AlbatrossWeb.Endpoint,
  http: [:inet6, port: "4000"],
  url: [scheme: "https", host: "my.website", port: "443"],
  secret_key_base: "SUPERSECRET",
  render_errors: [view: AlbatrossWeb.ErrorView, accepts: ~w(html json)],
  pubsub: [name: Albatross.PubSub,
           adapter: Phoenix.PubSub.PG2]

That only allows me to get to http:etc! So I've also tried this:

config :albatross, AlbatrossWeb.Endpoint,
  http: [:inet6, port: "4000"],
  https: [ :inet6, port: "4443"],
  url: [scheme: "https", host: "my.website", port: "443"],
  secret_key_base: "SUPERSECRET",
  render_errors: [view: AlbatrossWeb.ErrorView, accepts: ~w(html json)],
  pubsub: [name: Albatross.PubSub,
           adapter: Phoenix.PubSub.PG2]

Which doesn't work of course because there's no PEM files. Since I'm only using elixir as an API (and not a DNS) I can't use solutions like this (http://51percent.tech/blog/uncategorized/serving-phoenix-apps-ssl-and-lets-encrypt/), because letsencrypt does not allow IP address only auth (https://www.digitalocean.com/community/questions/ssl-for-ip-address).

So at this point I'm very confused. Does anyone have any advice?


EDIT:


Someone mentioned that you can go on to cloudflare and generate TLS certs by going to crypto>Origin Certificates>Create Certificate. I did that, downloaded the files, saved them in my project and ran this:

config :albatross, AlbatrossWeb.Endpoint,
  http: [:inet6, port: "4000"],
  https: [ port: "4443", 
           keyfile: "priv/ssl/cloudflare/private.key", 
           certfile: "priv/ssl/cloudflare/public.pem"],
  url: [scheme: "https", host: "website.me", port: "443"],
  secret_key_base: "SUPERSECRET",
  render_errors: [view: AlbatrossWeb.ErrorView, accepts: ~w(html json)],
  pubsub: [name: Albatross.PubSub,
           adapter: Phoenix.PubSub.PG2]

So what are the results of all the possible ways to query the backend?

Well I'm running docker-compose so https://backendservice:4443 is what I query from the frontend. That gives me -

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://backendservice:4443/getComments?postnum=6. (Reason: CORS request did not succeed).[Learn More]
value of error :  Error: "Network Error"
    exports https://mywebsi.te/js/chunk-vendors.4345f11a.js:49:15423
    onerror https://mywebsi.te/js/chunk-vendors.4345f11a.js:65:538540
actions.js:61:12 

So that clearly doesn't work.

I can go to http://my.ip.address:4000, but I cannot go to https://my.ip.address:4443.

As far as I can tell cloudflare TLS certificates do not work.

Or, more likely, I am doing something stupid in writing the elixir config.


FURTHER CLARIFICATION:


Yes, there is a CORS header error above. However please note that it is only firing for https request and NOT http requests. Why this is happening is very confusing. I have a cors plugin for elixir in the entrypoint of my application that is currently allowing * incoming requests. This is it - it should be pretty straight forward:

plug CORSPlug, origin: "*"

More information can be found here (https://github.com/mschae/cors_plug).

3
I could be wrong, but in a setup like this doesn't cloudflare terminate SSL on their end and just send you an HTTP request? Which means you wouldn't need to setup anything within Phoenix. You probably just need to make sure your frontend is sending an HTTPS request instead of an HTTP request?Justin Wood
No, that's not correct. If I don't do anything special my backend resolves at MY.IP.ADDRESS/route. If I https request there is nothing there, and if I http request I get the Blocked loading mixed active content error.Peter Weyand

3 Answers

1
votes

I seem to have been able to make my website work with the following configuration:

config :my_app, MyAppWeb.Endpoint,
  force_ssl: [hsts: true],
  url: [host: "my.website", port: 443],
  http: [:inet6, port: 4000],
  https: [
    :inet6,
    port: 8443,
    cipher_suite: :strong,
    keyfile: "private.key.pem",
    certfile: "public.cert.pem",
    cacertfile: "intermediate.cert.pem"
  ],
  cache_static_manifest: "priv/static/cache_manifest.json"

where private.key.pem and public.cert.pem are the origin certificate downloaded from Cloudflare.

(Note that the origin certificate you can download from Cloudflare is only useful for encrypting the connection between your website and Cloudflare.)

I also had to add routing rules via iptables.

iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 4000
iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 8443

This configuration also worked before for a setup using letsencrypt certificates.

I'm not sure if the cacertfile: "intermediate.cert.pem" part is needed. I obtained it from "Step 4" of the Cloudflare documentation.

0
votes

I never used Cloudflare but I know that phoenix can pretend it is serving https content if x-forwarded-proto is set in request headers. Your configuration is OK (without https part, you don't need it).

Try to add force_ssl option in endpoint configuration. This will instruct to Plug.SSL to force SSL and redirect any http request to https.

config :albatross, AlbatrossWeb.Endpoint,
   http: [:inet6, port: "4000"],
   url: [scheme: "https", host: "my.website", port: "443"],
   force_ssl: [rewrite_on: [:x_forwarded_proto], host: nil],
   secret_key_base: "SUPERSECRET",
   render_errors: [view: AlbatrossWeb.ErrorView, accepts: ~w(html json)],
   pubsub: [name: Albatross.PubSub,
           adapter: Phoenix.PubSub.PG2]
0
votes

In my experience with Cloudflare and web backends (and without seeing the exact request that is causing the issue), this is usually caused by hard-coding a CSS/JS dependency with http:// or by making an AJAX request with http:// hard-coded. If the back end you are requesting with http:// doesn't automatically redirect to https://, you will get the error you're seeing.

If you're using Cloudflare's "One-Click SSL", this is how requests are being made to your server:

[ Client Browser ] <-- HTTPS --> [ Cloudflare ] <-- HTTP --> [ Your Server ]

Since the communication between Cloudflare and your server is all over http://, you should not need to change any Phoenix configuration at all, but there are 2 potential sources of error:

  1. Automatic redirection to https:// is not being done by Cloudflare (or Cloudflare is not serving your content)
  2. You have some CSS/JS dependencies or Ajax requests that are hard-coded to http:// and the server responding to the requests is not automatically redirecting to https://.

Troubleshooting point 1: According to Cloudflare's docs for their "One-Click SSL", they should automatically redirect http:// requests to https:// (although this might be a setting that you need to change). If a request comes to your domain over http://, Cloudflare should automatically redirect to https://. You can check whether this is happening by requesting a page from your backend in your browser with http:// explicitly included at the front of the URL. If you are not redirected to https://, it could mean that a) Cloudflare is not actually in between your browser and the backend or b) that Cloudflare is not automatically redirecting to https://. In either case, you have some hints about your next step in solving the problem.

Troubleshooting point 2: You can check this by using the "Network" tab in the developer tools in your browser, or by manually going through your website code looking for CSS/JS dependencies or Ajax requests hard-coded with http://. In the developer tools, you can check for exactly which request(s) are causing the problem (the line should be red and have an error about mixed content). From there you can check where this request is coming from in your code. When searching through your code for hard-coded http:// requests, most of the time you can simply replace it with https:// directly, but you should double-check the URL in your browser to make sure that the URL is actually available over https://. If the CSS/JS dependency or Ajax endpoint is not actually available over https:// then you will need to remove/replace it.

Hope this helps.