Archive for July, 2024

HTTPS for local development

How to local dev

Local developer environments can take many forms but for non-trivial web applications I’m still fond of Vagrant to spin up a VM that is “close enough” to production environments. Typically, I will create a Vagrant box and map a hostname to the IP of the box via an entry in the host system’s hosts file (I use vagrant-hostmap for this, as DHCP is used to avoid conflicts and that means the IP address change often).

Does HTTPS matter for local dev?

Probably not. I went down this particular rabbit hole as I was doing some experiments with server-side events and noticed there’s a limitation on open connection when not using HTTP/2.

So, I thought it would be a good idea to get HTTP/2 working in my local dev environment, which was using Apache and PHP. This is fairly straightforward to setup on Apache. However, the browser (Chrome) was not happy with. Currently, browsers only support HTTP/2 over TLS (h2) and there’s is no support for HTTP/2 Cleartext (h2c). There some good reasons for this.. but many of these reasons are addressing concerns on the public web, for other use-cases (e.g. local dev) there’s additional complexity to support h2 with minimal benefits.

Scripting

The code in the sections below is Bash for a provisioning script (bootstrap.sh), intended to run when the Vagrant is provisioned. The code is such that the environment of the box is reproducible (after destroying and re-creating) and there is no need to re-adjust configuration on the host machine (comes into play when dealing with certificates).

The Vagrantfile looks something like this:

$startScript = <<START_SCRIPT sudo service apache2 restart START_SCRIPT Vagrant.configure("2") do |config| config.vm.box = "ubuntu/mantic64" config.vm.provision :shell, path: "localdev/bootstrap.sh" config.vm.provision "shell", inline: $startScript, run: "always" config.vm.network "private_network", type: "dhcp" end

… with the bootstrap script and related assets (e.g. Apache conf files) within the localdev folder.

Supporting h2: disable prefork

First, disable the prefork module, it doesn’t play well with HTTP/2:

sudo a2dismod mpm_prefork

In prefork, mod_http2 will only process one request at at time per connection. But clients, such as browsers, will send many requests at the same time. If one of these takes long to process (or is a long polling one), the other requests will stall.

Supporting h2: enable HTTP/2 modules and configuration

Enable modules and configuration for HTTP/2:

sudo a2enmod mpm_event sudo a2enmod http2 sudo cp /vagrant/localdev/http2.conf /etc/apache2/conf-available/http2.conf sudo a2enconf http2

The http2.conf file contains the following:

<IfModule http2_module> Protocols h2 h2c http/1.1 H2Direct on </IfModule>

Required modules and configuration are now enabled. For a given VirtualHost definition, you can add Protocols h2 h2c http/1.1.

e.g.:

<VirtualHost *:443> ServerAdmin webmaster@localhost DocumentRoot /var/www/html Protocols h2 h2c http/1.1 ...

Supporting h2: minica

For HTTPS TLS certificate generation, minica is awesome and simple to use. However, it is only distributed as source, so you’ll need to install git to checkout the repo and golang to compile the source.

sudo apt-get -y install git sudo apt-get -y install golang-go sudo mkdir /certs sudo mkdir /tools cd /tools git clone https://github.com/jsha/minica.git cd /tools/minica go env -w GO111MODULE=auto go build -buildvcs=false

Here a /tools folder is created, the minica repo is cloned within, and the source is compiled.

A /certs folder is also created as a location for generated certificates.

Supporting h2: generate root CA certificate

Once compiled, run minica to generate a root CA certificate (minica.pem) and key (minica-key.pem), which can be installed on the host to avoid browsers showing that the certificate is invalid or untrusted. This shouldn’t be done in the Vagrant box provisioning script, as you’ll get new root certificates every time the box is provisioned. Instead, SSH into the box, run, and copy the files to a secure location.. or run minica somewhere else (e.g. host machine or another machine). ./minica --domains "test.localdev.com"

minica requires a domain argument but the need is really for the minica.pem and minica-key.pem files at this point, not the site certificate. Generation of the site certificate is something that should be done within the provisioning script.

Supporting h2: generate site certificate

Within the provisioning script:

SITE_DOMAIN="localdev.com" WILDCARD_CERT="*.localdev.com" CERT_FOLDER="_.$SITE_DOMAIN"; CA_CERT="/vagrant/localdev/secrets/minica.pem" CA_KEY="/vagrant/localdev/secrets/minica-key.pem" ./minica --ca-cert "$CA_CERT" --ca-key "$CA_KEY" --domains "$WILDCARD_CERT" cp -R "/tools/minica/$CERT_FOLDER" "/certs/$CERT_FOLDER"

There’s a 2 things to note here:

  • To avoid having to generate new certificates for different subdomains, a wildcard cert is generated (*.localdev.com)
  • An existing root CA certificate, and key, is expected

Supporting h2: use site certificate

Update the VirtualHost definition to reference the site certificate and key:

<VirtualHost *:443> ServerAdmin webmaster@localhost DocumentRoot /var/www/html Protocols h2 h2c http/1.1 SSLEngine on SSLCertificateFile /certs/_.localdev.com/cert.pem SSLCertificateKeyFile /certs/_.localdev.com/key.pem ...

Trusting minica certs on the host

Finally, to avoid the browser showing a warning that the site’s certificate is invalid or insecure, add the root certificate as a trusted root CA authority on the host machine. Martin Widmann has some nice documentation on how to do this for different operating systems.

Verify h2 protocol in the browser

With the site up, Chrome and Firefox dev tools should now show that traffic is being served via the h2 protocol.

The Protocol column isn’t visible by default, so you may have to enable it (right click on visible header → select Protocol).