nanobox icon indicating copy to clipboard operation
nanobox copied to clipboard

Support HTTPS on local (not only in dry-run)

Open lakano opened this issue 7 years ago • 16 comments
trafficstars

Hi !

To work on a PWA, we need HTTPS support in the local environment, not only in the dry-run part, is it possible to permit this please?

Regards,

lakano avatar Jan 05 '18 08:01 lakano

@lakano, this is a challenging problem. Nanobox is designed to terminate SSL in a router/load-balancer, which isn't provided for local dev apps. Your code is run directly out of a code container. Local code containers are designed to be ephemeral and transient. Even if a certificate was installed, it would get wiped every time the container gets replaced (which is often).

So there would need to be a certificate chain for each an every app you deploy locally. This is definitely something that could be done. I've added an item to our roadmap, so feel free to upvote: https://trello.com/c/EBJQFyxN/110-https-support-in-local-dev-apps-nanobox-run

We'll probably need to introduce a new command in the nanobox CLI for generating self-signed certs by domain and associating them with local apps.

sanderson avatar Jan 05 '18 14:01 sanderson

Do you known if there is a workaround, may be with a custom nginx.conf and a self-signed certificate? We have made some tests but this doesn't works.

        ssl on;
        ssl_certificate /data/etc/nginx/ssl/self-signed.crt;
        ssl_certificate_key /data/etc/nginx/ssl/self-signed.key;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        listen 8080 ssl;

lakano avatar Jan 05 '18 17:01 lakano

There is a workaround. @danhunsaker walks through it here: https://content.nanobox.io/testing-https-locally-with-nanobox/

sanderson avatar Jan 05 '18 20:01 sanderson

Thank you for the workaround @sanderson . I've created another workaround where you just need to install one time for all current and future nanobox projects. I share this workaround to help another nanobox users:

Firstly, we use the same URL as the real project but with a suffix on the TLD, for example if the project will be available on http(s)://my-project.com then:

$ nanobox dns add local my-project.com-local
$ nanobox dns add dry-run my-project.com-staging

Add in your /etc/hosts this line: 127.0.0.1 my-project.com-local-ssl

Install NginX / openSSL if not yet installed: $ sudo apt-get install nginx openssl

Create generic self signed certificate $ sudo openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout /etc/ssl/self-signed.key -out /etc/ssl/self-signed.crt

Then, add this file content in /etc/nginx/sites-available/nanobox:

proxy_hide_header Server;
proxy_hide_header X-Powered-By;
proxy_hide_header X-AspNet-Version;
proxy_set_header  Host              $http_host;
proxy_set_header  X-Forwarded-By    $server_addr;
proxy_set_header  X-Forwarded-For   $remote_addr;
proxy_set_header  X-Forwarded-Proto $scheme;
resolver localhost ipv6=off valid=5s;

server {
  listen 443 ssl http2;
  server_name ~^(?<nanobox>.*)-ssl$;

  ssl_certificate     /etc/ssl/self-signed.crt;
  ssl_certificate_key /etc/ssl/self-signed.key;

  location / {
    proxy_pass http://$nanobox$request_uri;
  }
}

Enable this configuration and restart NginX

$ sudo ln -s /etc/nginx/sites-available/nanobox /etc/nginx/sites-enabled/
$ sudo systemctl restart nginx

Now, to connect on local with SSL, rather than http://my-project.com-local you need to go on https://my-project.com-local-ssl and this should works as expected.

If you get an 50X error, you surely need to install a DNS server: $ sudo apt-get install dnsmasq and put this content in /etc/dnsmasq.d/nanobox:

listen-address=127.0.0.1
port=53
bind-interfaces
user=dnsmasq
group=dnsmasq
pid-file=/var/run/dnsmasq.pid
domain-needed
bogus-priv
dns-forward-max=150
cache-size=1000
no-negcache
neg-ttl=3600
resolv-file=/etc/resolv.conf
no-poll

and restart dnsmasq / nginx

$ sudo systemctl restart dnsmasq
$ sudo systemctl restart nginx

enjoy!

lakano avatar Jan 07 '18 00:01 lakano

I'm not sure how your single cert is actually configured to properly work on multiple domains... That's why the process I documented works per-app – each domain needs its own certificate in order to properly load.

danhunsaker avatar Jan 07 '18 10:01 danhunsaker

@danhunsaker For local development it's not a problem, you can use one cert for all :)

lakano avatar Jan 07 '18 10:01 lakano

That doesn't answer how...

danhunsaker avatar Jan 07 '18 10:01 danhunsaker

As you could see in the nginx.conf, we catch all domains with a suffix « -ssl » (eg: https://first-project.com-local-ssl , https://second-project.com-local-ssl) and we transfert to the proxy without this suffix (eg: first-project.com-local, second-project.com-local ). The NginX configuration use the same cert file for all theses projects ( /etc/ssl/self-signed.* ), this is enough to support SSL for each project, it's not required to create one for each project.

But if you really want one cert per project (BTW: I don't see any advantage), without the need to re-configure NginX each time, you can do: $ sudo openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout /etc/ssl/first-project.com-local.key -out /etc/ssl/first-project.com-local.crt $ sudo openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout /etc/ssl/second-project.com-local.key -out /etc/ssl/second-project.com-local.crt and just adjust the /etc/nginx/sites-available/nanobox like that:

  ssl_certificate     /etc/ssl/$nanobox.crt;
  ssl_certificate_key /etc/ssl/$nanobox.key;

But, again, this is not required IMHO.

lakano avatar Jan 07 '18 10:01 lakano

I see how Nginx is configured, yes. What I don't see is how the certificate itself is set up to actually match any domain. Browsers check that the domain you're connecting to actually matches the name in the certificate, and vice-versa.

danhunsaker avatar Jan 07 '18 10:01 danhunsaker

I suppose it's not really needed to match a specific domain for a self-signed cert. Get a specific domain name in a self-signed cert is useless because in all case your browser warn us and we need to accept (the first time) to by-pass the warning. Technically, I suppose the browser ask for the cert to nginx, then nginx give him one cert, then the browser just see it's a self-signed cert, and don't dig more to known if it's specific to this domain or not, and then show us a warning before continue.

lakano avatar Jan 07 '18 10:01 lakano

Ah, ok, so your short approach doesn't actually handle that at all. Something to note when considering which approach to take, since the original workaround actually generates trusted certs for each domain, since the CA certificate you generate in the first part would be actually registered as a trusted CA with your browsers. (Though the specifics of how to actually install the CA certificate this way are not included in the article itself, since there are so many browsers and platforms with so many variations in the process, and so many tutorials already available for that step.)

danhunsaker avatar Jan 07 '18 11:01 danhunsaker

Yep, our short approach don't require to handle that. When you run the openssl command, this will ask you some generic information ( country, company, email... ) but don't ask us to fill a specific domain. So, the cert is accepted for all project that use it ( after the classic self-signed warning ). So to develop a PWA for example, it's enough.

lakano avatar Jan 07 '18 11:01 lakano

Hi,

I've found again another solution to support HTTPS for « local » and we don't need anymore to install nginx. Our example is with php-engine but this should be possible to adapt it to other engines.

This solution is to use 2 custom nginx.conf, one for « deploy », listening on port 8080, and one for « local », listening on ports 8080 (bind to 80) and 443 with a self-signed cert. To permit that, we run the nginx server inside the nanobox container with the sudo command (to bind on port < 1024).

1/ Create a config/nginx/ path to store your config files mkdir -p config/nginx

2/ Create a generic self-signed cert nanobox run sudo openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout /app/config/nginx/self-signed.key -out /app/config/nginx/self-signed.crt

This will ask you some informations, but keep it generic, leave empty all fields (at least the FQDN/domain). Like that, you could simply copy theses files to next projects. The browsers will only warn you because it's a self-signed cert, and you simply need to bypass the warning.

3/ Add all your specific vhost configuration inside (eg: rewrites rules..) to a common file and save it inside /config/nginx/ For example, you could have a vhost for sub-domain www, another one for sub-domain api.

4/ Create your custom config/nginx-deploy.conf

    server {
      server_name ~^(www\.)*my-project\.com(-staging)*$;
      listen 8080;
      if ($http_x_forwarded_proto = 'http') {
        return 301 https://$host$request_uri;
      }
      include /data/etc/nginx/www.conf;
    }

BTW, our nanobox DNS are set to use the TLD suffix « -staging » for dry-run and « -local » for local, but you can adjust it to your preferences.

5/ Create your custom config/nginx-local.conf

    server {
      server_name ~^(www\.)*my-project\.com-local$;
      listen 8080;
      listen 443 default_server ssl http2;
      ssl_certificate /data/etc/nginx/self-signed.crt;
      ssl_certificate_key /data/etc/nginx/self-signed.key;
      include /data/etc/nginx/www.conf;
    }

6/ Set boxfile.yml to use the custom nginx.conf and certs, and to fix the php-server script

run.config:
  extra_steps:
    - cp config/nginx/* /data/etc/nginx/
    - cp config/nginx-local.conf /data/etc/nginx/nginx.conf
    - ./fix-php-server.sh

deploy.config:
  extra_steps:
    - cp config/nginx/* /data/etc/nginx/
    - cp config/nginx-deploy.conf /data/etc/nginx/nginx.conf

7/ Create the ./fix-php-server.sh

#!/bin/bash
sed -i 's/start-nginx ${document_root} &/sudo -E "PATH=$PATH" bash -c "start-nginx  ${document_root} \&" \&\& sleep 2s \&\& chmod 777 \/data\/var\/tmp\/php.sock /g' /data/bin/php-server

and make it executable: chmod +x ./fix-php-server.sh

8/ On build, the /data/bin/php-server script will be updated to start nginx as root, so you can continue to use the standard command: nanobox run php-server

Now, you can go in https://www.my-project.com-local and bypass the first time the self-signed cert warning. You can also access to the HTTP version on port 80.

Hope this help!

lakano avatar Jan 24 '18 17:01 lakano

This entire thread was super helpful! Thanks!

madasebrof avatar Jan 09 '19 15:01 madasebrof

@madasebrof BTW, in some cases, this could be usefull to use the serveo.net service (free and open-source, and could be self-hosted). This permit with a simple ssh command to get a subdomain on serveo.net and to get a valid TLS cert.

lakano avatar Jan 09 '19 15:01 lakano

@lakano Cool, thanks! The main thing this thread helped with was getting both Elixir and Node set up using different web apps on the same deploy.

madasebrof avatar Jan 10 '19 22:01 madasebrof