├── .gitignore ├── LICENSE ├── README.md ├── demos ├── acme-consolidated │ ├── main.tf │ └── variables.tf ├── acme-part-1-registration │ ├── main.tf │ └── variables.tf └── acme-part-2-core │ ├── main.tf │ └── variables.tf └── modules ├── acme-account-registration ├── inputs.tf ├── main.tf └── outputs.tf ├── acme-cert-request ├── inputs.tf ├── main.tf └── outputs.tf ├── aws-demo-environment ├── inputs.tf ├── main.tf └── outputs.tf └── dns ├── direct ├── inputs.tf ├── main.tf └── outputs.tf └── indirect ├── inputs.tf ├── main.tf └── outputs.tf /.gitignore: -------------------------------------------------------------------------------- 1 | **/terraform.tfvars 2 | **/.terraform 3 | 4 | # This demo will more than likely contain sensitive data in the local 5 | # terraform state files. We ignore it just incase this is commited 6 | # by accident 7 | **/terraform.tfstate* 8 | **/*.key 9 | **/*.pem -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 OpenCredo 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Let's Encrypt Terraform example 2 | 3 | This repository houses the source code referenced in the blog [Let's Encrypt and Terraform - Getting free certificates for your infrastructure](https://opencredo.com/letsencrypt-terraform). It demonstrates a working example of leveraging the [Terraform ACME provider](https://github.com/paybyphone/terraform-provider-acme) to generate and install a free Let's Encrypt certificate on an AWS ELB, fronting some EC2 instances with NGINX on them. Please see the blog for more details / instructions. 4 | 5 | 6 | # Pre-requisites (if running the examples as-is) 7 | **An AWS account** 8 | 9 | You will need a working AWS account as well as acquire credentials for a user who has the necessary privileges in an AWS account to be able to create and destroy the appropriate resources defined in the Terraform files. For more information about AWS access keys, and how to create them see https://aws.amazon.com/developers/access-keys. 10 | 11 | **A domain** 12 | 13 | You will need a domain which you can control. You can use an existing one, or buy a new one which is not been taken yet. This can be acquired from somewhere like GoDaddy, NameCheap etc. 14 | 15 | **AWS Route53 configured as your DNS provider** 16 | 17 | Within AWS, using Route53 you will need to configure a public hosted zone for this domain. Once created, you can then take the nameservers generated by AWS for you, and update them in your DNS registrar as your new nameservers. This will allow Terraform to use Route53 to manage the domain. For more information on how to do this see http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/CreatingHostedZone.html and http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/GetInfoAboutHostedZone.html. 18 | 19 | # Running the consolidated all in one version 20 | These instructions are for Mac OS 21 | 22 | * Get the binaries, setup your PATH 23 | ```` 24 | mkdir ~/demobin 25 | wget https://releases.hashicorp.com/terraform/0.8.4/terraform_0.8.4_darwin_amd64.zip 26 | unzip terraform_0.8.4_darwin_amd64.zip -d ~/demobin 27 | wget https://github.com/paybyphone/terraform-provider-acme/releases/download/v0.2.1/terraform-provider-acme_v0.2.1_darwin_amd64.zip 28 | unzip terraform-provider-acme_v0.2.1_darwin_amd64.zip -d ~/demobin 29 | sudo chmod +x demobin/terraform* 30 | export PATH=$PATH:~/demobin 31 | ```` 32 | 33 | * Get the GitHub repo 34 | ```` 35 | git clone https://github.com/opencredo/letsencrypt-terraform.git 36 | cd demos/acme-consolidated 37 | ```` 38 | 39 | 40 | * Configure variables 41 | Within the `demos/acme-consolidated/variables.tf` file, change to configure to point to your domain (i.e. not example.com), and point at the appropriate Let's Encrypt server. (staging or production) 42 | 43 | ```` 44 | # -- Staging 45 | variable "demo_acme_server_url" { default = "https://acme-staging.api.letsencrypt.org/directory"} 46 | variable "demo_acme_registration_email" { default = "your-email@example.com" } 47 | 48 | # Domain against which certificate will be created 49 | # i.e. letsencrypt-terraform.example.com 50 | variable "demo_domain_name" { default = "example.com"} 51 | variable "demo_domain_subdomain" { default = "letsencrypt-terraform"} 52 | ```` 53 | 54 | * Configure core Terraform (AWS) provider credentials 55 | ```` 56 | export AWS_ACCESS_KEY_ID=yyyyyyyy 57 | export AWS_SECRET_ACCESS_KEY=zzzzzzzz 58 | export AWS_DEFAULT_REGION=eu-west-1 59 | ```` 60 | 61 | * Configure credentials used by the ACME Terraform provider 62 | ```` 63 | # These can simply be the same as those specified for the core 64 | # AWS terraform credentials 65 | export TF_VAR_demo_acme_challenge_aws_access_key_id=$AWS_ACCESS_KEY_ID 66 | export TF_VAR_demo_acme_challenge_aws_secret_access_key=$AWS_SECRET_ACCESS_KEY 67 | export TF_VAR_demo_acme_challenge_aws_region=$AWS_DEFAULT_REGION 68 | ```` 69 | 70 | * Run terraform 71 | ```` 72 | terraform get 73 | terraform plan 74 | terraform apply 75 | ```` 76 | 77 | * Verify 78 | 79 | Go to a browser and go to your domain i.e. https://letsencrypt-terraform.example.com 80 | 81 | -------------------------------------------------------------------------------- /demos/acme-consolidated/main.tf: -------------------------------------------------------------------------------- 1 | # The Let's Encrypt registration process has been included to help demo 2 | # a single end-to-end process, however this would normally be split into 3 | # two. See demos/acme-part-1-registration and demos/acme-part-2-core 4 | # for an example of how this might be split 5 | module "acme-reg" { 6 | source = "../../modules/acme-account-registration" 7 | acme_server_url = "${var.demo_acme_server_url}" 8 | acme_registration_email = "${var.demo_acme_registration_email}" 9 | } 10 | 11 | module "dns" { 12 | source = "../../modules/dns/direct" 13 | dns_domain_name = "${var.demo_domain_name}" 14 | dns_domain_subdomain = "${var.demo_domain_subdomain}" 15 | dns_cname_value = "${module.aws-demo-env.demo_env_elb_dnsname}" 16 | } 17 | 18 | module "acme-cert" { 19 | source = "../../modules/acme-cert-request" 20 | acme_server_url = "${var.demo_acme_server_url}" 21 | acme_account_registration_url = "${module.acme-reg.registration_url}" 22 | acme_account_key_pem = "${module.acme-reg.registration_private_key_pem}" 23 | acme_certificate_common_name = "${module.dns.fqdn_domain_name}" 24 | # To make use of a single direct DNS record, comment out the line 25 | # above, uncomment the one below, and ensure the dns module source 26 | # is loaded from modules/dns/direct. This current approach has been 27 | # done to remove a cyclic dependency. 28 | # acme_certificate_common_name = "${var.demo_domain_name}.${var.demo_domain_subdomain}" 29 | 30 | acme_challenge_aws_access_key_id = "${var.demo_acme_challenge_aws_access_key_id}" 31 | acme_challenge_aws_secret_access_key = "${var.demo_acme_challenge_aws_secret_access_key}" 32 | acme_challenge_aws_region = "${var.demo_acme_challenge_aws_region}" 33 | } 34 | 35 | module "aws-demo-env" { 36 | source = "../../modules/aws-demo-environment" 37 | demo_env_nginx_count = "2" 38 | demo_env_cert_body = "${module.acme-cert.certificate_pem}" 39 | demo_env_cert_chain = "${module.acme-cert.certificate_issuer_pem}" 40 | demo_env_cert_privkey = "${module.acme-cert.certificate_private_key_pem}" 41 | } -------------------------------------------------------------------------------- /demos/acme-consolidated/variables.tf: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------- 2 | # Variables required for ACME provider demo 3 | # ---------------------------------------------------------------- 4 | 5 | # Let's Encrypt Account Registration Config 6 | # -- Production 7 | # variable "demo_acme_server_url" { default = "https://acme.api.letsencrypt.org/directory"} 8 | # variable "demo_acme_registration_email" { default = "your-email@your-company.com" } 9 | # -- Staging 10 | variable "demo_acme_server_url" { default = "https://acme-staging.api.letsencrypt.org/directory"} 11 | variable "demo_acme_registration_email" { default = "your-email@example.com" } 12 | 13 | # Domain against which certificate will be created 14 | # i.e. letsencrypt-terraform.example.com 15 | variable "demo_domain_name" { default = "example.com"} 16 | variable "demo_domain_subdomain" { default = "letsencrypt-terraform"} 17 | 18 | # Leave blank here, supply securely at runtime 19 | variable "demo_acme_challenge_aws_access_key_id" { } 20 | variable "demo_acme_challenge_aws_secret_access_key" { } 21 | variable "demo_acme_challenge_aws_region" { } -------------------------------------------------------------------------------- /demos/acme-part-1-registration/main.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------ 2 | # Use terraform to perform an ACME account registration 3 | # ------------------------------------------------------------ 4 | module "acme-reg" { 5 | source = "../../modules/acme-account-registration" 6 | acme_server_url = "${var.demo_acme_server_url}" 7 | acme_registration_email = "${var.demo_acme_registration_email}" 8 | } 9 | 10 | # ------------------------------------------------------------ 11 | # Output any ACME registration variables which 12 | # A) will be required for use in part 2 of the process and/or 13 | # B) might be helpful for storing for later use 14 | # ------------------------------------------------------------ 15 | output "server_url" { 16 | value = "${var.demo_acme_server_url}" 17 | } 18 | 19 | output "registration_email" { 20 | value = "${module.acme-reg.registration_email}" 21 | } 22 | 23 | output "registration_url" { 24 | value = "${module.acme-reg.registration_url}" 25 | } 26 | 27 | output "registration_new_authz_url" { 28 | value = "${module.acme-reg.registration_new_authz_url}" 29 | } 30 | 31 | output "registration_public_key_pem" { 32 | value = "${module.acme-reg.registration_public_key_pem}" 33 | } 34 | 35 | # 36 | # This registration private key is only being outputed here to 37 | # make it easier to demonstrate a decoupled terraform only 38 | # registration and cert request process. As this is however 39 | # a sensitive value, in reality you would not do this, but 40 | # rather you would do the process out of band and then simply 41 | # make it available for part 2. Currently creating local files 42 | # via terraform is also not supported, ref 43 | # https://github.com/hashicorp/terraform/issues/9718 44 | # 45 | output "registration_private_key_pem" { 46 | value = "${module.acme-reg.registration_private_key_pem}" 47 | } 48 | -------------------------------------------------------------------------------- /demos/acme-part-1-registration/variables.tf: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------- 2 | # Variables required for ACME provider registration 3 | # ---------------------------------------------------------------- 4 | 5 | # Let's Encrypt Account Registration Config 6 | # -- Production 7 | # variable "demo_acme_server_url" { default = "https://acme.api.letsencrypt.org/directory"} 8 | # variable "demo_acme_registration_email" { default = "your-email@your-company.com" } 9 | # -- Staging 10 | variable "demo_acme_server_url" { default = "https://acme-staging.api.letsencrypt.org/directory"} 11 | variable "demo_acme_registration_email" { default = "your-email@example.com" } 12 | 13 | -------------------------------------------------------------------------------- /demos/acme-part-2-core/main.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------- 2 | # Load the outputs from part 1 of the separately managed 3 | # process (terraform acme registration in this case) 4 | # --------------------------------------------------------- 5 | data "terraform_remote_state" "letsencrypt_registration" { 6 | backend = "local" 7 | config { 8 | path = "${path.root}/../acme-part-1-registration/terraform.tfstate" 9 | } 10 | } 11 | 12 | # --------------------------------------------------------- 13 | # Perform part 2 of the demo setup 14 | # --------------------------------------------------------- 15 | module "dns" { 16 | source = "../../modules/dns/indirect" 17 | dns_domain_name = "${var.demo_domain_name}" 18 | dns_domain_subdomain = "${var.demo_domain_subdomain}" 19 | dns_cname_value = "${module.aws-demo-env.demo_env_elb_dnsname}" 20 | } 21 | 22 | module "acme-cert" { 23 | source = "../../modules/acme-cert-request" 24 | acme_server_url = "${data.terraform_remote_state.letsencrypt_registration.server_url}" 25 | acme_account_registration_url = "${data.terraform_remote_state.letsencrypt_registration.registration_url}" 26 | acme_account_key_pem = "${data.terraform_remote_state.letsencrypt_registration.registration_private_key_pem}" 27 | acme_certificate_common_name = "${module.dns.fqdn_domain_name}" 28 | # To make use of a single direct DNS record, comment out the line 29 | # above, uncomment the one below, and ensure the dns module source 30 | # is loaded from modules/dns/direct. This current approach has been 31 | # done to remove a cyclic dependency. 32 | # acme_certificate_common_name = "${var.demo_domain_name}.${var.demo_domain_subdomain}" 33 | 34 | acme_challenge_aws_access_key_id = "${var.demo_acme_challenge_aws_access_key_id}" 35 | acme_challenge_aws_secret_access_key = "${var.demo_acme_challenge_aws_secret_access_key}" 36 | acme_challenge_aws_region = "${var.demo_acme_challenge_aws_region}" 37 | } 38 | 39 | 40 | module "aws-demo-env" { 41 | source = "../../modules/aws-demo-environment" 42 | demo_env_nginx_count = "2" 43 | demo_env_cert_body = "${module.acme-cert.certificate_pem}" 44 | demo_env_cert_chain = "${module.acme-cert.certificate_issuer_pem}" 45 | demo_env_cert_privkey = "${module.acme-cert.certificate_private_key_pem}" 46 | } -------------------------------------------------------------------------------- /demos/acme-part-2-core/variables.tf: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------- 2 | # Variables required for ACME provider demo 3 | # ---------------------------------------------------------------- 4 | 5 | # Domain against which certificate will be created 6 | # i.e. letsencrypt-terraform.example.com 7 | variable "demo_domain_name" { default = "example.com"} 8 | variable "demo_domain_subdomain" { default = "letsencrypt-terraform"} 9 | 10 | # Leave blank here, supply securely at runtime 11 | variable "demo_acme_challenge_aws_access_key_id" { } 12 | variable "demo_acme_challenge_aws_secret_access_key" { } 13 | variable "demo_acme_challenge_aws_region" { } -------------------------------------------------------------------------------- /modules/acme-account-registration/inputs.tf: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------- 2 | # Inputs required to request a new cert from lets encrypt 3 | # ---------------------------------------------------------------- 4 | variable "acme_server_url" {} 5 | variable "acme_registration_email" {} 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /modules/acme-account-registration/main.tf: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------- 2 | # Inputs required to do an initial registration (aka create an 3 | # account) with the ACME provider (Let's Encrypt) 4 | # ---------------------------------------------------------------- 5 | 6 | # Create an on the fly private key for the registration 7 | # (not the certificate). Could simply be imported as well 8 | resource "tls_private_key" "acme_registration_private_key" { 9 | algorithm = "RSA" 10 | } 11 | 12 | # Set up a registration using the registration private key 13 | resource "acme_registration" "reg" { 14 | server_url = "${var.acme_server_url}" 15 | account_key_pem = "${tls_private_key.acme_registration_private_key.private_key_pem}" 16 | email_address = "${var.acme_registration_email}" 17 | } 18 | 19 | -------------------------------------------------------------------------------- /modules/acme-account-registration/outputs.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------ 2 | # Outputs required for use in other modules 3 | # ------------------------------------------ 4 | 5 | output "registration_url" { 6 | value = "${acme_registration.reg.registration_url}" 7 | } 8 | 9 | output "registration_new_authz_url" { 10 | value = "${acme_registration.reg.registration_new_authz_url}" 11 | } 12 | 13 | output "registration_email" { 14 | value = "${var.acme_registration_email}" 15 | } 16 | 17 | output "registration_private_key_pem" { 18 | value = "${tls_private_key.acme_registration_private_key.private_key_pem}" 19 | } 20 | 21 | output "registration_public_key_pem" { 22 | value = "${tls_private_key.acme_registration_private_key.public_key_pem}" 23 | } 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /modules/acme-cert-request/inputs.tf: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------- 2 | # Inputs required to request a new cert from ACME Provider 3 | # LetsEncrypt 4 | # ---------------------------------------------------------------- 5 | variable "acme_server_url" {} 6 | variable "acme_account_registration_url" {} 7 | 8 | variable "acme_account_key_pem" {} 9 | variable "acme_certificate_common_name" {} 10 | 11 | variable "acme_challenge_aws_access_key_id" {} 12 | variable "acme_challenge_aws_secret_access_key" {} 13 | variable "acme_challenge_aws_region" {} 14 | 15 | 16 | -------------------------------------------------------------------------------- /modules/acme-cert-request/main.tf: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------- 2 | # Inputs required to request a new cert from ACME provider 3 | # ---------------------------------------------------------------- 4 | 5 | # Create a certificate 6 | resource "acme_certificate" "certificate" { 7 | 8 | server_url = "${var.acme_server_url}" 9 | account_key_pem = "${var.acme_account_key_pem}" 10 | registration_url = "${var.acme_account_registration_url}" 11 | common_name = "${var.acme_certificate_common_name}" 12 | 13 | dns_challenge { 14 | provider = "route53" 15 | 16 | # Without this explicit config, the ACME provider (which uses lego 17 | # under the covers) will look for environment variables to use. 18 | # These environment variable names happen to overlap with the names 19 | # also required by the native Terraform AWS provider, however is not 20 | # guaranteed. You may want to explicitly configure them here if you 21 | # would like to use different credentials to those used by the main 22 | # Terraform provider 23 | config { 24 | AWS_ACCESS_KEY_ID = "${var.acme_challenge_aws_access_key_id}" 25 | AWS_SECRET_ACCESS_KEY = "${var.acme_challenge_aws_secret_access_key}" 26 | AWS_REGION = "${var.acme_challenge_aws_region}" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /modules/acme-cert-request/outputs.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------ 2 | # Outputs required for use in other modules 3 | # ------------------------------------------ 4 | output "certificate_domain" { 5 | value = "${acme_certificate.certificate.certificate_domain}" 6 | } 7 | output "certificate_url" { 8 | value = "${acme_certificate.certificate.certificate_url}" 9 | } 10 | output "certificate_pem" { 11 | value = "${acme_certificate.certificate.certificate_pem}" 12 | } 13 | output "certificate_private_key_pem" { 14 | value = "${acme_certificate.certificate.private_key_pem}" 15 | } 16 | output "certificate_issuer_pem" { 17 | value = "${acme_certificate.certificate.issuer_pem}" 18 | } -------------------------------------------------------------------------------- /modules/aws-demo-environment/inputs.tf: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------- 2 | # Inputs required to create our demo (NGINX, ELB fronted) 3 | # environment in AWS 4 | # ----------------------------------------------------------- 5 | 6 | variable "demo_env_cert_body" {} 7 | variable "demo_env_cert_chain" {} 8 | variable "demo_env_cert_privkey" {} 9 | variable "demo_env_nginx_count" {} 10 | 11 | -------------------------------------------------------------------------------- /modules/aws-demo-environment/main.tf: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------- 2 | # AWS : Networking Resources 3 | # ----------------------------------------------- 4 | resource "aws_vpc" "demovpc" { 5 | cidr_block = "10.20.100.0/24" 6 | enable_dns_hostnames = true 7 | 8 | tags { 9 | Name = "letfdemo-vpc" 10 | Purpose = "letfdemo" 11 | } 12 | } 13 | 14 | # Internet gateway gives subnet access to the internet 15 | resource "aws_internet_gateway" "demovpc-ig" { 16 | vpc_id = "${aws_vpc.demovpc.id}" 17 | tags { 18 | Name = "letfdemo-ig" 19 | Purpose = "letfdemo" 20 | } 21 | } 22 | 23 | # Ensure VPC can access internet access on its main route table 24 | resource "aws_route" "internet_access" { 25 | route_table_id = "${aws_vpc.demovpc.main_route_table_id}" 26 | destination_cidr_block = "0.0.0.0/0" 27 | gateway_id = "${aws_internet_gateway.demovpc-ig.id}" 28 | 29 | } 30 | 31 | # Create a subnet for our instances 32 | resource "aws_subnet" "public" { 33 | vpc_id = "${aws_vpc.demovpc.id}" 34 | cidr_block = "10.20.100.32/27" 35 | map_public_ip_on_launch = true 36 | 37 | tags { 38 | Name = "letfdemo-subnet" 39 | Purpose = "letfdemo" 40 | } 41 | } 42 | 43 | # ------------------------------------------ 44 | # AWS : ELB related config 45 | # ------------------------------------------ 46 | 47 | # ELB security group (ensure its accessible via the web) 48 | resource "aws_security_group" "elb" { 49 | name = "letfdemo-sg-elb" 50 | description = "ELB security group" 51 | vpc_id = "${aws_vpc.demovpc.id}" 52 | 53 | # HTTPS access from anywhere 54 | ingress { 55 | from_port = 443 56 | to_port = 443 57 | protocol = "tcp" 58 | cidr_blocks = ["0.0.0.0/0"] 59 | } 60 | 61 | # outbound internet access 62 | egress { 63 | from_port = 0 64 | to_port = 0 65 | protocol = "-1" 66 | cidr_blocks = ["0.0.0.0/0"] 67 | } 68 | 69 | tags { 70 | Name = "letfdemo-sg-elb" 71 | Purpose = "letfdemo" 72 | } 73 | } 74 | 75 | resource "aws_iam_server_certificate" "elb_cert" { 76 | name_prefix = "letfdemo-cert-" 77 | certificate_body = "${var.demo_env_cert_body}" 78 | certificate_chain = "${var.demo_env_cert_chain}" 79 | private_key = "${var.demo_env_cert_privkey}" 80 | 81 | lifecycle { 82 | create_before_destroy = true 83 | } 84 | } 85 | 86 | resource "aws_elb" "web" { 87 | name = "letfdemo-elb-www" 88 | 89 | subnets = ["${aws_subnet.public.id}"] 90 | security_groups = ["${aws_security_group.elb.id}"] 91 | instances = ["${aws_instance.nginx.*.id}"] 92 | 93 | listener { 94 | instance_port = 80 95 | instance_protocol = "http" 96 | lb_port = 443 97 | lb_protocol = "https" 98 | ssl_certificate_id = "${aws_iam_server_certificate.elb_cert.arn}" 99 | 100 | } 101 | 102 | tags { 103 | Name = "letfdemo-elb" 104 | Purpose = "letfdemo" 105 | } 106 | } 107 | 108 | # ------------------------------------------ 109 | # AWS : Instance (nginx) related config 110 | # ------------------------------------------ 111 | 112 | # Our Nginx security group to access 113 | # the instances over SSH and HTTP 114 | resource "aws_security_group" "nginx-sg" { 115 | name = "letfdemo-nginx-sg" 116 | description = "Security group for nginx" 117 | vpc_id = "${aws_vpc.demovpc.id}" 118 | 119 | # SSH access from anywhere 120 | ingress { 121 | from_port = 22 122 | to_port = 22 123 | protocol = "tcp" 124 | cidr_blocks = ["0.0.0.0/0"] 125 | } 126 | 127 | # HTTP access from the VPC 128 | ingress { 129 | from_port = 80 130 | to_port = 80 131 | protocol = "tcp" 132 | cidr_blocks = ["10.20.100.0/24"] 133 | } 134 | 135 | # outbound internet access 136 | egress { 137 | from_port = 0 138 | to_port = 0 139 | protocol = "-1" 140 | cidr_blocks = ["0.0.0.0/0"] 141 | } 142 | 143 | tags { 144 | Name = "letfdemo-nginx-sg" 145 | Purpose = "letfdemo" 146 | } 147 | } 148 | 149 | # ---------------------------------------------------- 150 | # Terraform currently only supports importing an existing 151 | # key pair, not creating a new key pair 152 | # ---------------------------------------------------- 153 | resource "tls_private_key" "nginx-provisioner" { 154 | algorithm = "RSA" 155 | } 156 | 157 | resource "aws_key_pair" "nginx-provisioning" { 158 | key_name = "letfdemo-provisioning-key" 159 | public_key = "${tls_private_key.nginx-provisioner.public_key_openssh}" 160 | } 161 | 162 | data "aws_ami" "ubuntu" { 163 | most_recent = true 164 | filter { 165 | name = "name" 166 | values = ["ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*"] 167 | } 168 | filter { 169 | name = "virtualization-type" 170 | values = ["hvm"] 171 | } 172 | owners = ["099720109477"] # Canonical 173 | } 174 | 175 | resource "aws_instance" "nginx" { 176 | 177 | count = "${var.demo_env_nginx_count}" 178 | connection { 179 | # The default username for our AMI, and use our on the fly created 180 | # private key to do the initial bootstrapping (install of nginx) 181 | user = "ubuntu" 182 | private_key = "${tls_private_key.nginx-provisioner.private_key_pem}" 183 | } 184 | 185 | instance_type = "t2.micro" 186 | ami = "${data.aws_ami.ubuntu.id}" 187 | key_name = "${aws_key_pair.nginx-provisioning.id}" 188 | vpc_security_group_ids = ["${aws_security_group.nginx-sg.id}"] 189 | # We're going to launch into the same single (public) subnet as our ELB. 190 | # In a production environment it's more common to have a separate 191 | # private subnet for backend instances. 192 | subnet_id = "${aws_subnet.public.id}" 193 | 194 | # We run a remote provisioner on the instance after creating it. 195 | # In this case, we just install nginx and start it. By default, 196 | # this should be on port 80 197 | provisioner "remote-exec" { 198 | inline = [ 199 | "sudo apt-get -y update", 200 | "sudo apt-get -y install nginx", 201 | "sudo sed -i 's/nginx\\!/nginx - instance ${count.index + 1}/g' /var/www/html/index.nginx-debian.html", 202 | "sudo systemctl start nginx", 203 | ] 204 | } 205 | 206 | tags { 207 | Name = "letfdemo-nginx${count.index + 1}" 208 | Purpose = "letfdemo" 209 | } 210 | } -------------------------------------------------------------------------------- /modules/aws-demo-environment/outputs.tf: -------------------------------------------------------------------------------- 1 | output "demo_env_elb_dnsname" { 2 | value = "${aws_elb.web.dns_name}" 3 | } 4 | 5 | -------------------------------------------------------------------------------- /modules/dns/direct/inputs.tf: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------- 2 | # Inputs required to create our DNS record 3 | # ---------------------------------------------------------------- 4 | variable "dns_domain_name" { } 5 | variable "dns_domain_subdomain" { } 6 | variable "dns_cname_value" { } 7 | -------------------------------------------------------------------------------- /modules/dns/direct/main.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------ 2 | # AWS ROUTE53 : Domain creation 3 | # ------------------------------------------ 4 | 5 | # This assumes that you have already (out of band) setup AWS as your 6 | # DNS provider, and created a hosted zone again the main domain, e.g 7 | # against example.com. This datasource simply looks up the zone details 8 | # to use in the creation of the additional sub domain records. 9 | 10 | data "aws_route53_zone" "main" { 11 | name = "${var.dns_domain_name}" 12 | private_zone = false 13 | } 14 | 15 | resource "aws_route53_record" "letsencrypt-terraform" { 16 | zone_id = "${data.aws_route53_zone.main.zone_id}" 17 | name = "${var.dns_domain_subdomain}.${data.aws_route53_zone.main.name}" 18 | type = "CNAME" 19 | ttl = "60" 20 | records = ["${var.dns_cname_value}"] 21 | } 22 | -------------------------------------------------------------------------------- /modules/dns/direct/outputs.tf: -------------------------------------------------------------------------------- 1 | output "fqdn_domain_name" { 2 | value = "${aws_route53_record.letsencrypt-terraform.fqdn}" 3 | } 4 | -------------------------------------------------------------------------------- /modules/dns/indirect/inputs.tf: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------- 2 | # Inputs required to create our DNS record 3 | # ---------------------------------------------------------------- 4 | variable "dns_domain_name" { } 5 | variable "dns_domain_subdomain" { } 6 | variable "dns_cname_value" { } 7 | -------------------------------------------------------------------------------- /modules/dns/indirect/main.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------ 2 | # AWS ROUTE53 : Domain creation including a secondary 3 | # temporary DNS to get around creating 4 | # a cyclic terraform dependency 5 | # ------------------------------------------------------ 6 | 7 | # This assumes that you have already (out of band) setup AWS as your 8 | # DNS provider, and created a hosted zone again the main domain, e.g 9 | # against example.com. This datasource simply looks up the zone details 10 | # to use in the creation of the additional sub domain records. 11 | 12 | data "aws_route53_zone" "main" { 13 | name = "${var.dns_domain_name}" 14 | private_zone = false 15 | } 16 | 17 | resource "aws_route53_record" "letsencrypt-terraform" { 18 | zone_id = "${data.aws_route53_zone.main.zone_id}" 19 | name = "${var.dns_domain_subdomain}.${data.aws_route53_zone.main.name}" 20 | type = "CNAME" 21 | ttl = "60" 22 | records = ["tmp-${var.dns_domain_subdomain}.${data.aws_route53_zone.main.name}"] 23 | } 24 | 25 | resource "aws_route53_record" "tmp-letsencrypt-terraform" { 26 | zone_id = "${data.aws_route53_zone.main.zone_id}" 27 | name = "tmp-${var.dns_domain_subdomain}.${data.aws_route53_zone.main.name}" 28 | type = "CNAME" 29 | ttl = "60" 30 | records = ["${var.dns_cname_value}"] 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /modules/dns/indirect/outputs.tf: -------------------------------------------------------------------------------- 1 | output "fqdn_domain_name" { 2 | value = "${aws_route53_record.letsencrypt-terraform.fqdn}" 3 | } 4 | --------------------------------------------------------------------------------