🔐 Full HTTPS development environment
Tags: monorepo, security, https, development
Published: 2020-10-08 09:22:43

Hey, in this post series, I will explain how you can have a full development environment in HTTPS with a valid certificate for your local development domain.

# Why should I use HTTPS in development?

In fact, there are many reasons you should use HTTPS for your development environment:

- To use **Secure** Cookie for authentication (using Auth service such as Keycloack, Auth0, ...) → See [more here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) or [here](https://web.dev/samesite-cookies-explained/)
- To have the development & production environment as similar as possible. See the [10th factor](https://12factor.net/dev-prod-parity) of the [12-factor app](https://12factor.net/).
- Some external services (SaaS) required HTTPS (i.e. for webhooks)

# Setup the certificate

We need to setup an certificate signed with a rootCA which is recognized by our system and browser. 

First we will choose an hostname for our local development environment, then we will use the awesome tool **[mkcert](https://mkcert.dev/)** to generate a valid certificate.

# Choose a dev hostname

Choose an hostname for your development environment. 

⚠️ Don't use existing domain name which could create conflicts!

You can use the usual [localhost](http://localhost) or another hostname. I like to use [dev.local](http://dev.local) which I will take for the next steps.

You will need to add the hostname in your local dns which is `/etc/hosts` in Linux & Mac. So add `127.0.0.1	dev.local` in your `/etc/hosts`.

💡 Tips: I'm using this script from [this gist](https://gist.github.com/irazasyed/a7b0a079e7727a4315b9) to add & remove hostname easily 👍

🗒️ Note: If you use sub-domain such as `api.dev.local`, `app.dev.local`, ... You have to add them in `/etc/hosts` too. You can't use wildcard such as `*.dev.local` directly on `/etc/hosts` but you can use a service such as **dnsmasq** to achieve this.

# Mkcert

We will use [mkcert](https://mkcert.dev/) to manage the rootCA & generate our certificate.

## Installation

### 🍏 MacOS

On MacOS, use [Homebrew](https://brew.sh/)

```bash
brew install mkcert
brew install nss # if you use Firefox
```

### 🐧 Linux

On Linux, first install `certutil`.

```bash
sudo apt install libnss3-tools
curl -Lo /tmp/mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.1/mkcert-v1.4.1-linux-amd64
chmod +x /tmp/mkcert
sudo mv /tmp/mkcert /usr/local/bin/mkcert
```

See more installation methods [here](https://github.com/FiloSottile/mkcert#installation) 👌

## Usage

First we need to install the local CA  to the system and browsers.

```bash
$ mkcert -install
Created a new local CA at "/home/***/.local/share/mkcert" 💥
The local CA is now installed in the system trust store! ⚡️
The local CA is now installed in the Firefox trust store (requires browser restart)! 🦊
```

Next, we will use mkcert to generate our certificate. Using my example domain [`dev.local`](http://dev.local) & the wildcard `*.dev.local`. 

```bash
$ mkcert -cert-file certs/local-cert.pem -key-file certs/local-key.pem dev.local *.dev.local
Using the local CA at "/home/***/.local/share/mkcert" ✨

Created a new certificate valid for the following names 📜
 - "dev.local"
 - "*.dev.local"

Reminder: X.509 wildcards only go one level deep, so this won't match a.b.dev.local ℹ️

The certificate is at "certs/local-cert.pem" and the key at "certs/local-key.pem" ✅
```

We can now use the certificate located at `certs/local-cert.pem` & `certs/local-key.pem`.

Next we will see how we will use this certificate for different apps.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/050gu7mk194vqrt3th6r.png)

# BONUS: Wrapping everything in a Makefile for a dev-stack

here is how I organized my dev-stack, it's highly opinionated so take only what you need from it 😉!

## 📁 Folders Structure

```
project_root
├── dev-stack
│   ├── certs
│   │   ├── .gitignore
│   │   ├── local-cert.pem
│   │   └── local-key.pem
│   ├── scripts
│   │   ├── get-ip.sh
│   │   └── manage-hosts.sh
│   ├── .env.local
│   ├── .gitignore
│   ├── docker-compose.yml
│   ├── Makefile
│   └── README.md
```

## Makefile

```makefile
ifndef DEV_STACK_DIR
DEV_STACK_DIR = $(CURDIR)
endif
SCRIPTS_DIR=${DEV_STACK_DIR}/scripts

ifndef HOSTNAME
HOSTNAME = dev.local
endif
ifndef SUBDOMAINS
SUBDOMAINS = docs \
             traefik \
             mail \
             media \
             portainer \
             graphql \
             auth
endif
ifndef DATABASE
DATABASE = postgres
endif
ifndef INFRA
INFRA = traefik \
        maildev \
        minio \
        mkdocs \
        portainer \
        ${DATABASE} \
        graphql-engine \
        keycloak \
        auth-connector
endif

export HOST_IP := $(shell ${SCRIPTS_DIR}/get-ip.sh)

# HELP
.PHONY: help

help: ## List of the command available, make {command}
	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

.DEFAULT_GOAL := help

start:  ## Start the docker stack
	docker-compose up -d ${INFRA}

up: ## Start the docker stack
	docker-compose up ${INFRA}

stop: ## Stop the docker stack
	docker-compose stop

restart: ## Restart the docker stack
	docker-compose restart

down: ## Down the docker stack and remove all containers and networks
	docker-compose down

build: ## Build or rebuild all docker container
	docker-compose build

pull: ## Pull latest image
	docker-compose pull

add-hosts: ## Add Hosts entries for Dev stack
	${SCRIPTS_DIR}/manage-hosts.sh addhost ${HOSTNAME}
	$(foreach subdomain, $(SUBDOMAINS), ${SCRIPTS_DIR}/manage-hosts.sh addhost $(subdomain).$(HOSTNAME);)

remove-hosts: ## Remove Hosts entries for Dev stack
	${SCRIPTS_DIR}/manage-hosts.sh removehost ${HOSTNAME}
	$(foreach subdomain, $(SUBDOMAINS), ${SCRIPTS_DIR}/manage-hosts.sh removehost $(subdomain).$(HOSTNAME);)

certs-generate: ## Generate certs for all our domains
	mkcert -install
	mkcert -cert-file certs/local-cert.pem -key-file certs/local-key.pem $(HOSTNAME) *.$(HOSTNAME)

certs-uninstall: ## Uninstall the local CA (but do not delete it)
	mkcert -uninstall

```

## Scripts

### get-ip.sh

```bash
#!/bin/bash

# Get host IP address

if [ "$(uname)" = "Darwin" ];then
    ifconfig en0 | grep "inet "| cut -d ' ' -f 2
else
    ip route get 1.2.3.4 | awk '{print $7}'
fi
```

### manage-hosts.sh

```bash
#!/bin/bash

# copy from https://gist.github.com/irazasyed/a7b0a079e7727a4315b9

# PATH TO YOUR HOSTS FILE
ETC_HOSTS=/etc/hosts

# DEFAULT IP FOR HOSTNAME
IP="127.0.0.1"

# Hostname to add/remove.
HOSTNAME=$2

removehost() {
    echo "removing host";
    if [ -n "$(grep $HOSTNAME /etc/hosts)" ]
    then
        echo "$HOSTNAME Found in your $ETC_HOSTS, Removing now...";
        sudo sed -i".bak" "/$HOSTNAME/d" $ETC_HOSTS
    else
        echo "$HOSTNAME was not found in your $ETC_HOSTS";
    fi
}

addhost() {
    echo "adding host";
    HOSTS_LINE="$IP\t$HOSTNAME"
    if [ -n "$(grep $HOSTNAME /etc/hosts)" ]
        then
            echo "$HOSTNAME already exists : $(grep $HOSTNAME $ETC_HOSTS)"
        else
            echo "Adding $HOSTNAME to your $ETC_HOSTS";
            sudo -- sh -c -e "echo '$HOSTS_LINE' >> /etc/hosts";

            if [ -n "$(grep $HOSTNAME /etc/hosts)" ]
                then
                    echo "$HOSTNAME was added succesfully \n $(grep $HOSTNAME /etc/hosts)";
                else
                    echo "Failed to Add $HOSTNAME, Try again!";
            fi
    fi
}

$@
```

# Usage of the certificate

→ See in next posts how you can now use this new generated certificates

- Local HTTPS for Angular app in Nx workspace (or angular cli)
- Local HTTPS for React app in Nx workspace
- Local HTTPS for Express app (api) in Nx workspace
- Local HTTPS for NestJS app (api) in Nx workspace
- Local HTTPS for Docker services using Traefik

# Github repository

 {% github Nightbr/full-https-development-environment %}