2
votes

The following terraform configuration is supposed to:

  1. Obtain the id of the relevant Key Vault
  2. Obtain the id of the certificate secret
  3. Setup custom hostname binding
  4. Setup app service certificate
data "azurerm_key_vault" "hosting_secondary_kv" {
  name                = local.ctx.HostingSecondaryKVName
  resource_group_name = local.ctx.HostingSecondaryRGName
}

data "azurerm_key_vault_secret" "cert" {
  name         = var.env == "prod" ? local.ctx.ProdCertificateName : local.ctx.NonProdCertificateName
  key_vault_id = data.azurerm_key_vault.hosting_secondary_kv.id
}

resource "azurerm_app_service_custom_hostname_binding" "webapp_fqdn" {
  for_each = local.apsvc_map

  hostname         = each.value.fqdn
  app_service_name = azurerm_app_service.webapp[each.key].name
  resource_group_name = var.regional_web_rg[each.value.location].name

  ssl_state = "SniEnabled"
  thumbprint = azurerm_app_service_certificate.cert[each.value.location].thumbprint

  depends_on = [
    azurerm_traffic_manager_endpoint.ep
  ]
}

resource "azurerm_app_service_certificate" "cert" {
  for_each = local.locations

  name                = var.env == "prod" ? local.ctx.ProdCertificateName : local.ctx.NonProdCertificateName
  resource_group_name = var.regional_web_rg[each.value].name
  location            = each.value
  key_vault_secret_id = data.azurerm_key_vault_secret.cert.id
}

I have configured all the permissions as explained in https://www.terraform.io/docs/providers/azurerm/r/app_service_certificate.html

Running the code yields the following error:

Error: Error creating/updating App Service Certificate "wildcard-np-xyzhcm-com" (Resource Group "MyAppServiceResourceGroup"): web.CertificatesClient#CreateOrUpdate: Failure responding to request: StatusCode=403 -- Original Error: autorest/azure: Service returned an error. Status=403 Code="LinkedAuthorizationFailed" Message="The client '5...8' with object id '5...8' has permission to perform action 'Microsoft.Web/certificates/write' on scope '/subscriptions/0...7/resourceGroups/MyAppServiceResourceGroup/providers/Microsoft.Web/certificates/wildcard-np-xyzhcm-com'; however, it does not have permission to perform action 'write' on the linked scope(s) '/subscriptions/0...7/resourceGroups/MyKeyVaultResourceGroup/providers/Microsoft.KeyVault/vaults/MyKeyVault' or the linked scope(s) are invalid."

All the resources are in the same subscription.

I do not understand. Does Azure want me to grant the Service Principal performing the deployment (5...8) the 'write' permission on the key vault containing the certificate? What am I missing?

EDIT 1

I used terraform to create the access policy to the Key Vault. Here is the relevant code:

A custom role definition allowing the "Microsoft.KeyVault/vaults/read" action:

resource "azurerm_role_definition" "key_vault_reader" {
  name  = "Key Vault Reader"
  scope = data.azurerm_subscription.current.id

  permissions {
    actions     = ["Microsoft.KeyVault/vaults/read"]
    not_actions = []
  }

  assignable_scopes = [
    data.azurerm_subscription.current.id
  ]
}

Letting the Microsoft WebApp Service Principal access the certificate:

data "azurerm_key_vault" "hosting_secondary_kv" {
  name                = local.ctx.HostingSecondaryKVName
  resource_group_name = local.ctx.HostingSecondaryRGName
}

data "azuread_service_principal" "MicrosoftWebApp" {
  application_id = "abfa0a7c-a6b6-4736-8310-5855508787cd"
}

resource "azurerm_key_vault_access_policy" "webapp_sp_access_to_hosting_secondary_kv" {
  key_vault_id = data.azurerm_key_vault.hosting_secondary_kv.id

  object_id = data.azuread_service_principal.MicrosoftWebApp.object_id
  tenant_id = data.azurerm_subscription.current.tenant_id

  secret_permissions = ["get"]
  certificate_permissions = ["get"]
}

Next grant the Service Principal used by the deployment the custom Key Vault Reader role in the resource group of the respective Key Vault:

data "azurerm_key_vault" "hosting_secondary_kv" {
  name                = local.ctx.HostingSecondaryKVName
  resource_group_name = local.ctx.HostingSecondaryRGName
}

data "azurerm_role_definition" "key_vault_reader" {
  name  = "Key Vault Reader"
  scope = data.azurerm_subscription.current.id
}

resource "azurerm_role_assignment" "sp_as_hosting_secondary_kv_reader" {
  scope              = "${data.azurerm_subscription.current.id}/resourceGroups/${local.ctx.HostingSecondaryRGName}"
  role_definition_id = data.azurerm_role_definition.key_vault_reader.id
  principal_id       = azuread_service_principal.sp.id
}

Finally setup the access policy for the aforementioned Service Principal:

resource "azurerm_key_vault_access_policy" "sp_access_to_hosting_secondary_kv" {
  key_vault_id = data.azurerm_key_vault.hosting_secondary_kv.id

  object_id = azuread_service_principal.sp.object_id
  tenant_id = data.azurerm_subscription.current.tenant_id

  secret_permissions = ["get"]
  certificate_permissions = ["get"]
}

And the snapshots from the portal:

enter image description here

enter image description here

enter image description here

1
Did you use Terraform to create access policy? And what is the role of your service principal in your keyvault -> Access control (IAM)?Joy Wang-MSFT
Please, see EDIT 1. And thank you.mark
I wonder if my custom role need to have some data actions. I do not really understand the difference between them.mark
We had a call with Azure engineers. They are investigating it.mark

1 Answers

1
votes

So we have discussed it with the Microsoft Support and the solution they have provided is that we can use a custom Role Definition based on the built-in Reader role + Key Vault deploy action.

The terraform role definition looks like this:

resource "random_uuid" "reader_with_kv_deploy_id" {}

resource "azurerm_role_definition" "reader_with_kv_deploy" {
  role_definition_id = random_uuid.reader_with_kv_deploy_id.result
  name               = "Key Vault Reader with Action for ${var.sub}"
  scope              = data.azurerm_subscription.current.id
  description        = "Can deploy/import secret from key vault to Web App"

  permissions {
    actions     = ["*/read", "Microsoft.KeyVault/vaults/deploy/action"]
    not_actions = []
  }

  assignable_scopes = [
    data.azurerm_subscription.current.id
  ]
}

Anyway, using this role instead of "Key Vault Contributor" does allow to link an App Service to a certificate in a Key Vault.

Those two questions remain:

  1. Why on earth this complication is even necessary and just Reader was not deemed good enough?
  2. Why there is no built-in role for this? I cannot believe anyone would agree to grant a service principal Key Vault Contributor where a mere Reader should be enough.