Terraform-provider-azurerm: Add Support for App Service Managed Certificates

Created on 7 Nov 2019  路  18Comments  路  Source: terraform-providers/terraform-provider-azurerm

Community Note

  • Please vote on this issue by adding a 馃憤 reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Description

Add support for the just announced Managed Certificate capability of Azure App Services.

New or Affected Resource(s)

  • azurerm_app_service_certificate

Potential Terraform Configuration

Unknown

References

  • https://docs.microsoft.com/en-us/azure/app-service/configure-ssl-certificate#create-a-free-certificate-preview

  • new-resource servicapp-service

    Most helpful comment

    Closing since this has been fixed via #9415

    All 18 comments

    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
    image

    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!!!

    https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service_certificate_binding#example-usage

    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

    Was this page helpful?
    0 / 5 - 0 ratings