Deploy Let's Encrypt SSL On Google App Engine (Full Automation)

August 2, 2017
Create, verify, renew & deploy SSL certs using scripts only.

Update: Google has introduced managed SSL for Google App Engine.

The main purpose of using Let’s Encrypt SSL is to ensure SSL setup and renewal could be executed automatically, usually using shell scripts and cronjob.

Install Certbot

Install Certbot on Ubuntu

sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot

Create SSL cert

We will create the SSL cert using manual way, which is without using any plugins. We shall be using HTTP domain verification because it’s easier to write script to deploy file up App Engine server. If we use DNS domain verification, we would need to write a script to make DNS TXT update.

Create script to HTTP verification file to App Engine

In order to support cert renewal, we must include –manual-auth-hook. We shall write a script which upload a certain file up App Engine server for the domain verification to take place. Create authenticator.sh bash script with the following code.

Note: chmod +x authenticator.sh to make the script executable.

#!/bin/bash
# for debugging purpose only
set -x

# won't work
# sudo su username

# create the HTTP domain validation file
cd /projects/hello-app
mkdir -p "acme-challenge"
echo $CERTBOT_VALIDATION > ./acme-challenge/$CERTBOT_TOKEN

# using non-root user
# cmd1="source /projects/google-cloud-sdk/path.bash.inc"
# cmd2="gcloud app deploy --version 1 --project hello-app --quiet"
# sudo -u myusername -H bash -c "$cmd1; $cmd2;"

# using root: make sure gloud is installed and conigure for root
gcloud app deploy --version 1 --project hello-app --quiet

# exit
set +x

Edit app.yaml

Edit app.yaml to make sure the acme-challenge created by authenticator.sh are served.

handlers:
- url: /.well-known/acme-challenge
  static_dir: acme-challenge

Install gcloud for root

Certbot shall be run as root using sudo, thus authenticator.sh also shall be run as root. We need to install gcloud tools for root.

Download the latest Google Cloud SDK.

Extract the package (e.g. tar -zxf google-cloud-sdk*.tar.gz)

sudo su
# install gcloud for root
./google-cloud-sdk/install.sh
# reload shell
source ~/.bashrc
# problem launching chrome as root, use following method
gcloud auth login --no-launch-browser
# Output: Go to the following link in your browser:
# Output: Enter verification code:

Run script to obtain SSL certs

Provide full path to the /fullpath/authenticator.sh script and make sure the location is permanent (the same script shall be used for SSL renewal every 90 days). Run the following command to create SSL cert.

sudo su
certbot certonly --manual --preferred-challenges http --manual-auth-hook /fullpath/authenticator.sh -d www.mydomain.com

Take note of the location of the SSL directory (/etc/letsencrypt/live/www.mydomain.com/) and the expiry date (90 days)

 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/www.mydomain.com/fullchain.pem. Your cert
   will expire on 2017-10-31. To obtain a new or tweaked version of
   this certificate in the future, simply run certbot again. To
   non-interactively renew *all* of your certificates, run "certbot
   renew"

Renew SSL cert

Renewal Configuration

A renewal configuration file is created (after executing certbot certonly) at /etc/letsencrypt/renewal/www.mydomain.com.conf with the following content. If you forgotten to include –manual-auth-hook when executing certbot certonly, you can edit the .conf to include manual_auth_hook.

# renew_before_expiry = 30 days
version = 0.14.2
archive_dir = /etc/letsencrypt/archive/www.mydomain.com
cert = /etc/letsencrypt/live/www.mydomain.com/cert.pem
privkey = /etc/letsencrypt/live/www.mydomain.com/privkey.pem
chain = /etc/letsencrypt/live/www.mydomain.com/chain.pem
fullchain = /etc/letsencrypt/live/www.mydomain.com/fullchain.pem

# Options used in the renewal process
[renewalparams]
authenticator = manual
installer = None
account = d25e296e5ef6dc39627bb0a6c64dxxxx
pref_challs = http-01,
manual_public_ip_logging_ok = True
manual_auth_hook = /fullpath/authenticator.sh

SSL renewal test run

You can test if the renewal will run properly.

# remove 
sudo certbot renew --dry-run

List Certificates

You can list all certificates available.

certbot certificates
# Output
# Found the following certs:
#   Certificate Name: www.mydomain.com
#     Domains: www.mydomain.com
#     Expiry Date: 2017-10-31 04:35:00+00:00 (VALID: 89 days)
#     Certificate Path: /etc/letsencrypt/live/www.mydomain.com/fullchain.pem
#     Private Key Path: /etc/letsencrypt/live/www.mydomain.com/privkey.pem

You can also delete them if you no longer want to renew them.

certbot delete --cert-name www.mydomain.com

Cronjob

A renew will only happen if you run certbot renew where the cert is less than 30 days from expiry. You can run it daily or run it weekly. It should be run as root’s cronjob.

You need to be careful when running this as a cronjob as authenticator.sh is executed where it will upload the entire project up to the server (make sure you are not in development mode and uploaded unwanted/untested changes to the server).

sudo crontab -e 
# m h  dom mon dow   command
# 8AM every monday
0 8 * * 1 certbot renew --quiet

Configure SSL on App Engine

Create deploy_ssl.sh script to deploy the SSL to App Engine Server and chmod +x deploy_ssl.sh to make the script executable. You probably want to test run gcloud beta to trigger installation of [beta] component.

gcloud beta app
# Output
You do not currently have this command group installed.  Using it
requires the installation of components: [beta]

You can run it with certbot renew --post-hook "/fullpath/deploy_ssl.sh".

Note: If you bump into error of Caller is not authorized to administer this certificate. You must be a verified owner of the certificate's domain(s) [www.mydomain.com] to create, modify, or delete this resource., run gcloud beta domains verify www.mydomain.com to verify domain ownership.

#!/bin/bash
# for debugging purpose only
set -x

# /etc/letsencrypt/live/www.mydomain.com
# `privkey.pem`  : the private key for your certificate.
# `fullchain.pem`: the certificate file used in most server software.
# `chain.pem`    : used for OCSP stapling in Nginx >=1.3.7.
# `cert.pem`     : will break many server configurations, and should not be used without reading further documentation (see link below).

# gcloud beta need user intervention when running 1st time to install [beta] component.
# capture stderr output: Created [560873].
certId=$({ gcloud beta app ssl-certificates create --display-name mydomain-letsencrypt-aug2017 \
  --certificate /etc/letsencrypt/live/www.mydomain.com/fullchain.pem \
  --private-key /etc/letsencrypt/live/www.mydomain.com/privkey.pem \
  --project hello-app; } 2>&1)

# extract number from "Created [560873]."
certId=$(echo $certId | cut -d "[" -f2 | cut -d "]" -f1)

# Output: Created [560873].
# certId=$(echo "Created [560873]." | cut -d "[" -f2 | cut -d "]" -f1)

gcloud beta app domain-mappings update www.mydomain.com \
    --certificate-id $certId \
    --project hello-app

set +x

Open Google Cloud Console: Goto App Engine > Settings > SSL Certificates (or search SSL Certificates at the top search bar) to verify that SSL certs are setup properly.

APp Engine SSL Settings

After deploying the SSL, your website should be SSL enabled (https://www.mydomain.com).

app.yaml secure always

If you want your website to always serve using HTTPS, enable secure: always in app.yaml

handlers:
- url: .*
  script: main.app
  secure: always

References

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.