Let’s Encrypt with App Engine

I use Google App Engine often, even for my personal site. HTTPS is now cheap, we have SNI, and it should exist everywhere. My personal site is hosted on a custom domain: if you’re hosting only at hwhistlr.appspot.com, you already have HTTPS. Google is vouching for you. 🔒

So, Let’s Encrypt is here now. Instead of paying $x/month for your SSL cert, you now have a free service, backed by the ISRG and other official-sounding organisations. Great! My site mostly contains trivial stuff anyway, but I want to practice what I preach. The main downside: Let’s Encrypt offers certs valid for 3 months at a time, but it does remind you about expiry. 🗓️

You’ll need openssl, python, git (all installed on most *nix and macOS) and gcloud.

0. Set up your domain

This guide is only for a single domain — I don’t serve sites on www, so this doesn’t bother me. You might need to repeat for more.

You should follow at least some of these steps—or just open the Cloud Console to App Engine > Settings > Custom Domains > Add a custom domain and follow the guide to configure your DNS.

1. Get the client code and set up user keys

Let’s Encrypt offer an official client that works well if you own the machine you’re requesting a cert on, and are happy for their code to run as root. We’re using App Engine, and I’m not happy for their code to run that way.

First, check out letsencrypt-nosudo

git clone https://github.com/diafygi/letsencrypt-nosudo.git

You’ll need to generate a user key pair if you don’t already have one —

openssl genrsa 4096 > user.key
openssl rsa -in user.key -pubout > user.pub

You’ll also need a domain key —

export DOMAIN="example.com"   # we'll need this for later too
openssl genrsa 2048 > domain-${DOMAIN}.key

Note that Let’s Encrypt might recommend you use a 4096 bit key, but as of writing, App Engine doesn’t support them.

2. Sign the keys and verify ownership

These are the steps you’ll have to run every three months. You need to generate a signing certificate and verify that you own the domain.

a. Generate a signing certificate —

openssl req -new -sha256 -key domain-${DOMAIN}.key \
-subj "/CN=${DOMAIN}" > domain-${DOMAIN}.csr

b. Run the Let’s Encrypt signing code —

python letsencrypt-nosudo/sign_csr.py -f -p user.pub \
domain-${DOMAIN}.csr > signed-${DOMAIN}.crt

This will prompt you a few times to perform openssl commands, which you should do in a different window. It’ll eventually ask you to ‘update your server’—when you see that, continue reading below.

c. Update your app.yaml and redeploy your app —

Open up your app.yaml file and add the following handler. (If you’re updating your keys for the 2nd time, then you’ll already have this.)

- url: /.well-known
static_dir: .well-known

You can now create the .well-known directory inside your application’s root directory, and create a file with the secret (that letsencrypt-nosudo emits).

mkdir -p .well-known/acme-challenge
# Make sure you replace SECRET_HERE and FILENAME_HERE
echo "SECRET_HERE" > .well-known/acme-challenge/FILENAME_HERE

Deploy your App Engine instance (e.g. via gcloud app deploy). Head back to the Let’s Encrypt client, hit Enter, and some files will be created. Hooray!

d. Combine the signed certificate and the ‘cross-signed’ PEM file —

We have to combine the output certificate with extra data from Let’s Encrypt. This is needed to support various older browsers.

curl https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > lets-encrypt-x3-cross-signed.pem 
cat signed-${DOMAIN}.crt lets-encrypt-x3-cross-signed.pem \
> chained-${DOMAIN}.pem

3. Upload the keys to the Cloud Console

Use the beta gcloud command to upload keys from the command-line (don’t worry—the beta component will install if you need it) —

gcloud beta app ssl-certificates create \
--display-name "cert built $(date)" \
--certificate chained-${DOMAIN}.pem \
--private-key domain-${DOMAIN}.key

This will display a new certificate ID (typically a 6–7 digit number). You’ll then need to mark it active —

gcloud beta app domain-mappings update ${DOMAIN} \
--certificate-id THE_ID_GOES_HERE

You can also complete both these steps using the Cloud Console directly—under SSL Settings. Remember that domain- is your private key 🔐, and chained- is public 🔓.

Phew. You’re done! 😴🎇

Done

That’s it. Try loading up your custom domain now over https. And, in what’s possibly the most important step of this whole excursion, be sure to now actually get your users to load your site securely!

You can do this either by redirecting them in code, or by sending a HSTS header. Unfortunately, if you’re actually a serious target for cyber-whatever, nefarious MITM attacks could still strip whatever approach you try from ever getting to your users. Have fun!

Note: This article was rewritten in June 2017 to use letsencrypt-nosudo and the command-line gcloud tool. Previous versions used the official client.

Contact me on Twitter if you have questions or queries 🐦