DEV Community

Mike Mollick
Mike Mollick

Posted on

Encrypt your Development Environment Traffic

Keeping your development environment faithful to your production environment ensures your application works as expected. However, our development environments often come with caveats and drawbacks that lead to us negating specific settings, features or functionality. One of these often overlooked features is the ability to serve your application over HTTPS.

While keeping the traffic from your local Docker container encrypted may not be a priority, ensuring your application is loading all of its assets and data over the correct protocol is. A hard-coded HTTP:// URL in a <link> or <script> tag can break your application's styling or prevent scripts from loading at all. The best way to prevent this is to load your application over HTTPS during development as well.

To load your application using HTTPS locally we'll have to generate SSL certificates. In a production environment, you're likely using a Certificate Authority (CA) to generate trusted certificates like Let's Encrypt or one of the dozens of paid CA's.

While it is possible to reuse the same certificates in your production environment, passing around your production certificate and private keys for the certificate introduces significant security risks. It's best to not to use your production certificate and private key on your local development machine.

The best solution to enable HTTPS locally is to use a self-signed certificate. Your browser won't trust this self-signed certificate natively and will most likely tell you "Your connection is not private." Encountering this message on a website you don't control is not something you should ignore. However, in this instance, you control the origin of the cert and the server itself so you can safely bypass this screen.

While these principals work for any environment or stack; this article explains how to create a self-signed certificate with Docker and Nginx. If you're not interested in the Docker specific example, skip to the end of the article.

Docker Example

With Docker, we have two options for creating self-signed certificates. The first option is to generate the certificate on your local machine and copy them into the container during the build process. The second is to create a certificate during the build process.

Copying a certificate from your local machine.

I won't go into very much detail on the process of generating a self-signed certificate, as it's been explained at great lengths many times over. You'll find additional links that explain the process in more detail at the end of the article.

To create a new self-signed certificate you'll first need to ensure OpenSSL is available on your machine. If you're running a Unix machine (Linux or MacOS), it's likely already installed. To ensure it's available, run the following command:

$ openssl version

This command should output something like "LibreSSL 2.6.5" or "OpenSSL 1.1.1a 20 Nov 2018" depending on your platform. If you receive a message saying "command not found," you'll need to install OpenSSL before proceeding. Once you've verified that OpenSSL is available, you can run the following command in the terminal and follow the interactive prompts.

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout PROJECT_DIRECTORY/nginx.key -out PROJECT_DIRECTORY/nginx.crt

Replace "PROJECT_DIRECTORY" with the directory where your Dockerfiles are.

Once completed you'll be left with a nginx.key and nginx.crt file. Your Dockerfile can be modified to copy these two files into your container. This step is shown below with the two lines that begin with ADD PROJECT_DIRECTORY/...

FROM nginx:latest

# Adds certificates from local machine to docker container
ADD PROJECT_DIRECTORY/nginx.key /etc/nginx/ssl/nginx.key
ADD PROJECT_DIRECTORY/nginx.crt /etc/nginx/ssl/nginx.crt

# Assumes servers/vhosts are defined in /etc/nginx/conf.d/
ADD PROJECT_DIRECTORY/vhost.conf /etc/nginx/conf.d/default.conf

With the certificates now in our Docker container, the last step is to update our Nginx server configuration to use the certificates. This step is demonstrated below in our vhost.conf file using the ssl_certificate and ssl_certificate_key options.

server {
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;

    index index.php index.html;
    root /var/www/public;

    # Self-signed certificate setup
    ssl_certificate /etc/nginx/ssl/nginx.crt;
    ssl_certificate_key /etc/nginx/ssl/nginx.key;

    # Example PHP application
    location / {
        try_files $uri /index.php?$args;
    }

    # Example PHP application upstream
    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

This option works well if you're developing a project on your own. However, if you're working with several other developers, they'll also need the certificates. Each developer could generate a local certificate to use, but requires introduces additional steps to the setup process. A better method is to check the certificate into source control although there is another option which makes the process easier.

Generate the Certificate with Docker Build

The best solution I've found is to generate the certificate during the build process with the Dockerfile. This method allows every developer to have their certificates that only their machine knows about as well as removing the burden of managing certificate expirations in source control.

If a developer ever encounters an expired certificate they can rebuild the Docker container with a single command to get a fresh certificate. The previous approach would require someone of the team to generate a new cert locally, commit it to version control and then rebuild the docker container.

To do this, we'll modify the previous Dockerfile to look like the following:

FROM nginx:latest

# Ensure OpenSSL is available and generate the certificate chain
RUN apt-get update && apt-get install -y openssl\
    && mkdir /etc/nginx/ssl\
    && openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt -subj "/C=US/ST=Ohio/L=Cleveland/O=X/CN=www.example.com"

# Assumes servers/vhosts are defined in /etc/nginx/conf.d/
ADD PROJECT_DIRECTORY/vhost.conf /etc/nginx/conf.d/default.conf

Instead of copying the static certificates with the ADD command we're now installing OpenSSL (if not already available) and generating the certificate on the container. OpenSSL normally requires us to answer several questions before issuing the certificate chain. However, the -subj flag allows us to specify the answers to these questions when we run the command. With this, we can now create "unattended" self-signed certificates. In this scenario, we would use the same Nginx config from the previous example.

TL;DR

Use self-signed certificates for local development and auto-generate them during your development environment setup. Use the following command to generate unattended certificates (no prompts).

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt -subj "/C=US/ST=Ohio/L=Cleveland/O=X/CN=www.example.com"

Top comments (0)