Skip to main content
Security Modules 0.71.0Last updated in version 0.47.0

Generate a TLS/SSL Certificate for a Private Service

View SourceRelease Notes

This Gruntwork Docker Module generates the public and private keys of a new TLS/SSL certificate, and also returns the public and private keys of the Certificate Authority (CA) used to generate the TLS certificate. The private key of the CA is optionally destroyed immediately after generation to prevent any future malfeasance.

These certificates are ideal for use with a private service. For services that are publicly addressable, consider using a commercial CA or public, free CA like Let's Encrypt.

Generating the TLS Public and Private Keypairs

Requirements

  • Make sure the latest version of Docker is installed and running.

Instructions

  1. Edit the docker-compose.yml file and fill in your desired argument values.
  2. Now run docker-compose up and your TLS certs will output to a local output directory!

To see documentation on the arguments in docker-compose.yml, see the main.sh file.

Note that the Docker Compose file mounts the local machine folder ./output in the Docker container. Mac and Windows users sohuld take note that, in some cases, volume mounting may be extremely slow, or even one-way-only if you use an rsync-based workaround to Docker's occasionally unsatisfying native volume mounting functionality.

How It Works

Once you run docker-compose up, the following steps occur:

  1. A Docker image is built on your local machine that contains the latest OpenSSL for Ubuntu.
  2. Docker-compose runs the Docker image and passes in the command value.
  3. This kicks off a series of scripts that:
    • Generate a CA public key ("CA cert") and the corresponding CA private key
    • Generate a Certificate Signing Request (CSR) which is used to generate a public key (the "TLS cert") that is valid for the desired domain name and the corresponding private key.
    • Outputs the following files:
      • CA public key: ca/ca.cert.pem
      • CA private key: ca/ca.key.pem (if deletion was not requested)
      • TLS public key: cert/cert.pem
      • TLS private key: cert/key.pem

Inspecting a certificate

You may wish to inspect some of the certificates generated by this tool. Use OpenSSL to do this with this command:

openssl x509 -inform pem -noout -text -in cert.pem 

Background

How TLS/SSL Works

The industry-standard way to add encryption for data in motion is to use TLS (the successor to SSL). There are many examples online explaining how TLS works, but here are the basics:

  • Some entity decides to be a "Certificate Authority" ("CA") meaning it will issue TLS certificates to websites or other services
  • An entity becomes a Certificate Authority by creating a public/private key pair and publishing the public portion (typically known as the "CA Cert"). The private key is kept under the tightest possible security since anyone who possesses it could issue TLS certificates as if they were this Certificate Authority!
  • In fact, the consequences of a CA's private key being compromised are so disastrous that CA's typically create an "intermediate" CA keypair with their "root" CA key, and only issue TLS certificates with the intermediate key.
  • Your client (e.g. a web browser) can decide to trust this newly created Certificate Authority by including its CA Cert (the CA's public key) when making an outbound request to a service that uses the TLS certificate.
  • When CAs issue a TLS certificate ("TLS cert") to a service, they again create a public/private keypair, but this time the public key is "signed" by the CA. That public key is what you view when you click on the lock icon in a web browser and what a service "advertises" to any clients such as web browsers to declare who it is. When we say that the CA signed a public key, we mean that, cryptographically, any possessor of the CA Cert can validate that this same CA issued this particular public key.
  • The public key is more generally known as the TLS cert.
  • The private key created by the CA must be kept secret by the service since the possessor of the private key can "prove" they are whoever the TLS cert (public key) claims to be as part of the TLS protocol.
  • How does that "proof" work? Well, your web browser will attempt to validate the TLS cert in two ways:
    • First, it will ensure this public key (TLS cert) is in fact signed by a CA it trusts.
    • Second, using the TLS protocol, your browser will encrypt a message with the public key (TLS cert) that only the possessor of the corresponding private key can decrypt. In this manner, your browser will be able to come up with a symmetric encryption key it can use to encrypt all traffic for just that one web session.
  • Now your client/browser has:
    • declared which CA it will trust
    • verified that the service it's connecting to possesses a certificate issued by a CA it trusts
    • used that service's public key (TLS cert) to establish a secure session

Commercial or Public Certificate Authorities

For public services like banks, healthcare, and the like, it makes sense to use a "Commercial CA" like Verisign, Thawte, or Digicert, or better yet a widely trusted but free service like Let's Encrypt. (For additional information on Let's Encrypt, see Alternative Solutions Considered in this document.) That's because every web browser comes pre-configured with a set of CA's that it trusts. This means the client connecting to the bank doesn't have to know anything about CA's at all. Instead, their web browser is configured to trust the CA that happened to issue the bank's certificate.

Connecting securely to private services is similar to connecting to your bank's website over TLS, with one primary difference: We want total control over the CA.

Imagine if we used a commercial CA to issue our private TLS certificate and that commercial or public CA -- which we don't control -- were compromised. Now the attackers of that commercial or public CA could impersonate our private server. And indeed, it has happened multiple times.

How We'll Generate a TLS Cert for Private Services

One option is to be very selective about choosing a commercial CA, but to what benefit? What we want instead is assurance that our private service really was launched by people we trust. Those same people -- let's call them our "operators" - can become their own CA and generate their own TLS certificate for the private service.

Sure, no one else in the world will trust this CA, but we don't care because we only need our organization to trust this CA.

So here's our strategy for issuing a TLS Cert for a private service:

  1. Create our own CA.
  • If a client wishes to trust our CA, they need only reference this CA public key.
  • We'll deal with the private key in a moment.
  1. Using our CA, issue a TLS Certificate for our private service.
  • Create a public/private key pair for the private service, and have the CA sign the public key.
  • This means anyone who trusts the CA will trust that the possessor the private key that corresponds to this public key is who they claim to be.
  • We will be extremely careful with the TLS private key since anyone who obtains it can impersonate our private service! For this reason, we recommend immediately encrypting the private key with AWS KMS by using gruntkms.
  1. Freely advertise our CA's public key to all internal services.
  • Any service that wishes to connect securely to our private service will need our CA's public key so it can declare that it trusts this CA, and thereby the TLS cert it issued to the private service.
  1. Finally, consider throwing away the CA private key.
    • By erasing a CA private key it's impossible for the CA to be compromised, because there's no private key to steal!
    • Future certs can be generated with a new CA.
    • Contrast this to protecting your CA private key. There are tradeoff's either way so choose the option that makes the most sense for your organization.

Alternative Solutions Considered

Terraform's TLS Provider

A compelling alternative is to use Teraform's built-in TLS Provider. The primary concern with this approach is that the TLS private key generated by the tls_private_key Terraform resource is stored in plaintext in the Terraform state and is therefore not recommended for production use.

Let's Encrypt

Let's Encrypt behaves in every way like a traditional commercial Certificate Authority (CA), except that it's free. Because Let's Encrypt has good documentation around how to generate and and automatically renew a TLS Certificate, it seemed like a good solution to solve our problem.

Unfortunately, Let's Encrypt is optimized for public services, not private ones. As a result, when issuing a new TLS certificate, operators must prove ownership of their domain name by either provisioning a DNS record under example.com or provisioning an HTTP resource under a well-known URI on https://example.com. This means it's not possible to generate a TLS cert for a service with a private DNS address since Let's Encrypt would have no way of either resolving the domain name (because it's private) or reaching the service to validate an HTTP document (because, again, it's private).

While Let's Encrypt is not the ideal solution for the intent of this module, it's well-suited to automatically generating TLS certificates for any public services.