Setting up LetsEncrypt TLS certificate on AWS

In a recent project to setup a vault cluster, I required the use of a TLS certificate. On AWS, there are at least 2 ways to obtain a certificate via Certificate Manager:

  • Request a public certificate
  • Request a private certificate through an AWS Private CA

Since vault requires both the private key and certificate files to exist locally, we cannot use a public certificate as we cannot retrieve the private key from it. Using a Private CA is too costly as we are only using this in a development environment.

Before we can proceed, we need to have a custom domain created in Route53 with its own hosted zone.

The approach I took was to utilise the letsencrypt API to generate the TLS certificate:

1
2
3
4
5
6
7
8
9
10
11
12
docker pull certbot/dns-route53:latest

docker run -it --rm --name certbot \
    --env-file env-file  \
    -v "/home/chee/.aws:/root/.aws" \
    -v "${PWD}/tls:/etc/letsencrypt" \
    -v "${PWD}/tls:/var/lib/letsencrypt" \
    certbot/dns-route53 certonly \
    -d 'teka-teka.xyz' \
    -d 'vault.teka-teka.xyz' \
    -m user@example.com \
    --agree-tos --server https://acme-v02.api.letsencrypt.org/directory

Certbot provides a docker image that integrates with AWS Route53. It generates a DNS entry in the hosted zone for the domain during verification. We pass in the AWS credentials using an env-file and map our local AWS credentials into the running container.

Note that we need to specify each domain separately via the -d flag. Specifying localhost or IP addresses generates an error.

Once completed, the private key and certificate are created in the local directory ${PWD}/tls mapped into the container:

1
2
3
4
5
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/teka-teka.xyz/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/teka-teka.xyz/privkey.pem
This certificate expires on 2025-01-27.
These files will be updated when the certificate renews.

To use the certificates in vault setup, we can upload them to Secrets Manager as binary files:

1
2
3
4
5
6
7
aws secretsmanager create-secret --name "VAULT_TLS_PRIVKEY" \
   --description "Vault Private key file" \
   --secret-binary fileb://tls/live/example.xyz/privkey.pem

aws secretsmanager create-secret --name "VAULT_TLS_CERT" \
   --description "Vault Certificate file" \
   --secret-binary fileb://tls/live/example.xyz/fullchain.pem

To access the certificate and private key during setup, we can invoke aws secretsmanager get-secret-value to download the files onto the setup ec2 instance:

1
2
3
4
5
6
7
8
9
10
aws secretsmanager get-secret-value \
  --secret-id VAULT_TLS_PRIVKEY \
  --query 'SecretBinary' \
  --output text | base64 --decode > vault_key.pem


aws secretsmanager get-secret-value \
  --secret-id VAULT_TLS_CERT \
  --query 'SecretBinary' \
  --output text | base64 --decode > vault_ca.crt
1
2
3
4
5
sudo chmod 0600 ${tpl_vault_storage_path}/tls/vault_ca.crt
sudo chown vault:vault ${tpl_vault_storage_path}/tls/vault_ca.crt

sudo chmod 0640 ${tpl_vault_storage_path}/tls/vault_key.pem
sudo chown vault:vault ${tpl_vault_storage_path}/tls/vault_key.pem

To use it in an example vault configuration file:

1
2
3
4
5
6
7
8
9
10
11
12
storage "raft" {
  path    = "${tpl_vault_storage_path}"
  node_id = "$${INSTANCE_ID}"

  retry_join {
    auto_join_scheme = "https"
    auto_join = "provider=aws region=${tpl_aws_region} tag_key=cluster_name tag_value=vault-dev"
    leader_tls_servername = "vault.teka-teka.xyz"
    leader_client_cert_file = "${tpl_vault_storage_path}/tls/vault_ca.crt"
    leader_client_key_file = "${tpl_vault_storage_path}/tls/vault_key.pem"
  }
}

We can also import the certificate into Certificate Manager by going to Import Certificate under Certificate Manager and filling in the following fields:

  • Certificate body => contents of cert.pem
  • Certificate private key => contents of privkey.pem
  • Certificate chain => contents of fullchain.pem

If successful, we should see the certificate listed on the dashboard. The screenshots below show details of the imported certificate.

Imported Certificate in Certificate Manager

We can use this imported certificate in load balancers and Cloudfront distributions but that will be in another post.

Letsencrypt TLS certificates are shortlived and needs to be renewed. For an imported certificate, we can create an Eventbridge event that triggers a lambda function that calls the certbot binary and upload the renewed certs into secrets manager. This will be explored further in a future post.

Note that DNSSEC is disabled in order for the above to work. This needs to be also done on the source domain registratr configuration if the domain is not obtained from AWS. We can use the DNS Checker tool to validate this.

Further References