sasa1977 / site_encrypt

Integrated certification via Let's encrypt for Elixir-powered sites
MIT License
462 stars 33 forks source link

Feature Request: More flexibility #26

Open axelson opened 3 years ago

axelson commented 3 years ago

First off thanks for this library! I like the idea of reducing the operational complexity of a deployment by moving more of the logic and moving parts into a single system (the BEAM in this case). In fact I'd like to explore that idea even further. To that end I'm working on a method to deploy phoenix applications to cloud hosting via nerves. The benefit to this is that the running system is a very stripped down Linux system along with easy deployment via SSH and nerves_firmware_ssh. Of course there's downsides as well (very low level of maturity mainly), but I think this is an interesting deployment target for simple hobby sites, and I am already running multiple sites off of one nerves installation running on Vultr (https://depviz.jasonaxelson.com/ and https://makeuplive.jasonaxelson.com/).

SiteEncrypt is a valuable part of that deployment because it allows for a very simple SSL setup (and without having to worry about cross-compiling or packaging certbot). However, this was a bit difficult to setup and I would like to make that process easier.

Since my goal is to run multiple Phoenix sites on one server there's two main options, run multiple Phoenix endpoints separately and use a reverse-proxy such as nginx to route the incoming requests to the correct ports, but installing and managing Nginx would be more needless complexity/dependencies, so instead I'm using master_proxy (https://github.com/jesseshieh/master_proxy/) which in a sense is a reverse proxy, but only for Phoenix and Plug. However, it's proven a little difficult to get MasterProxy and SiteEncrypt to play nicely together.

So I would like to make SiteEncrypt more flexible and more easily configurable to make this use case (along with others, such as #24) easier as well.

Problems I've run into (on the support-plug branch):

  1. I'm unable to start MasterProxy and SiteEncrypt.Phoenix as siblings in my supervision tree a. This is because I need the https configuration SiteEncrypt.https_keys/1 to properly configure MasterProxy, but SiteEncrypt.Registry is not populated until SiteEncrypt.Adapter.start_all_children/1 runs. Also this configuration was difficult to track down because it is configured at compile time via SiteEncrypt.Phoenix.__using__/1 b. Potential solution: This seems a bit difficult to resolve without relying on configuration in the Application env, but either SiteEncrypt could be moved to use Application env, or SiteEncrypt.https_keys/1 could be documented to not work until the SiteEncrypt.Adapter is running.
  2. The amount of changes needed to the end-user's Endpoint module makes me feel uncomfortable a. Granted, this is more of a stylistic question and as such it might not really count as an "issue" b. Potential solution: Split out the responsibilities by having the user create a new module that will implement the SiteEncrypt behaviour c. This also makes SiteEncrypt feel more magical, especially by hiding the call to the SiteEncrypt.AcmeChallenge plug. My preference is for more explicit, easy to understand code. d. As a side-note, I generally try to keep my Endpoint module as small as possible
  3. The reliance on macros that hard-code the calling __MODULE__ can result in surprising behavior. I didn't expect the SiteEncrypt.Phoenix.configure_https/1 call to suddenly fail because it was being called from a non-endpoint module. a. Potential solution: move away from some of the macros and pass the module in explicitly (thus increasing flexibility)
  4. There's also some problems on configuring MasterProxy but I'll document those in an issue on the MasterProxy repository later

I'm working on a PR to make some of these improvements (based on the support-plug branch), but I wanted to solicit your thoughts before going too much farther.

sasa1977 commented 3 years ago

I'll just address 1 for the moment, I need to think about about 2 & 3, but anyway, 1 is the core problem.

If you want to run multiple sites behind a single http proxy, you need to terminate ssl at the proxy. Therefore, SiteEncrypt.Phoenix will not work here. Instead, MasterProxy should be extended with the support for site_encrypt, because this is where ssl needs to be terminated. Since MasterProxy is based on Plug, #24 is a prerequisite for making this happen.

sasa1977 commented 3 years ago

One addition to the previous answer: I believe you can actually make this work for your case without needing to make any contributions if you roll your own proxy powered by Phoenix. This shouldn't take too much code. You can see an example of a small endpoint here. In such endpoint you need to add your own plug, something like plug :redirect with a private redirect/2 function, and then from redirect manually invoke FooEndpoint.call(conn, opts) or BarEndpoint.call(conn, opts), depending on conn.host.

In fact, I don't think you even need FooEndpoint or BarEndpoint. It should work with just a single endpoint and two routers, calling one or the other from your custom plug. In other words, in this setup there would be just one endpoint, and it would include all the standard plugs, except for the router, which would be replaced with the manual switch. If I were hosting multiple sites on the same port, this is the approach I'd personally try.

Either way, with such setup in place, you can then add site_encrypt to the proxy endpoint in a standard way, and you're all set.

axelson commented 3 years ago

Yeah, plug support via #24 would be a nice clean approach for this. But right now I'm using a Phoenix application as a proxy (one of my goals is for the sub-phoenix apps to be able to work standalone so I do want to keep the separate endpoints). It works except that I need to add a layer of supervision so that the SiteEncrypt registry is populated before MasterProxy starts up.

sasa1977 commented 3 years ago

If you want to keep separate endpoints, then the first part of my second comment is what I'd advise. This would basically be a manual implementation of the proxy, powered by Phoenix. At this point you control the code, and you can setup SiteEncrypt at the proxy endpoint.