While the introduction of RBAC roles for key vaults is already written in history the support of this from the terraform module azurerm_key_vault has been lacking a bit. This issue has been reported on the different GitHub fora as issues and even PRs were created but due to reasons never completed. Questions remain about how to resolve setting certificate contacts on your key vault resource when using RBAC. Short answer: You can’t, however, there is a workaround that I will describe in this blog.
Problem
When you create an azurerm_key_vault resource you want to set your certificate contacts so they will get the email notification when certificates expire. To be able to set this value you need to have the manage contacts access policy to be able to set it. This has been solved when you use access policies by being able to set the access policies either on the separate azurerm_key_vault_access_policy resource or, on the azurerm_key_vault itself. Using the latter gives you the possibility to grant your service principal the correct privileges to set the contacts before doing so.
resource "azurerm_key_vault" "main" {
name = var.keyvault_name
location = var.keyvault_location
resource_group_name = var.resource_group_name
sku_name = var.sku_name
tenant_id = data.azurerm_client_config.current.tenant_id
enabled_for_deployment = var.enabled_for_deployment
enabled_for_disk_encryption = var.enabled_for_disk_encryption
enabled_for_template_deployment = var.enabled_for_template_deployment
purge_protection_enabled = false
soft_delete_retention_days = 7
access_policy {
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
certificate_permissions= [
"ManageContacts",
]
}
contact {
name = var.support_name
email = var.support_email
}
network_acls {
default_action = "Allow"
bypass = "AzureServices"
}
}
When you want to change this to RBAC however there is no way to add the roles prior to or at the same time of creation. You can only apply it after the key vault is there. So the following code will not work cause, at create time you will not have the “Key Vault Certificates Officer” role needed to manage the contacts. Resulting in an error.
resource "azurerm_key_vault" "main" {
name = var.keyvault_name
location = var.keyvault_location
resource_group_name = var.resource_group_name
sku_name = var.sku_name
tenant_id = data.azurerm_client_config.current.tenant_id
enabled_for_deployment = var.enabled_for_deployment
enabled_for_disk_encryption = var.enabled_for_disk_encryption
enabled_for_template_deployment = var.enabled_for_template_deployment
enable_rbac_authorization = true
purge_protection_enabled = false
soft_delete_retention_days = 7
contact {
name = var.support_name
email = var.support_email
}
network_acls {
default_action = "Allow"
bypass = "AzureServices"
}
}
resource "azurerm_role_assignment" "rbac_role" {
scope = azurerm_key_vault.main.id
role_definition_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/providers/Microsoft.Authorization/roleDefinitions/a4417e6f-fecd-4de8-b567-7b0420556985"
principal_id = data.azurerm_client_config.current.object_id
}

Solution
While we are still waiting for a proper native solution from the provider but that can take a while. Here is a workaround to at least let you use RBAC roles and not require you to deploy twice. The workaround consists of removing the creation of contacts from the key vault module and using a custom resource with some az-cli to manage this for you after you set the correct RBAC. Make sure that you only add contacts if they don’t already exist of course and, add a lifecycle for the contacts that will not be cleared on re-deploying your infrastructure.
resource "azurerm_key_vault" "main" {
name = var.keyvault_name
location = var.keyvault_location
resource_group_name = var.resource_group_name
sku_name = var.sku_name
tenant_id = data.azurerm_client_config.current.tenant_id
enabled_for_deployment = var.enabled_for_deployment
enabled_for_disk_encryption = var.enabled_for_disk_encryption
enabled_for_template_deployment = var.enabled_for_template_deployment
enable_rbac_authorization = true
purge_protection_enabled = false
soft_delete_retention_days = 7
lifecycle {
ignore_changes = [contact]
}
network_acls {
default_action = "Allow"
bypass = "AzureServices"
}
}
resource "azurerm_role_assignment" "rbac_role" {
scope = azurerm_key_vault.main.id
role_definition_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/providers/Microsoft.Authorization/roleDefinitions/a4417e6f-fecd-4de8-b567-7b0420556985"
principal_id = data.azurerm_client_config.current.object_id
}
resource "null_resource" "add_contacts" {
provisioner "local-exec" {
// add az cli command to add certificate contact to keyvault when it does not exists already
command = "az keyvault certificate contact list --vault-name ${var.keyvault_name} | jq -r '.[].emailAddress' | grep -q ${var.support_email} || az keyvault certificate contact add --vault-name ${var.keyvault_name} --email ${var.support_email} --name ${var.support_name}"
when = create
}
depends_on = [
azurerm_role_assignment.rbac_role
]
}
The first part of the command will generate an error cause it cannot find the contacts. That will trigger the second part of the command that will create it. If the first part will find the correct contacts it will leave the creation part alone.

Conclusion
I really don’t like workarounds but sometimes you need to. In this case, I can take over the state configuration and add the creation of the contacts through another tool while still being inside the terraform modules. This way I keep the context the same and my configuration in one place and do not have the need for some post-processing steps after deployment.
Have fun,
Erick
You must be logged in to post a comment.