Skip to content

Commit 0f1c4c6

Browse files
fix: Document LetsEncrypt Certificates module and simplify outputs (#154)
1 parent 522e119 commit 0f1c4c6

File tree

3 files changed

+93
-26
lines changed

3 files changed

+93
-26
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,96 @@
11
# lets-encrypt-certificates
22

3+
A Terraform module to obtain publicly trusted SSL certificates from the Let's Encrypt Certificate Authority. This module addresses the lack of a native, integrated certificate management solution in Azure, equivalent to AWS Certificate Manager.
4+
5+
## Features
6+
- Integrates the Python [Certbot](https://github.com/certbot/certbot) package, since pip3 conveniently manages the extensive dependencies.
7+
- Wildcard certificate support.
8+
- Rolls up the configuration and log files into a persistent state.
9+
- State storage in Azure Storage Account.
10+
- Seamless certificate renewals within 30 days of expiry.
11+
- Automates the public DNS challenge TXT records via [certbot‑dns‑azure](https://docs.certbot-dns-azure.co.uk/en/latest/) plugin.
12+
- Supports DNS challenges for nested subdomains without needing to create delegated DNS zones (e.g. _www.private.domain.com_ challenge via _domain.com_)
13+
- Certificates are stored in Azure Key Vault as Certificate objects, skipped if thumbprint remains unchanged.
14+
- A fallback .pfx file is also stored as a base64-encoded secret for cases where Key Vault Certificate objects cannot be consumed directly.
15+
- Multiple subscription support (hub/spoke model).
16+
- Multiple region support.
17+
18+
## Requirements
19+
- A Storage Account container named `certbot-state`.
20+
- Let's Encrypt does not allow concurrent certificate requests from a single account, so this module obtains all specified certificates serially in a single execution. Avoid using the for_each meta-argument or running multiple instances in parallel.
21+
- A bash shell.
22+
- This module runs as a null resource, so it needs its own authentication with Azure since it launches in the context of the shell which launched Terraform. The recommended approach for CI/CD pipelines is to launch Terraform from an authenticated shell. e.g. in Azure DevOps use [AzureCLI@2 task](https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/azure-cli-v2?view=azure-pipelines) with:
23+
```yaml
24+
addSpnToEnvironment: true
25+
scriptType: bash
26+
```
27+
This will result in the parent shell being authenticated with Azure (for the null resource), and Terraform will use those same session credentials exported via environment variables.
28+
29+
## Example Usage
30+
31+
```terraform
32+
module "lets_encrypt_certificates" {
33+
source = "../../dtos-devops-templates/infrastructure/modules/lets-encrypt-certificates"
34+
35+
certificates = {
36+
nationalscreening_wildcard = "*.nationalscreening.nhs.uk"
37+
screening_wildcard = "*.screening.nhs.uk"
38+
screening_www = "www.screening.nhs.uk"
39+
}
40+
41+
dns_zone_names = {
42+
nationalscreening = "nationalscreening.nhs.uk"
43+
screening = "screening.nhs.uk"
44+
}
45+
46+
dns_zone_resource_group_name = var.dns_zone_rg_name_public
47+
environment = var.environment
48+
email = var.LETS_ENCRYPT_CONTACT_EMAIL
49+
key_vaults = module.key_vault # a map of Azure Key Vault objects, keyed by region name
50+
storage_account_name_hub = var.HUB_BACKEND_AZURE_STORAGE_ACCOUNT_NAME
51+
subscription_id_hub = var.SUBSCRIPTION_ID_HUB
52+
subscription_id_target = var.SUBSCRIPTION_ID_TARGET
53+
}
54+
```
55+
56+
## Example Output
57+
58+
Note the compound key `${naming_key}-${region}`:
59+
60+
```terraform
61+
key_vault_certificates = {
62+
"nationalscreening_wildcard-uksouth" = {
63+
"id" = "redacted"
64+
"location" = "uksouth"
65+
"name" = "wildcard-nationalscreening-nhs-uk"
66+
"naming_key" = "nationalscreening_wildcard"
67+
"pfx_blob_secret_name" = "pfx-wildcard-nationalscreening-nhs-uk"
68+
"subject" = "*.nationalscreening.nhs.uk"
69+
"versionless_id" = "redacted"
70+
"versionless_secret_id" = "redacted"
71+
}
72+
"screening_wildcard-uksouth" = {
73+
"id" = "redacted"
74+
"location" = "uksouth"
75+
"name" = "wildcard-screening-nhs-uk"
76+
"naming_key" = "screening_wildcard"
77+
"pfx_blob_secret_name" = "pfx-wildcard-screening-nhs-uk"
78+
"subject" = "*.screening.nhs.uk"
79+
"versionless_id" = "redacted"
80+
"versionless_secret_id" = "redacted"
81+
}
82+
"screening_www-uksouth" = {
83+
"id" = "redacted"
84+
"location" = "uksouth"
85+
"name" = "www-screening-nhs-uk"
86+
"naming_key" = "screening_www"
87+
"pfx_blob_secret_name" = "pfx-www-screening-nhs-uk"
88+
"subject" = "www.screening.nhs.uk"
89+
"versionless_id" = "redacted"
90+
"versionless_secret_id" = "redacted"
91+
}
92+
}
93+
```
94+
395
## Terraform documentation
496
For the list of inputs, outputs, resources... check the [terraform module documentation](tfdocs.md).

infrastructure/modules/lets-encrypt-certificates/data.tf

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,3 @@ data "azurerm_key_vault_certificate" "letsencrypt" {
3434
null_resource.letsencrypt_cert
3535
]
3636
}
37-
38-
# references to the created certificate pfx blobs, for outputs
39-
data "azurerm_key_vault_secret" "pfx_blob" {
40-
for_each = local.letsencrypt_certs_map
41-
42-
name = "pfx-${replace(replace(each.value.cert_subject, "*.", "wildcard-"), ".", "-")}"
43-
44-
key_vault_id = var.key_vaults[each.value.region].key_vault_id
45-
46-
depends_on = [
47-
null_resource.letsencrypt_cert
48-
]
49-
}

infrastructure/modules/lets-encrypt-certificates/output.tf

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,10 @@ output "key_vault_certificates" {
66
naming_key = v.cert_key
77
subject = v.cert_subject
88
location = v.region
9+
pfx_blob_secret_name = "pfx-${data.azurerm_key_vault_certificate.letsencrypt[k].name}"
910
id = data.azurerm_key_vault_certificate.letsencrypt[k].id
1011
versionless_id = data.azurerm_key_vault_certificate.letsencrypt[k].versionless_id
1112
versionless_secret_id = data.azurerm_key_vault_certificate.letsencrypt[k].versionless_secret_id
1213
}
1314
}
1415
}
15-
16-
output "key_vault_certificate_pfx_blobs" {
17-
value = {
18-
for k, v in local.letsencrypt_certs_map : k => {
19-
name = data.azurerm_key_vault_secret.pfx_blob[k].name
20-
naming_key = v.cert_key
21-
subject = v.cert_subject
22-
location = v.region
23-
id = data.azurerm_key_vault_secret.pfx_blob[k].id
24-
versionless_id = data.azurerm_key_vault_secret.pfx_blob[k].versionless_id
25-
}
26-
}
27-
}

0 commit comments

Comments
 (0)