Add support for the just announced Managed Certificate capability of Azure App Services.
Unknown
https://docs.microsoft.com/en-us/azure/app-service/configure-ssl-certificate#create-a-free-certificate-preview
brought this up in the PR, but it's feature specific
does the hostname for the managed cert have to already be setup as a custom domain binding for the app service? It does in the GUI it seems since it's a select box not a combo/free text box

If so, then the https://www.terraform.io/docs/providers/azurerm/r/app_service_custom_hostname_binding.html would need to be split to having the cert for a binding be it's own resource as the chain would need to be
setup custom domain binding -> generate managed certificate -> bind managed certificate to custom domain binding
Perhaps it could be achieved with azurerm_app_service_certificate_order?
I tried using it but despite the fact that the order was successfully created I can't use it as the certificates attribute is an empty list. Azure Portal hint says a vault must be created to store the cert. The azurerm_app_service_certificate_order resource doesn't have an argument to select a key vault though.
https://github.com/Azure/azure-quickstart-templates/tree/master/101-app-service-certificate-standard
azurerm_app_service_certificate_order Is something else.
afaik u must use the gui to put it in the vault
I have checked with ARM Microsoft.Web/certificates (2019-08-01) and the result was:
Properties.CanonicalName is invalid. Certificate creation requires hostname xxx added to an app in the serverFarmId /subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Web/serverfarms/xxx
note to whoever implements this, they'll need to account for the fact that an App Service Managed Certificate can change thumbprints at anytime outside any normal tf applies. You can kinda hack around this with the existing azurerm_app_service_certificate datasource which will return app service managed certs, but it creates a chicken egg problem since you can't conditionally use a datasource per https://github.com/hashicorp/terraform/issues/16380
here is a workaround we use https://gist.github.com/imod/52050eec87f87a60b5944c29ebf0f7a1
...but as pointed out by @drdamour the certificate can change at any time and then the binding should be marked as tainted to be recreated.
Is this going anywhere? or blocked for some reason?
for anyone interested... it's blocked by:
https://github.com/Azure/azure-sdk-for-go/issues/6498
which in turn is blocked by:
https://github.com/Azure/azure-rest-api-specs/issues/5029
If anyone knows anyone at MS that can push someone to fix it, that would be swell...
This would be great!
Right now, without this we have to manually add the certification, then take the thumbprint and paste it in tf. The thumbprint can change at any time.
@thomasbeauvais - I've got a few custom terraform modules to bridge the gap while we are waiting... ill link you to them here shortly
@tombuildsstuff -- In order to achieve this support for Managed App Service Certificates there are 3 other issues which need to be fixed:
Below I've managed to make it work end-to-end, fully automated within terraform, but I've filled the gaps with custom script resources, thanks to @scottwinkler (fully stateful with Read, Update, Create, Delete support) in some published modules... I'll remove these modules in my implementation as the gaps are filled.
data "azurerm_dns_zone" "main" {
provider = azurerm.corp
name = var.dns_zone
resource_group_name = var.dns_zone_resource_group
}
# This custom module is the same as a data.azurerm_app_service, except
# this exposes the required CustomDomainVerificationId field which is being fixed in #7537
module "webapp_data" {
source = "AdamCoulterOz/webappdata/azurerm"
web_app_id = azurerm_app_service.mywebsite.id
}
resource "azurerm_dns_cname_record" "main" {
provider = azurerm.corp
name = var.id
zone_name = data.azurerm_dns_zone.main.name
resource_group_name = var.dns_zone_resource_group
ttl = 300
record = azurerm_app_service.mywebsite.default_site_hostname
}
resource "azurerm_dns_txt_record" "main" {
provider = azurerm.corp
name = "asuid.${var.id}"
zone_name = data.azurerm_dns_zone.main.name
resource_group_name = var.dns_zone_resource_group
ttl = 300
record {
value = module.webapp_data.custom_domain_verification_id
}
}
# Ignore the certificate binding fields in this resource, as it would cause a circular
# dependency, this is critical as the managed certificate needs to be in-between
# these 2 parts.... a fix for this is tracked in #8069
resource "azurerm_app_service_custom_hostname_binding" "main" {
hostname = trim(azurerm_dns_cname_record.main.fqdn,".")
app_service_name = azurerm_app_service.mywebsite.name
resource_group_name = azurerm_resource_group.mywebsite.name
depends_on = [azurerm_dns_txt_record.main]
lifecycle {
ignore_changes = [ssl_state,thumbprint]
}
# TODO: Add retry / wait until the txt/cname records have propagated
# @tombuildsstuff is there general pattern for this?
}
# Creates the managed web app certificate, same as azurerm_app_service_certificate,
# except it allows the password to be empty string, tracked in azure-sdk-for-go#6498
# but im not sure they've tried setting the password to empty string yet, instead of just not setting it
module "webappcert" {
source = "AdamCoulterOz/webappcert/azurerm"
resource_group = azurerm_resource_group.mywebsite.name
location = azurerm_resource_group.mywebsite.location
app_service_plan_id = data.azurerm_app_service_plan.hosting_group.id
name = trim(azurerm_dns_cname_record.main.fqdn,".")
depends_on = [azurerm_app_service_custom_hostname_binding.main]
}
# This is the second half of the required split of azurerm_app_service_custom_hostname_binding
# to separate the certificate binding into a separate resource, tracked in #8069
module "webappcertbind" {
source = "AdamCoulterOz/webappcertbind/azurerm"
resource_group = azurerm_resource_group.mywebsite.name
web_app_name = azurerm_app_service.mywebsite.name
thumbprint = module.webappcert.thumbprint
}
Note that the following provider block is also required (in addition to pwsh 7+, and a few pwsh modules):
Install-Module @('Az.Accounts', 'Az.Websites', 'AzureHelpers')
provider "shell" {
environment = {
AzureClientId = var.client_id
AzureTenantId = var.tenant_id
AzureSubscription = var.subscription_id
}
sensitive_environment = {
AzureClientSecret = var.client_secret
}
interpreter = ["pwsh", "-Command"]
}
fyi @thomasbeauvais / @ablyler / @drdamour / @krzyszt0fd / @imod
that "" password vs nil is a pretty funny story i'll tell my grand kids
So we are almost there... as of v2.38.0 custom_domain_verification_id is now available as an attribute of azurerm_app_service and a new resource for azurerm_app_service_managed_certificate has been released... thanks @jackofallops !!
Now the only last remaining piece in this puzzle is ... #8069 (azurerm_app_service_custom_hostname_certificate_binding), which will allow us to bind the managed certificate to the hostname_binding.
@AdamCoulterOz - I'll take a look into this Monday, I think it's going to be more involved than first glance.
Closing since this has been fixed via #9415
This has been released in version 2.40.0 of the provider. Please see the Terraform documentation on provider versioning or reach out if you need any assistance upgrading. As an example:
provider "azurerm" {
version = "~> 2.40.0"
}
# ... other configuration ...
Boooh yeah.... works great!!!
Thanks @jackofallops for getting all the different parts of this one over the line!
fwiw i verified u can also use the id from https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service_certificate in the app_service_certificate_binding i've been using a pattern like
certificate_id = app_service_certificate_binding_example_exists ? app_service_certificate_binding.example.id : azurerm_app_service_managed_certificate.example.id
to default to managed cert if an explicit cert isn't present
Most helpful comment
Closing since this has been fixed via #9415