I don't usually write these howto articles, partially because I'm lazy, and partially because there is usually already an article explaining it better. But this time I created something fairly useful, and I haven't seen any tutorials about this exact way of doing it.
As we are increasingly living in an HTTPS-everywhere world, it's becoming more important to do every stage of your development over HTTPS. Please note that there is almost no security benefit to doing this, but there is a considerable risk involved, so this should only be done if you need to develop locally over HTTPS. But there are quite a few reasons you might want to do this. The most obvious is to catch potiential mixed content, or similar warnings as early as possible. It is also good practice to use only secure cookies, and it can be tricky to conditionally send those. Mozilla has also announced that they will only introduce new features over HTTPS, so that will also become a good reason.
But lets get down to business. What we want to do is to create our own CA certificate that we can add as a trusted CA in the browser, and then have Vagrant create a certificate signed with that for each development environment that you start locally. Since these are not public, we can't use Let's Encrypt or something similar, unless we are willing to perform very ugly hacks. Plus we want this to be completely automated.
Creating the CA
To start with we will create a new key that will be our CA. For this howto we will put all the CA files in a folder called "certs" in the vagrant root. To create the key enter:
openssl genpkey -algorithm rsa -des3 -pkeyopt rsa_keygen_bits:2048 -out tinyCA.key
The new genpkey format is a bit hairy, but it's also pretty self explanatory. This command generates a 2048bit private key using the RSA algorithm and encrypts it with the triple DES cipher. The file is named tinyCA.key. It will probably ask you for a password, I suggest using a password generator and give it something suitably long and random. Don't worry, you won't have to enter it. Write down the password in a file named "password".
This is all the tricky and non-secure bit. You probably use Git or something similar for your project, it's a really good idea to see to it that the files in "certs" are in the ".gitignore" file. Even if your project is private, don't put the keys in there, they will stay in the repo forever.
Next we create the request for the CA certificate.
openssl req -new -x509 -sha256 -nodes -days 1095 -key tinyCA.key -out tinyCA.pem -passin file:password
This command creates a new request (req -new), in the x509 format, using sha256 hashing. Don't encrypt the output (-nodes). This would be valid for 3 years (-days 1095). You may make this longer but I think 3 years is a good compromise between convenience, and not having this thing hanging around forever. You will need to generate a new request when it expires, as well as install it in all browsers. The "-passin file:password" line reads the password from the file we wrote earlier.
When you enter this command it will ask you a lot of questions. You can enter suitable values here. It mostly doesn't matter what it says, but it will be listed as the issuer in the browser:
Country Name (2 letter code) [AU]:FI
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:Raseborg
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Awesome Company
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:Mr Awesome
Email Address []:
Now you should have 3 files in the "certs" folder:
- tinyCA.key
- tinyCA.pem
- password
At this point you can add "tinyCA.pem" to your browsers certificate store. I won't go into detail about how to do that, because it varies quite a bit depending on browser and platform. But make sure you edit the trust to allow it to verify web-sites.
Configuring Vagrant
Here I will go through the basics, because everyone probably have very different setups. In my case I use simple bash scripts for provisioning, and Apache as the web server. But if you use something else, it should be fairly trivial to adapt to that.
First of all we need to make a script that does all the heavy lifting. I put it in my provisioning folder as "generate-tls.sh", but you can put it wherever is suitable for your needs. The important part is that it is run before the webserver is started with the new configuration.
It's important that you get the domain name correct somehow. How to do this will vary depending on what kind of setup you have. Personally I always use the Vagrant name followed by ".test". With shell scripts you can pass the name as an environment variable:
vagrant_dir = File.expand_path(File.dirname(__FILE__))
vagrant_name = File.basename(vagrant_dir)
config.vm.provision :shell, path: "provisioning/generate-tls.sh", env: {"NAME" => vagrant_name}
The reason to use ".test" is that it's one of the reserved TLDs, it will always remain free for local use.
And here comes the script:
#!/bin/bash
# Specify folders for certificates
CERT_DIR="/vagrant/certs"
SSL_DIR="/etc/ssl/vagrant"
# Set the domain we want to use
DOMAIN="$NAME.test"
# This should be blank to allow webserver to start
PASSPHRASE=""
# Set our CSR variables
SUBJ="
C=FI
ST=
O=Awesome Company
localityName=Raseborg
commonName=$DOMAIN
organizationalUnitName=
emailAddress=
"
# Create the SSL directory
mkdir -p "$SSL_DIR"
# Generate the Private Key
openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048 -out "$SSL_DIR/vagrant.key"
# Generate the CSR
openssl req -new -subj "$(echo -n "$SUBJ" | tr "\n" "/")" -key "$SSL_DIR/vagrant.key" -out "$SSL_DIR/vagrant.csr" -passin pass:$PASSPHRASE
# Generate the Certificate
openssl x509 -req -days 365 -in "$SSL_DIR/vagrant.csr" -CA "$CERT_DIR/tinyCA.pem" -CAkey "$CERT_DIR/tinyCA.key" -CAcreateserial -out "$SSL_DIR/vagrant.crt" -sha256 -passin "file:$CERT_DIR/password"
The SSL_DIR can be anywhere, it exists only on the virtual machine, but the CERT_DIR needs to point to the place where you put your CA certs.
The key generation is the same as before, but this one is not encrypted. We could use a password here, but it would be no bigger point in it. The password would need to be saved in the same virtual machine as the key.
The CSR is pretty straight forward, but here the info for the request is added from the variable after reformatting. It is also given the empty password, again everything happens here in the same place, so a password wouldn't add any security.
Lastly we sign the request with the CA cert, reading the key password from the file. We give it a validity of a year, as the cert is generated every time the box is provisioned, this should be more than we need. Arguably the CA password is also completely pointless, but I decided to leave it in, since you can block access to the certs folder after the VM is created if you want to restrict access to the password file.
This is probably a good point to stress the danger in doing this whole setup, and why it actually reduces security. If someone would get their hands on the CA cert that we made, they could create certificates for any websites that would be accepted in your browser without question. It is therefore very important that you don't add them to Git. Every developer should generate their own CA certificate, using the same one doesn't add any convenience since it shouldn't be distributed in Git, and all the VMs are local only. It also increases the attack surface in the sense that if an attacker gets hold of it, more machines can be compromised. The exception to this is if you host your vagrant boxes on a LAN for testing or something similar. In that case I actually recomment using a USB stick or other offline means to distribute the cert.
We now have everything we need to set up the webserver to use TLS.
Setting up the Webserver
As I stated earlier, I use Apache in my setup, so I will describe how to set that up. Other webservers should be quite similar, and there are probably good guides out there.
First of all, it's important to enable the ssl module, you can do that by adding a2enmod ssl
to your provisioning script. Then using whatever method you use to get the apache config done, add the following (or something similar):
<VirtualHost *:80>
ServerName vagrant.test
ServerAlias *.test
DocumentRoot /var/www
<IfModule mod_ssl.c>
RewriteEngine on
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</IfModule>
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName vagrant.test
ServerAlias *.test
DocumentRoot /var/www
SSLEngine on
SSLCertificateFile /etc/ssl/vagrant/vagrant.crt
SSLCertificateKeyFile /etc/ssl/vagrant/vagrant.key
</VirtualHost>
</IfModule>
If you want to allow HTTP traffic, leave out the:
<IfModule mod_ssl.c>
RewriteEngine on
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</IfModule>
That's pretty much it. If you use this in all your Vagrant projects, you will have valid certificates on all of them.
There is probably room for improvement as well. It might be a good idea to have the certs folder outside of your projects and mount the same folder for all of them. This works well if you have all projects in one parent folder.
Photo by Jose Fontano on Unsplash