hashicorp / terraform-provider-azurerm

Terraform provider for Azure Resource Manager
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs
Mozilla Public License 2.0
4.59k stars 4.62k forks source link

azurerm_app_service_certificate can't reference azurerm_key_vault_certificate versionless secret ID #23237

Closed jared-koiter closed 2 weeks ago

jared-koiter commented 1 year ago

Is there an existing issue for this?

Community Note

Terraform Version

1.5.3

AzureRM Provider Version

3.66.0

Affected Resource(s)/Data Source(s)

azurerm_app_service_certificate

Terraform Configuration Files

data "azurerm_key_vault_certificate" "example_keyvault_cert" {
  name         = "example-keyvault-cert"
  key_vault_id = azurerm_key_vault.example_keyvault.id
}

resource "azurerm_app_service_certificate" "example_appservice_cert" {
  resource_group_name = azurerm_resource_group.example_rg.name
  location            = azurerm_resource_group.example_rg.location
  name                = "example-appservice-cert"
  key_vault_secret_id = data.azurerm_key_vault_certificate.example_keyvault_cert.versionless_secret_id
}

Debug Output/Panic Output

Planning failed. Terraform encountered an error while generating this plan.

╷
│ Error: parsing "https://example-key-vault.vault.azure.net/secrets/example-keyvault-cert": expected a key vault versioned ID but no version information was found in: "https://example-key-vault.vault.azure.net/secrets/example-keyvault-cert"
│
│   with azurerm_app_service_certificate.example_appservice_cert,
│   on example.tf line 10, in resource "azurerm_app_service_certificate" "example_appservice_cert":
│   10:   key_vault_secret_id = data.azurerm_key_vault_certificate.example_keyvault_cert.versionless_secret_id
│

Expected Behaviour

azurerm_app_service_certificate resource should be created referencing the versionless (latest) ID of the Key Vault certificate, allowing for automatic certificate rotation.

Actual Behaviour

Resource cannot be created. Only works if referencing the secret_id attribute of the Key Vault certificate which contains the version. Resource field validation seems to be using the NestedItemId function which requires the parsed string to include a version ID, though there are other functions in that file that allow for versionless/optional version validation.

Steps to Reproduce

terraform apply

Important Factoids

No response

References

Similar request to #14085 which added versionless ID support to azurerm_storage_encryption_scope.

UppyAU commented 1 year ago

Hi rcskosir,

From my use of this resource I haven't needed to specify the versionless secret ID like you might for an Application Gateway.

When I used the .id resource attribute and viewed the certificate in the app services Certificate pane it did not seem to be pinned to a version ID. I haven't been through a renewal of these certificates yet however.

From the App Service Certificate management pane there seems to be a sync option which furthers my suspicion that .id is fine here.

Cheers

jared-koiter commented 1 year ago

I might have missed the intended path for automatic certificate rotation in that case.

In my own scenario I had updated a certificate in the Key Vault to a new version, but the certificate reference in the App Service certificates pane was not updating to the new version (unless I hit 'Sync' manually). When I looked at the contents of the azurerm_key_vault_certificate secret_id it was displaying a versioned path like https://example-key-vault.vault.azure.net/secrets/example-keyvault-cert/1234567890abcdef1234567890abcdef. I was under the impression that this meant it was locked to a specific version, but running something like terraform apply to try and force an update (like I assumed the 'Sync' button would do) showed no changes.

I found another issue connected to the https://github.com/hashicorp/terraform-provider-azurerm/pull/21989 PR which appears to have added suppression for changes to the secret ID, which explains why I didn't see any changes to be made by Terraform.

I also found the following in the documentation for managing Key Vault certificate imports in App Services:

If you update your certificate in Key Vault with a new certificate, App Service automatically syncs your certificate within 24 hours.

I suspect that I was merely impatient in expecting the App Services to automatically sync their certificates once the Key Vault source was updated to a new version, and that the Terraform provider doesn't need to be responsible for forcing the update to take place. It might still be worth adding support for using a versionless ID in the key_vault_secret_id field, especially if Azure itself is not storing a versioned reference.

mazjindeel-code42 commented 11 months ago

I had the same misconception @jared-koiter mentions about how the auto rotation works.

The provider requires a version, but after cert is added to the app service, the daily sync will will automatically bring in a newer version of the cert.

Terraform might still reference an older version, but Terraform ignores the version on this resource, so it doesn't detect a change.

So the certificate rotation works on its own.

It would still be good to have the provider allow a versionless secret ID, so you didn't have to refer to potentially stale versions just to get the resource created.

calebak404 commented 7 months ago

Terraform might still reference an older version, but Terraform ignores the version on this resource, so it doesn't detect a change.

This is incorrect, as I regularly see plans that cause a forced replacement due to the secret id changing.

gsmith077 commented 4 months ago

This is ridiculous.

If you're following recommendations, you will likely have a key vault in a different subscription than the app service, as per Landing Zone Design recommendations.

If this is the case, the fact that I can't pass a versionless ID means that:

I don't care if Azure figures out the link on the backend: I'm being asked to hard code a versioned ID that will expire at some point. The azurerm_app_service_certificate resource should allow me to pass a single id string without a version. https://{KV}.vault.azure.net/{secrets,certificate}/{cert_name} is a complete and valid reference, recognized by other resources that require a KV reference to a certificate (see the azurerm_application_gateway resource)

resource "azurerm_application_gateway" "application_gateway" {
[...]
  ssl_certificate {
    name                = "${name}-cert"
    key_vault_secret_id = var.certificate
  }
}

Especially in the case where I need to have an app gateway and an app service referencing the same KV stored certificate, I shouldn't have multiple interfaces in to the same value. I'm passing the standard KV ID for the secretless value, azurerm_app_service_certificate should accept that format, regardless of the backend behavior around versions and auto sync.

NicholasMcGrath commented 2 months ago

Here is a fix with help from post: phillipsj

data "azurerm_key_vault" "cert_kv"{
    name = "my-ky"
    resource_group_name = "my-rg"
    provider = azurerm.my-other-subscription
}
data "azurerm_key_vault_certificate" "domain_cert" {
  name         = replace(var.dns_hostname, ".", "-")
  key_vault_id = data.azurerm_key_vault.cert_kv.id
}
# This does not work because cross-subscription
# resource "azurerm_app_service_certificate" "domain_cert" {
#   name                = replace(var.dns_hostname, ".", "-")
#   resource_group_name = var.rg.name
#   location            = var.location
#   key_vault_secret_id = data.azurerm_key_vault_certificate.domain_cert.secret_id
# }

# Grant Microsoft managed app secret and cert reader on the keyvault before (not done here otherwise would affect other deployments)
# https://learn.microsoft.com/en-us/azure/app-service/configure-ssl-certificate?tabs=apex#authorize-app-service-to-read-from-the-vault 

resource "azapi_resource" "app_service_certificate" {
  type      = "Microsoft.Web/certificates@2022-03-01"
  name      = replace(var.dns_hostname, ".", "-")
  parent_id = var.rg.id
  location  = var.location

  body = jsonencode({
    properties = {
      keyVaultId         = data.azurerm_key_vault.cert_kv.id
      keyVaultSecretName = data.azurerm_key_vault_certificate.domain_cert.name
      serverFarmId       = azurerm_service_plan.plan.id
    }
    kind = "string"
  })
  response_export_values = ["properties.thumbprint"]
}

resource "azurerm_app_service_custom_hostname_binding" "main" {
  hostname            = var.dns_hostname
  app_service_name    = azurerm_linux_web_app.app.name
  resource_group_name = var.rg.name
  ssl_state           = "SniEnabled"
  thumbprint          = jsondecode(azapi_resource.app_service_certificate.output).properties.thumbprint
}