This is the fourth post in a series on Modernizing my Personal Web Projects. In the previous post I set up public HTTP and HTTPS access to my sites running on Kubernetes using the NGINX Ingress Controller. However, I can do better. I’ve now decided to remove HTTP access completely and use HTTPS only. It’s 2021 – who needs plain HTTP now anyway?

HTTPS is the secure version of HTTP, adding a layer of encryption to protect user’s data and privacy. It’s well known that it should be used when submitting payment details online, but these days it’s become the standard whether or not payment is involved. Search engines even give a higher ranking to HTTPS-enabled sites. So why is HTTP still used? Well, historically it was expensive and complex to set up HTTPS. This has changed now though, and it’s free and far simpler to do.

Using Cloudflare for SSL

Cloudflare is a popular choice for securing websites with minimal effort. Just sign up for a free account, point your domain at their DNS servers and enable proxying. Thanks to their Flexible SSL mode, you don’t even need HTTPS on the backend at all! This is the simplest way to get the benefits of enabling HTTPS on your site. However, it’s not the most secure, because traffic between Cloudflare and your servers is still unencrypted. Also, it requires plain HTTP to be exposed by your backend servers, preventing HTTPS everywhere.

The better option is to enable Full SSL on Cloudflare. This provides end-to-end encryption by encrypting traffic between Cloudflare and the backends. However, it requires SSL certificates to be installed on the backend servers. Once they’re installed, then the SSL mode can be easily changed using the Cloudflare UI.

Installing Free SSL Certificates using Let’s Encrypt

Let’s Encrypt is a completely free Certificate Authority that you can use to install free SSL certificates on any of your domains. However, there’s a couple of things to be aware of. Firstly, you’ll need a way of proving that you control the domains you want certificates for. Secondly, the certificates are only issued for a short duration, so you’ll need some way of automating the renewal. Fortunately, there’s tools to do this.

There are two ways to authenticate your domain with Let’s Encrypt: HTTP and DNS. The HTTP challenge requires a HTTP server running on port 80, which I don’t want, so I’ll be describing how to use the DNS challenge.

Using cert-manager on Kubernetes

For Kubernetes, the solution is cert-manager. In true Kubernetes fashion, it automatically finds hosts that need certificates and takes care of obtaining and renewing those certificates for you.

The simplest install method is:

1
2
kubectl apply -f \
  https://github.com/jetstack/cert-manager/releases/download/v1.5.3/cert-manager.yaml

Next, create a Cloudflare API token for your domains so cert-manager is able to authenticate automatically. This can be done under ‘My Profile -> API Tokens‘. Make sure the token has the ‘read’ permission for Zone.Zone and the ‘edit’ permission for Zone.DNS. After that, create the secret in Kubernetes and the ClusterIssuer object. The secret must be created in the cert-manager namespace.

1
2
kubectl create secret generic cloudflare-api-token-secret \
  -n cert-manager --from-literal=api-token=<the-secret-here>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # You must replace this email address with your own.
    # Let's Encrypt will use this to contact you about expiring
    # certificates, and issues related to your account.
    email: [email protected]
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - dns01:
        cloudflare:
          email: [email protected]
          apiTokenSecretRef:
            name: cloudflare-api-token-secret
            key: api-token

To use the certificate with an Ingress resource, follow the steps from Securing Ingress Resources in the documentation. The key parts to note are the cert-manager annotation and tls section in the spec.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    # add an annotation indicating the issuer to use.
    cert-manager.io/cluster-issuer: letsencrypt-prod
  name: myIngress
  namespace: myIngress
spec:
  rules:
  - host: example.com
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: myservice
            port:
              number: 80
  tls: # < placing a host in the TLS config will determine what ends up in the cert's subjectAltNames
  - hosts:
    - example.com
    secretName: myingress-cert # < cert-manager will store the created certificate in this secret.

That’s it! After adding the Ingress annotation, cert-manager will request for the certificate to be issued. You can see the progress with kubectl get certs and kubectl describe cert <certname>.

Using certbot

Outside of Kubernetes, the official certbot tool is recommended for obtaining and renewing certificates. It supports many different webservers and operating systems, follow the instructions for your specific setup. In my case, I wanted to use the DNS authentication method, so I followed the instructions in the wildcard tab.

Certbot also comes with a plugin for Cloudflare, which is what I used. First, obtain an API token on Cloudflare following the steps above. Then, install Certbot and the plugin. These were the steps for me on Ubuntu:

1
2
3
4
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare

Certbot can discover and install certificates for you. This was the command I used, after creating the credentials file for Cloudflare:

1
2
# Cloudflare API token used by Certbot
dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567
1
2
3
sudo certbot --dns-cloudflare --dns-cloudflare-credentials ~/certbot/cloudflare.ini \
--installer nginx \
--cert-name my-site

You can also specify specific domains with -d or skip the Nginx installation using certbot certonly. Note that Certbot by default comes with a systemd timer that automatically renews the certificates, so this is all you need to do!

Wrapping Up

Using the above methods, I was able to obtain SSL certificates for the sites on my Kubernetes cluster and other VMs. After that, I enabled the ‘Full (strict)’ SSL mode in Cloudflare and removed port 80 access from the firewall. I now have peace of mind from using HTTPS everywhere – my site and my visitor’s data are secure, and it’s all nicely automated so I don’t need to worry about renewals.