Let’s Encrypt is a service that offers free TLS (aka SSL) certificates. The certificates are recognized by all modern browsers. The only “disadvantage” of using Let’s Encrypt is that the certificates have to be renewed every few months but the process can be automated.

Depending on your environment, there are various ways to get initially setup with their certificates. You can get specific domain (e.g. www.example.com or staging.example.com) or wildcard (*.example.com) certificates. Visit the Let’s Encrypt website to understand all of your options.

Assuming that you already have a Google Cloud project, have setup Google Cloud provider credentials for Terraform and have bought a domain name, you can use the Let’s Encrypt Docker certbot image to get a wildcard certificate using the following process.

Note the assumption is that this is a new domain name which does not have an existing DNS setup. If you are migrating a domain name, you should read the Google Cloud documentation instead.

Terraform

Resource Code

First, create a new directory and then create a Terraform file like:

provider "google" {
  version = "~> 1.0"
  project = "${var.google_project}"
  region  = "${var.google_region}"
  zone    = "${var.google_zone}"
}

variable "google_project" {
  description = "The Google Cloud project to use"
}

variable "google_region" {
  description = "The Google Cloud region to use"
}

variable "google_zone" {
  description = "The Google Cloud zone to use"
}

variable "domain_name" {
  description = "The domain name to use"
}

resource "google_dns_managed_zone" "example_com" {
  name        = "example-com"
  dns_name    = "${var.domain_name}."
  description = "${var.domain_name} domain"
}

resource "google_project_iam_custom_role" "dns_owner" {
  role_id     = "dns_owner"
  title       = "DNS Owner"
  description = "Allows service account to manage DNS."

  permissions = [
    "dns.changes.create",
    "dns.changes.get",
    "dns.managedZones.list",
    "dns.resourceRecordSets.create",
    "dns.resourceRecordSets.delete",
    "dns.resourceRecordSets.list",
    "dns.resourceRecordSets.update",
  ]
}

resource "google_service_account" "letsencrypt_dns" {
  account_id   = "dns-letsencrypt"
  display_name = "Lets Encrypt DNS Service Account"
}

resource "google_project_iam_member" "project" {
  role   = "projects/${var.google_project}/roles/${google_project_iam_custom_role.dns_owner.role_id}"
  member = "serviceAccount:${google_service_account.letsencrypt_dns.email}"
}

resource "google_service_account_key" "letsencrypt_dns" {
  service_account_id = "${google_service_account.letsencrypt_dns.name}"
  public_key_type    = "TYPE_X509_PEM_FILE"
}

resource "local_file" "letsencrypt_credentials_json" {
  content  = "${google_service_account_key.letsencrypt_dns.private_key}"
  filename = "letsencrypt-credentials.json.base64"
}

The above config sets up the Google Cloud provider with a domain name, project, region, and zone via variables to be set later. It creates a DNS managed zone on Google Cloud. You may want to rename some of the resource names like example_com to your specific setup.

Note that for the dns_name, the value will need a trailing . (so the final value will be like example.com.).

The above config also creates a service account with a custom role which allows the service account to modify DNS records. Once the account is created, it will store the credentials in a local letsencrypt-credentials.json.base64 file.

Variable Config

Create a terraform.tfvars file to fill in the variables with your specific config.

google_project = "project-id"
google_zone    = "us-central1-a"
google_region  = "us-central1"
domain_name    = "example.com"

Plan and Apply

To do the one-time initial Terraform provider setup, run:

terraform init

Then to create a plan for creating the resources:

terraform plan -out=terraform.plan

You may want to inspect the output of terraform plan to understand what resources are being created.

Run the following when ready to create the resources:

terraform apply terraform.plan

Setup DNS Nameserver

You will need to have your domain registar use the Google Cloud DNS nameservers. After applying the Terraform config, you can go to the Google Cloud Console under Networks services > Cloud DNS. Find your domain name and get the DNS nameservers. Go to your domain registar and use all of the DNS nameservers (under the NS record like ns-cloud-b1.googledomains.com.).

You may have to wait a few minutes to a day for the nameserver change to propagate.

Let’s Encrypt Certbot in Docker

Running Let’s Encrypt Certbot in Docker, you can finally get and renew a wildcard certificate.

Make 2 directories for Let’s Encrypt config and logs.

mkdir -p certs/config
mkdir -p certs/logs

Base64 decode the service account credentials into a file, and move the file into the config directory.

cat letsencrypt-credentials.json.base64 | base64 -D > letsencrypt-credentials.json
mv letsencrypt-credentials.json certs/config/google-cloud-service-account-credentials.json

Then run the following replacing the <absolute path> with the actual absolute paths:

docker run -it --rm --name certbot -v "<absolute path to>/certs/config:/etc/letsencrypt" -v "<absolute path to>/certs/logs:/var/lib/letsencrypt" certbot/dns-google certonly --dns-google-credentials /etc/letsencrypt/google-cloud-service-account-credentials.json --server https://acme-v02.api.letsencrypt.org/directory

After running the command and answering a few questions, the certbot will use the service account to create a DNS entry to verify domain ownership. Then it will issue a wildcard certificate for your domain. The certificate files and credentials will be stored in your certs/config directory.

You can then re-run the certbot when it is time to renew the certificates. Be sure to keep (and backup) a copy of the certs/* directories to re-use them later.