├── tls └── .gitkeep ├── .gitignore ├── outputs.tf ├── main.tf ├── variables.tf └── README.md /tls/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Terraform files 2 | .terraform 3 | terraform.tfstate 4 | terraform.tfvars 5 | *.tfstate* 6 | 7 | # OS X files 8 | .history 9 | .DS_Store 10 | 11 | # certs 12 | tls/* 13 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "ca_public_key_file_path" { 2 | value = "${var.ca_public_key_file_path}" 3 | } 4 | 5 | output "public_key_file_path" { 6 | value = "${var.public_key_file_path}" 7 | } 8 | 9 | output "private_key_file_path" { 10 | value = "${var.private_key_file_path}" 11 | } 12 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------------------------------------------------- 2 | # CREATE A CA CERTIFICATE 3 | # --------------------------------------------------------------------------------------------------------------------- 4 | 5 | resource "tls_private_key" "ca" { 6 | algorithm = "${var.private_key_algorithm}" 7 | ecdsa_curve = "${var.private_key_ecdsa_curve}" 8 | rsa_bits = "${var.private_key_rsa_bits}" 9 | } 10 | 11 | resource "tls_self_signed_cert" "ca" { 12 | key_algorithm = "${tls_private_key.ca.algorithm}" 13 | private_key_pem = "${tls_private_key.ca.private_key_pem}" 14 | is_ca_certificate = true 15 | 16 | validity_period_hours = "${var.validity_period_hours}" 17 | allowed_uses = ["${var.ca_allowed_uses}"] 18 | 19 | subject { 20 | common_name = "${var.ca_common_name}" 21 | organization = "${var.organization_name}" 22 | } 23 | 24 | # Store the CA public key in a file. 25 | provisioner "local-exec" { 26 | command = "echo '${tls_self_signed_cert.ca.cert_pem}' > '${var.ca_public_key_file_path}' && chmod ${var.permissions} '${var.ca_public_key_file_path}' && chown ${var.owner} '${var.ca_public_key_file_path}'" 27 | } 28 | } 29 | 30 | # --------------------------------------------------------------------------------------------------------------------- 31 | # CREATE A TLS CERTIFICATE SIGNED USING THE CA CERTIFICATE 32 | # --------------------------------------------------------------------------------------------------------------------- 33 | 34 | resource "tls_private_key" "cert" { 35 | algorithm = "${var.private_key_algorithm}" 36 | ecdsa_curve = "${var.private_key_ecdsa_curve}" 37 | rsa_bits = "${var.private_key_rsa_bits}" 38 | 39 | # Store the certificate's private key in a file. 40 | provisioner "local-exec" { 41 | command = "echo '${tls_private_key.cert.private_key_pem}' > '${var.private_key_file_path}' && chmod ${var.permissions} '${var.private_key_file_path}' && chown ${var.owner} '${var.private_key_file_path}'" 42 | } 43 | } 44 | 45 | resource "tls_cert_request" "cert" { 46 | key_algorithm = "${tls_private_key.cert.algorithm}" 47 | private_key_pem = "${tls_private_key.cert.private_key_pem}" 48 | 49 | dns_names = ["${var.dns_names}"] 50 | ip_addresses = ["${var.ip_addresses}"] 51 | 52 | subject { 53 | common_name = "${var.common_name}" 54 | organization = "${var.organization_name}" 55 | } 56 | } 57 | 58 | resource "tls_locally_signed_cert" "cert" { 59 | cert_request_pem = "${tls_cert_request.cert.cert_request_pem}" 60 | 61 | ca_key_algorithm = "${tls_private_key.ca.algorithm}" 62 | ca_private_key_pem = "${tls_private_key.ca.private_key_pem}" 63 | ca_cert_pem = "${tls_self_signed_cert.ca.cert_pem}" 64 | 65 | validity_period_hours = "${var.validity_period_hours}" 66 | allowed_uses = ["${var.allowed_uses}"] 67 | 68 | # Store the certificate's public key in a file. 69 | provisioner "local-exec" { 70 | command = "echo '${tls_locally_signed_cert.cert.cert_pem}' > '${var.public_key_file_path}' && chmod ${var.permissions} '${var.public_key_file_path}' && chown ${var.owner} '${var.public_key_file_path}'" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------------------------------------------------- 2 | # REQUIRED PARAMETERS 3 | # You must provide a value for each of these parameters. 4 | # --------------------------------------------------------------------------------------------------------------------- 5 | 6 | variable "ca_public_key_file_path" { 7 | description = "Write the PEM-encoded CA certificate public key to this path (e.g. /etc/tls/ca.crt.pem)." 8 | default = "tls/ca.crt.pem" 9 | } 10 | 11 | variable "public_key_file_path" { 12 | description = "Write the PEM-encoded certificate public key to this path (e.g. /etc/tls/vault.crt.pem)." 13 | default = "tls/vault.crt.pem" 14 | } 15 | 16 | variable "private_key_file_path" { 17 | description = "Write the PEM-encoded certificate private key to this path (e.g. /etc/tls/vault.key.pem)." 18 | default = "tls/vault.key.pem" 19 | } 20 | 21 | variable "owner" { 22 | description = "The OS user who should be given ownership over the certificate files." 23 | default = "ki.hong" 24 | } 25 | 26 | variable "organization_name" { 27 | description = "The name of the organization to associate with the certificates (e.g. Acme Co)." 28 | default = "bogotobogo" 29 | } 30 | 31 | variable "ca_common_name" { 32 | description = "The common name to use in the subject of the CA certificate (e.g. acme.co cert)." 33 | default = "bogotobogo cert" 34 | } 35 | 36 | variable "common_name" { 37 | description = "The common name to use in the subject of the certificate (e.g. acme.co cert)." 38 | default = "bogotobogo cert" 39 | } 40 | 41 | variable "dns_names" { 42 | description = "List of DNS names for which the certificate will be valid (e.g. vault.service.consul, foo.example.com)." 43 | type = "list" 44 | default = [ 45 | "bogotobogol.com" 46 | ] 47 | } 48 | 49 | variable "ip_addresses" { 50 | description = "List of IP addresses for which the certificate will be valid (e.g. 127.0.0.1)." 51 | type = "list" 52 | default = [ 53 | "127.0.0.1" 54 | ] 55 | } 56 | 57 | variable "validity_period_hours" { 58 | description = "The number of hours after initial issuing that the certificate will become invalid." 59 | default = "755" 60 | } 61 | 62 | # --------------------------------------------------------------------------------------------------------------------- 63 | # OPTIONAL PARAMETERS 64 | # These parameters have reasonable defaults. 65 | # --------------------------------------------------------------------------------------------------------------------- 66 | 67 | variable "ca_allowed_uses" { 68 | description = "List of keywords from RFC5280 describing a use that is permitted for the CA certificate. For more info and the list of keywords, see https://www.terraform.io/docs/providers/tls/r/self_signed_cert.html#allowed_uses." 69 | type = "list" 70 | 71 | default = [ 72 | "cert_signing", 73 | "key_encipherment", 74 | "digital_signature", 75 | ] 76 | } 77 | 78 | variable "allowed_uses" { 79 | description = "List of keywords from RFC5280 describing a use that is permitted for the issued certificate. For more info and the list of keywords, see https://www.terraform.io/docs/providers/tls/r/self_signed_cert.html#allowed_uses." 80 | type = "list" 81 | 82 | default = [ 83 | "key_encipherment", 84 | "digital_signature", 85 | ] 86 | } 87 | 88 | variable "permissions" { 89 | description = "The Unix file permission to assign to the cert files (e.g. 0600)." 90 | default = "0600" 91 | } 92 | 93 | variable "private_key_algorithm" { 94 | description = "The name of the algorithm to use for private keys. Must be one of: RSA or ECDSA." 95 | default = "RSA" 96 | } 97 | 98 | variable "private_key_ecdsa_curve" { 99 | description = "The name of the elliptic curve to use. Should only be used if var.private_key_algorithm is ECDSA. Must be one of P224, P256, P384 or P521." 100 | default = "P256" 101 | } 102 | 103 | variable "private_key_rsa_bits" { 104 | description = "The size of the generated RSA key in bits. Should only be used if var.private_key_algorithm is RSA." 105 | default = "2048" 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Private TLS Cert 2 | 3 | This module can be used to generate a Certificate Authority (CA) public key and the public and private keys of a 4 | TLS certificate signed by this CA. This certificate is meant to be used with **private** services, such as a Vault 5 | cluster accessed solely within your AWS account. For publicly-accessible services, especially services you access 6 | through a web browser, you should NOT use this module, and instead get certificates from a commercial Certificate 7 | Authority, such as [Let's Encrypt](https://letsencrypt.org/). 8 | 9 | If you're unfamiliar with how TLS certificates work, check out the [Background section](#background). 10 | 11 | 12 | 13 | 14 | ## Quick start 15 | 16 | 1. Copy this module to your computer. 17 | 18 | 1. Open `variables.tf` and fill in the variables that do not have a default. 19 | 20 | 1. DO NOT configure Terraform remote state storage for this code. You do NOT want to store the state files as they 21 | will contain the private keys for the certificates. 22 | 23 | 1. Run `terraform apply`. The output will show you the paths to the generated files: 24 | 25 | ``` 26 | Outputs: 27 | 28 | ca_public_key_file_path = ca.key.pem 29 | private_key_file_path = vault.key.pem 30 | public_key_file_path = vault.crt.pem 31 | ``` 32 | 33 | 1. Delete your local Terraform state: 34 | 35 | ``` 36 | rm -rf terraform.tfstate* 37 | ``` 38 | 39 | The Terraform state will contain the private keys for the certificates, so it's important to clean it up! 40 | 41 | 1. To inspect a certificate, you can use OpenSSL: 42 | 43 | ``` 44 | openssl x509 -inform pem -noout -text -in vault.crt.pem 45 | ``` 46 | 47 | Now that you have your TLS certs, check out the next section for how to use them. 48 | 49 | 50 | 51 | 52 | ## Using TLS certs 53 | 54 | 55 | ### Distributing TLS certs to your servers 56 | 57 | Distribute the private and public keys (the files at `private_key_file_path` and `public_key_file_path`) to the 58 | servers that will use them to handle TLS connections (e.g. Vault). For example, to run Vault with the [run-vault 59 | module](https://github.com/hashicorp/terraform-aws-vault/tree/master/modules/run-vault), you need to pass it the TLS certs: 60 | 61 | ``` 62 | /opt/vault/bin/run-vault --tls-cert-file /opt/vault/tls/vault.crt.pem --tls-key-file /opt/vault/tls/vault.key.pem 63 | ``` 64 | 65 | We **strongly** recommend encrypting the private key file while it's in transit to the servers that will use it. Here 66 | are some of the ways you could do this: 67 | 68 | * Encrypt the certificate using [KMS](https://aws.amazon.com/kms/) and include the encrypted files in the AMI for your 69 | Vault servers. Give those servers an IAM role that lets them access the same KMS key and decrypt their certs just 70 | before booting. 71 | * Put your TLS cert in a secure S3 Bucket with encryption enabled. Give your Vault servers an IAM role that allows them 72 | to download the certs from the S3 bucket just before booting. 73 | * Manually upload the certificate to each EC2 Instance with `scp`. 74 | 75 | 76 | ### Distributing TLS certs to your clients 77 | 78 | Distribute the CA public key (the file at `ca_public_key_file_path`) to any clients of those services so they can 79 | validate the server's TLS cert. Without the CA public key, the clients will reject any TLS connections: 80 | 81 | ``` 82 | vault read secret/foo 83 | 84 | Error initializing Vault: Get https://127.0.0.1:8200/v1/secret/foo: x509: certificate signed by unknown authority 85 | ``` 86 | 87 | Most TLS clients offer a way to explicitly specify extra public keys that you want to trust. For example, with 88 | Vault, you do this via the `-ca-cert` argument: 89 | 90 | ``` 91 | vault read -ca-cert=ca.crt.pem secret/foo 92 | 93 | Key Value 94 | --- ----- 95 | refresh_interval 768h0m0s 96 | value bar 97 | ``` 98 | 99 | As an alternative, you can configure the certificate trust on your server so that all TLS clients trust your CA 100 | public key by running the [update-certificate-store module](https://github.com/hashicorp/terraform-aws-vault/tree/master/modules/update-certificate-store) on your server. Once 101 | you do that, your system will trust the public key without having to pass it in explicitly: 102 | 103 | ``` 104 | update-certificate-store --cert-file /opt/vault/tls/ca.crt.pem 105 | vault read secret/foo 106 | 107 | Key Value 108 | --- ----- 109 | refresh_interval 768h0m0s 110 | value bar 111 | ``` 112 | 113 | 114 | 115 | ## Background 116 | 117 | 118 | ### How TLS/SSL Works 119 | 120 | The industry-standard way to add encryption for data in motion is to use TLS (the successor to SSL). There are many 121 | examples online explaining how TLS works, but here are the basics: 122 | 123 | - Some entity decides to be a "Certificate Authority" ("CA") meaning it will issue TLS certificates to websites or 124 | other services 125 | 126 | - An entity becomes a Certificate Authority by creating a public/private key pair and publishing the public portion 127 | (typically known as the "CA Cert"). The private key is kept under the tightest possible security since anyone who 128 | possesses it could issue TLS certificates as if they were this Certificate Authority! 129 | 130 | - In fact, the consequences of a CA's private key being compromised are so disastrous that CA's typically create an 131 | "intermediate" CA keypair with their "root" CA key, and only issue TLS certificates with the intermediate key. 132 | 133 | - Your client (e.g. a web browser) can decide to trust this newly created Certificate Authority by including its CA 134 | Cert (the CA's public key) when making an outbound request to a service that uses the TLS certificate. 135 | 136 | - When CAs issue a TLS certificate ("TLS cert") to a service, they again create a public/private keypair, but this time 137 | 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 138 | browser and what a service "advertises" to any clients such as web browsers to declare who it is. When we say that 139 | the CA signed a public key, we mean that, cryptographically, any possessor of the CA Cert can validate that this same 140 | CA issued this particular public key. 141 | 142 | - The public key is more generally known as the TLS cert. 143 | 144 | - The private key created by the CA must be kept secret by the service since the possessor of the private key can 145 | "prove" they are whoever the TLS cert (public key) claims to be as part of the TLS protocol. 146 | 147 | - How does that "proof" work? Well, your web browser will attempt to validate the TLS cert in two ways: 148 | - First, it will ensure this public key (TLS cert) is in fact signed by a CA it trusts. 149 | - Second, using the TLS protocol, your browser will encrypt a message with the public key (TLS cert) that only the 150 | possessor of the corresponding private key can decrypt. In this manner, your browser will be able to come up with a 151 | symmetric encryption key it can use to encrypt all traffic for just that one web session. 152 | 153 | - Now your client/browser has: 154 | - declared which CA it will trust 155 | - verified that the service it's connecting to possesses a certificate issued by a CA it trusts 156 | - used that service's public key (TLS cert) to establish a secure session 157 | 158 | 159 | ### Commercial or Public Certificate Authorities 160 | 161 | For public services like banks, healthcare, and the like, it makes sense to use a "Commercial CA" like Verisign, Thawte, 162 | or Digicert, or better yet a widely trusted but free service like [Let's Encrypt](https://letsencrypt.org/). That's 163 | because every web browser comes pre-configured with a set of CA's that it trusts. This means the client connecting to 164 | the bank doesn't have to know anything about CA's at all. Instead, their web browser is configured to trust the CA that 165 | happened to issue the bank's certificate. 166 | 167 | Connecting securely to private services is similar to connecting to your bank's website over TLS, with one primary 168 | difference: **We want total control over the CA.** 169 | 170 | Imagine if we used a commercial CA to issue our private TLS certificate and that commercial or public CA--which we 171 | don't control--were compromised. Now the attackers of that commercial or public CA could impersonate our private server. 172 | And indeed, [it](https://www.theguardian.com/technology/2011/sep/05/diginotar-certificate-hack-cyberwar) [has]( 173 | https://www.schneier.com/blog/archives/2012/02/verisign_hacked.html) [happened]( 174 | http://www.infoworld.com/article/2623707/hacking/the-real-security-issue-behind-the-comodo-hack.html) 175 | multiple times. 176 | 177 | 178 | ### How We'll Generate a TLS Cert for Private Services 179 | 180 | One option is to be very selective about choosing a commercial CA, but to what benefit? What we want instead is 181 | assurance that our private service really was launched by people we trust. Those same people--let's call them our 182 | "operators"--can become their *own* CA and generate their *own* TLS certificate for the private service. 183 | 184 | Sure, no one else in the world will trust this CA, but we don't care because we only need our organization to trust 185 | this CA. 186 | 187 | So here's our strategy for issuing a TLS Cert for a private service: 188 | 189 | 1. **Create our own CA.** 190 | - If a client wishes to trust our CA, they need only reference this CA public key. 191 | - We'll deal with the private key in a moment. 192 | 193 | 1. **Using our CA, issue a TLS Certificate for our private service.** 194 | - Create a public/private key pair for the private service, and have the CA sign the public key. 195 | - This means anyone who trusts the CA will trust that the possessor of the private key that corresponds to this public 196 | key is who they claim to be. 197 | - We will be extremely careful with the TLS private key since anyone who obtains it can impersonate our private 198 | service! For this reason, we recommend immediately encrypting the private key with 199 | [KMS](https://aws.amazon.com/kms/). 200 | 201 | 1. **Freely advertise our CA's public key to all internal services.** 202 | - Any service that wishes to connect securely to our private service will need our CA's public key so it can declare 203 | that it trusts this CA, and thereby the TLS cert it issued to the private service. 204 | 205 | 1. **Throw away the CA private key.** 206 | - By erasing a CA private key it's impossible for the CA to be compromised, because there's no private key to steal! 207 | - Future certs can be generated with a new CA. 208 | 209 | --------------------------------------------------------------------------------