Easy HTTPS
It has always frustrated me that even the largest companies like Microsoft have had HTTPS certificate renewal issues. With the invention of technologies like Let’s Encrypt people can finally not have to pay a fortune for certificates, and renew using its API. To make this process even easier, there are web server’s like Caddy that implement this for you. There is no excuse anymore, just do HTTPS from the start. Here is one way of deploying it on Google’s Kubernetes Engine!
I’m lost, you’ve mentioned too many technologies
Yes, there are lot’s of weird names in this industry, here is a very quick rundown on how they all fit together.
- Let’s Encrypt: Verifies and signs your website so you can host using a secure https:// site.
- Caddy: A web server which uses the above
- Docker: A way of packaging all this stuff up neatly so that it can be hosted exactly the same way in the cloud.
- Kubernetes: One way of hosting docker containers in a cluster
- Google Kubernetes Engine: Uses the above, open access $150 free credit for a year, makes tutorials like these easy and accessible to people like you!
Setup
Alright, let’s begin. First up, you need to set up a local development environment so that you can tinker locally before pushing everything live.
Prerequisite Software
Ensure you’ve installed the below, I’ve also included a handy bash script so that you can just grab the packages on any linux debian environment (ubuntu, mint, debian chicken spiced or whatever the cool kids are on about these days)…
Docker
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
$ sudo apt-get update && sudo apt-get install docker-ce
Google Cloud (gcloud with kubectl components)
$ echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
$ curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
$ sudo apt-get update && sudo apt-get install google-cloud-sdk
Next up, ensure you have a google account, and have signed up on Google Cloud. Create a new project, and create a new cluster under it. then run:
$ gcloud init
Specify the project you have created and the nearest region to you or where you whish to deploy to. After, we need to install kubectl
with:
$ gcloud components install kubectl
Navigate to your cloud console clusters and hit the big-ole Connect
button. A dialog will come up with akin to:
gcloud container clusters get-credentials alc-backend --zone australia-southeast1-a --project alcohol-survey-149200
Run the line in the dialog (similar to the above) to configure your kubectl CLI. Afterwards, you can run things like kubectl get pods
to return all pods which are running in your cluster. You can even run a bash session on a running container by using: kubectl exec -it <pod-name> bash
!
Docker Images
For the purpose of this guide, I use a minimal debian base image, more here. You can proceed one of two ways, pull my prebuilt images, or write your own using a provided configuration.
Pull TLDR; Method
$ docker pull tiggilyboo/https
DIY Dockerfile Method
FROM tiggilyboo/base
LABEL description="Caddy HTTPS rproxy"
MAINTAINER Simon Willshire <me@simonwillshire.com>
ENV DEBIAN_FRONTEND=noninteractive \
BUILD_PACKAGES="tar curl git wget libcap2-bin ca-certificates openssh-client"
RUN \
apt-get update && apt-get install --no-install-recommends -y $BUILD_PACKAGES && \
mkdir -p /config && \
curl "https://caddyserver.com/download/linux/amd64" | tar --no-same-owner -C /usr/bin/ -xz caddy && \
/usr/sbin/setcap 'cap_net_bind_Service=+ep' /usr/bin/caddy && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists && \
rm -rf /var/cache/apt
EXPOSE 80 443
CMD ["caddy"]
Configuration
Up until now, you’ve been handed a boatload of software, what comes next is the most important to get right - We need to configure Caddy in such a way that it points to a running container in the same cluster. Also, we need to be able to identify ourselves to Let’s Encrypt so that it can start serving us up fresh certificates.
The way that I have configured caddy to run is by using another Dockerfile which inherits from tiggilyboo/https, if you built from the configuration, remember to change the name of the FROM
keyword.
FROM tiggilyboo/https
COPY ./entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
All this does is sets up the container to run with the entrypoint on startup. The entrypoint.sh
file injects caddy with a configuration and starts it up:
#!/bin/bash
mkdir -p /config
echo "
some.domain.name.here {
proxy / $POD_IP:80 {
transparent
}
gzip
tls letsencrypt-email@address.com
}" >> /config/Caddyfile
echo "Loading caddy config: "
cat /config/Caddyfile
if [[ -z "${DEV}" ]]; then
caddy -quic --conf /config/Caddyfile --log stdout
else
echo "In development mode, https is disabled."
fi
Let’s break down some of the components:
some.domain.name.here
: Enter your full domain name with subdomain if required here.$POD_IP
: Environmental variable containing the running pod’s IP address to communicate with. This can be whatever you like, just export the variable from a grep ifconfig or whatever.letsencrypt-email@address.com
: An identifiable email address to sign up with Let’s Encrypt with and renew your certificates.
In essense, the above script injects a caddyfile. Caddyfile’s are configurations which Caddy server uses, in this case we are directing traffic with a reverse proxy proxy
at root /
into $POD_IP:80
at port 80 (At your existing web server container / pod).
Great, we should have a working Caddy configuration and we can now build the docker images, we use docker-compose
for this, you can install it via:
$ sudo curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
Next up, we need to write a docker-compose.yml file so that we can set up our environment to build and test locally, if you are not familiar with this process, check out some of their documentation.
Write a new file docker-compose.yml
:
version: '3'
services:
web:
build:
context: ./web
volumes:
- ./web:/src/web
ports:
- "80:80"
links:
- db
https:
build:
context: ./https
ports:
- "80:80"
- "443:443"
links:
- web
environment:
- POD_IP=172.18.0.1
- DEV=true
In the above example, we set up two images, web and https. It assumes we have a Dockerfile located in ./web and ./https that will run. The https image maps port 80 and 443 (http and https) ports to the local machine.
Alright, we can finally build the docker containers and push to our kubernetes cluster in the next section!
$ docker-compose build
$ docker images
If you want to just run and test it on your local machine, you can just do docker-compose up
. Though you won’t be able to test your https certificate unless you are hosting on the same public IP as your DNS record states on your domain (Let’s Encrypt won’t be able to assign your certificate on your local machine). This is why I’ve added a development flag to disable the https container.
Google Kubernetes Engine
After creating the images in the last section, you will need to tag your images to associate them to your poject and upload to your Google Cloud image repository. There is more information on this here.
$ docker tag image_name gcr.io/my-project/hello-app
$ gcloud docker -- push gcr.io/my-project/hello-app
Once this completes, you can now apply your kubernetes elements and host your https web server. Here is an example configuration:
kube-web.yml:
apiVersion: v1
kind: Service
metadata:
name: web
namespace: default
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: web
type: NodePort
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: web
spec:
template:
metadata:
labels:
app: web
spec:
containers:
- image: gcr.io/my-project/web
name: tmu-web
kube-https.yml
apiVersion: v1
kind: Pod
metadata:
name: https
labels:
name: https
spec:
containers:
- image: gcr.io/my-project/https
imagePullPolicy: Always
name: https
env:
- name: POD_IP
value: web
ports:
- containerPort: 80
name: web-port
Then apply it to your Google Kubernetes cluster:
$ kubectl apply -f kube-web.yml
$ kubectl apply -f kube-https.yml
Reminder: Set your gcr.io/my-project to your project tag listed in docker images
after you pushed your containers!
Once you’ve applied both files, you should be able to log to your cloud console and see the services hosted. You’ll need to create a load balancer or make the https image hosted on a public IP. Hook up this public IP to your DNS A redirect or however you wish to set it up to your domain name.
And lastly, to debug: check that your https container has a success handshake and setting of your certificate. It should look similar to the below…
https Feb 27, 2018, 4:12:34 PM 2018/02/27 03:12:34 http://www.somedomain.com
https Feb 27, 2018, 4:12:34 PM http://www.somedomain.com
https Feb 27, 2018, 4:12:34 PM 2018/02/27 03:12:34 http://somedomain.com
https Feb 27, 2018, 4:12:34 PM http://somedomain.com
https Feb 27, 2018, 4:12:34 PM 2018/02/27 03:12:34 https://www.somedomain.com
https Feb 27, 2018, 4:12:34 PM https://www.somedomain.com
https Feb 27, 2018, 4:12:34 PM 2018/02/27 03:12:34 https://somedomain.com
https Feb 27, 2018, 4:12:34 PM https://somedomain.com
https Feb 27, 2018, 4:12:34 PM done.
https Feb 27, 2018, 4:12:33 PM 2018/02/27 03:12:33 [INFO][www.somedomain.com] Certificate written to disk: /root/.caddy/acme/acme-v01.api.letsencrypt.org/sites/www.somedomain.com/www.somedomain.com.crt
https Feb 27, 2018, 4:12:33 PM 2018/02/27 03:12:33 [INFO][www.somedomain.com] Server responded with a certificate.
https Feb 27, 2018, 4:12:33 PM 2018/02/27 03:12:33 [INFO] acme: Requesting issuer cert from https://acme-v01.api.letsencrypt.org/acme/issuer-cert
https Feb 27, 2018, 4:12:31 PM 2018/02/27 03:12:31 [INFO][www.somedomain.com] acme: Validations succeeded; requesting certificates
https Feb 27, 2018, 4:12:31 PM 2018/02/27 03:12:31 [INFO][www.somedomain.com] The server validated our request
https Feb 27, 2018, 4:12:30 PM 2018/02/27 03:12:30 [INFO][www.somedomain.com] Served key authentication
https Feb 27, 2018, 4:12:29 PM 2018/02/27 03:12:29 [INFO][www.somedomain.com] acme: Trying to solve HTTP-01
Et Voila! A signed active certificate. Once you get in the swing of things with this configuration - It’s simple to set up and will automatically renew before the 3 month expiry.
If you have any questions, just chuck them in the comments below.