Terraform - Azure application gateway issue with keyvault certificate integration - azure

I am trying to deploy application g/w with ssl certificate from key vault. It is prompting error as SecretIdSpecifiedIsInvalid when I run the terraform apply …Even though it is showing correct certificate id and name on error code which I can validate manually on portal.
I am also able to deploy app gateway manually using the same certificate from keyvault.
│ Error: creating Application Gateway: (Name “poc-appgw-iaps” /
Resource Group “poc-rg-appgw”):
network.ApplicationGatewaysClient#CreateOrUpdate: Failure sending
request: StatusCode=400 – Original Error:
Code=“SecretIdSpecifiedIsInvalid” Message=“SecretId
‘https://pockv-iaps.vault.azure.net/certificates/poc-cert-admin/xxxxxxxxxx’
specified in
‘/subscriptions/xxxxxxxxxxxxxxx/resourceGroups/poc-rg-appgw/providers/Microsoft.Network/applicationGateways/poc-appgw-iaps/sslCertificates/poc-cert-admin’
is invalid.” Details=[]

Initially please try solve this problem by upgrading to the latest
azurerm terraform provider. The latest should contain fixes for
the situation if provision is all correct.
The ssl certificate block must contain your PFX certificate. Data
must be used if key vault secret_id is not already set.
Key vault secret id of base-64 encoded unencrypted pfx
certificate/secret must be stored in Azure KeyVault.
Please note that to enable the above feature , azure key vault soft delete must be anabled
Please make sure to have required access policies to get secrets .
provider "azurerm" {
features{}
}
data "azurerm_client_config" "current" {}
resource "azurerm_user_assigned_identity" "base" {
resource_group_name = "resourcegroup"
location = "resgrouplocation"
name = "appgwkeyvault"
}
data "azurerm_key_vault" "example"{
name = "keyvault-name"
resource_group_name = "resourcegroup"
}
resource "azurerm_key_vault_access_policy" "example" {
key_vault_id = data.azurerm_key_vault.example.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = azurerm_user_assigned_identity.base.principal_id
key_permissions = [
"Get",
]
certificate_permissions = [
"Get",
]
secret_permissions = [
"Get",
]
}
output "secret_identifier" {
value = azurerm_key_vault_certificate.example.secret_id
}
//TODO required soft delete on the keyvault
ssl_certificate {
name = "app_listener"
key_vault_secret_id = azurerm_key_vault_certificate.example.secret_id
}
Please make sure certificate properties are properly given , secrets must be .pfx format
resource "azurerm_key_vault_certificate" "example" {
name = "imported-cert"
key_vault_id = azurerm_key_vault.kv.id
//make sure certificate is base64 encoded pfx certificate
certificate {
contents = filebase64("C:/appgwlistener.pfx")
password = "password"
}
certificate_policy {
...
}
key_properties {
exportable = true
key_size = 2048
key_type = "RSA"
reuse_key = false
}
secret_properties {
content_type = "application/x-pkcs12"
}
}
}
Below references can guide you:
Terraform - How to attach SSL certificate stored in Azure KeyVault
to an Application Gateway - Stack Overflow
key_vault_secret_id azure_application_gateway| Terraform Registry

Related

How to import a an azure web app certificate using terraform from an azure key vault

I have a certificate for an app service in an azure keyvault, I want to import a key vault certificate to my web app in terraform but am not sure where i would refer to the keyvault in the below example?
resource "azurerm_app_service_certificate_binding" "example" {
hostname_binding_id = azurerm_app_service_custom_hostname_binding.example.id
certificate_id = azurerm_app_service_managed_certificate.example.id
ssl_state = "SniEnabled"
}
To bind the existing key vault certificate with your webapp need to use as mentioned below by #json we need to first call key vault certificate using data then bind with webapp .
//First Read the External Key Vault
data "azurerm_key_vault" "production_keyvault" {
name = "testingkeyvault2022"
resource_group_name = "KeyVaultWestEuropeBackend"
}
// Now Read the Certificate
data "azurerm_key_vault_secret" "prod_certificate" {
name = "testcert"
key_vault_id = data.azurerm_key_vault.production_keyvault.id
}
// Now bind the webapp to the domain and look for certificate.
resource "azurerm_app_service_custom_hostname_binding" "website_app_hostname_bind" { //Website App
depends_on = [
azurerm_app_service_certificate.cert,
]
hostname = var.websiteurlbind
app_service_name = data.azurerm_app_service.read_website_app.name
resource_group_name = data.azurerm_resource_group.Terraform.name
ssl_state = "SniEnabled"
thumbprint = azurerm_app_service_certificate.cert.thumbprint
}
// Get Certificate from External KeyVault
resource "azurerm_app_service_certificate" "cert" {
name = "testingcert"
resource_group_name = data.azurerm_resource_group.Terraform.name
location = data.azurerm_resource_group.Terraform.location
pfx_blob = data.azurerm_key_vault_secret.prod_certificate.value
}
Note:- I have not tested due to some access issue from my end,but it should work.
Please find this SO THREAD for more information.
If still the issue persists please re-open at this GitHub issue.

Unable to attach the certificate from a keyvault in a different subscription to an app service

The app service is in subscription1 and the keyvault is in subscription2, I want to attach the certificate in the keyvault to the appservice during the terraform deployment and I keep getting the error Error: Unable to determine the Resource ID for the Key Vault at URL "https://.vault.azure.net/"
If I skip the certificate and secret data source, and use the keyvault_secret_id, I get the error that Code="Forbidden" Message="Client address is not authorized and caller is
not a trusted service.
This is the below code:
data "azurerm_key_vault" "kvprod" {
provider = azurerm.<alias>
name = "<keyvaultname>"
resource_group_name = "<keyvaultrgname>"
}
data "azurerm_key_vault_certificate" "kvcertificate" {
provider = azurerm.<alias>
name = "<certifinatename>"
key_vault_id = data.azurerm_key_vault.kvprod.id
}
data "azurerm_key_vault_secret" "kvsecret" {
provider = azurerm.<alias>
name = data.azurerm_key_vault_certificate.kvcertificate.name
key_vault_id = data.azurerm_key_vault.kvprod.id
}
resource "azurerm_app_service_certificate" "certificate" {
name = "<certifinatename>"
location = data.azurerm_resource_group.<appservicerg>.location
resource_group_name = data.azurerm_resource_group.<appservicerg>.name
pfx_blob = data.azurerm_key_vault_secret.kv.value
#key_vault_secret_id = "<keyvaultID>"
}
One thing you have to do is to ad app service principal to key vault policy
data "azuread_service_principal" "web_app_resource_provider" {
application_id = "abfa0a7c-a6b6-4736-8310-5855508787cd"
}
resource "azurerm_key_vault_access_policy" "web_app_resource_provider" {
key_vault_id = module.key_vault.key_vault_id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azuread_service_principal.web_app_resource_provider.id
secret_permissions = ["Get"]
certificate_permissions = ["Get"]
}
That beeing said you will get another error
Error: Unable to determine the Resource ID for the Key Vault at URL
It seems that terraform is unable to create app service cert in diffrent resource group and subsciprtion.

Terraform does not wait for update of azurerm_key_vault_secret.xyz.id

We are using terraform to build my azure resources with azurerm provider.
We are injecting a secret during the terraform run and this secret may change from time to time.
We use a azurerm_key_vault_secret to store the secret and a function app with managed identity (that has got reading access to the key vault) that receives the secret like this:
resource "azurerm_key_vault_secret" "my_secret" {
name = "my-secret"
value = var.my_secret
key_vault_id = azurerm_key_vault.default.id
}
resource "azurerm_function_app" "app" {
name = "..."
app_settings = {
MySecret = "#Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.my_secret.id})"
}
identity {
type = "SystemAssigned"
}
...
}
When i run terraform apply and the secret is changed, the function app points to the old version of the secret. It seems the azurerm_key_vault_secret.my_secret.id is being read before the secret was updated.
Does anybody have any idea, how I can make sure the function_app will wait for the update of the secret?
(And yes, the id changes and I also don't like it, but that is how the provider works.)
When you are updating a key vault secret then the change is handled by Key vault UI . So Terraform won't detect the changes on azurerm_key_vault_secret.example.id and thus the reference's also won't be modified .
As a Workaround , You can use a data source for the same key vault secret and provide it in the function-app as shown in the below code , so that all the changes done in key vault secret can be read from data source and the changes can be applied accordingly :
resource "azurerm_key_vault_secret" "example" {
name = "functionappsecret"
value = "changedpassword"
key_vault_id = azurerm_key_vault.example.id
}
data "azurerm_key_vault_secret" "secret" {
name="functionappsecret"
key_vault_id = azurerm_key_vault.example.id
depends_on = [
azurerm_key_vault_secret.example
]
}
resource "azurerm_function_app" "example" {
name = "ansuman-azure-functions"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
app_service_plan_id = azurerm_app_service_plan.example.id
storage_account_name = azurerm_storage_account.example.name
storage_account_access_key = azurerm_storage_account.example.primary_access_key
app_settings = {
MySecret = "#Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.secret.id})"
}
identity {
type="SystemAssigned"
}
}
Ouptut:

Configuring an app service certificate seems to require write access to the Key Vault, IBYP?

The following terraform configuration is supposed to:
Obtain the id of the relevant Key Vault
Obtain the id of the certificate secret
Setup custom hostname binding
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:
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:
Why on earth this complication is even necessary and just Reader was not deemed good enough?
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.

Moving Certificate from Keyvault to another Keyvault in a diffrent subscription

I am trying to find some way of moving my certificates from a Key Vault in one Azure Subscription to another Azure subscription. Is there anyway of doing this>
Find below an approach to move a self-signed certification created in Azure Key Vault assuming it is already created.
--- Download PFX ---
First, go to the Azure Portal and navigate to the Key Vault that holds the certificate that needs to be moved. Then, select the certificate, the desired version and click Download in PFX/PEM format.
--- Import PFX ---
Now, go to the Key Vault in the destination subscription, Certificates, click +Generate/Import and import the PFX file downloaded in the previous step.
If you need to automate this process, the following article provides good examples related to your question:
https://blogs.technet.microsoft.com/kv/2016/09/26/get-started-with-azure-key-vault-certificates/
I eventually used terraform to achieve this. I referenced the certificates from the azure keyvault secret resource and created new certificates.
the sample code here.
terraform {
required_version = ">= 0.13"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=3.17.0"
}
}
}
provider "azurerm" {
features {}
}
locals {
certificates = [
"certificate_name_1",
"certificate_name_2",
"certificate_name_3",
"certificate_name_4",
"certificate_name_5",
"certificate_name_6"
]
}
data "azurerm_key_vault" "old" {
name = "old_keyvault_name"
resource_group_name = "old_keyvault_resource_group"
}
data "azurerm_key_vault" "new" {
name = "new_keyvault_name"
resource_group_name = "new_keyvault_resource_group"
}
data "azurerm_key_vault_secret" "secrets" {
for_each = toset(local.certificates)
name = each.value
key_vault_id = data.azurerm_key_vault.old.id
}
resource "azurerm_key_vault_certificate" "secrets" {
for_each = data.azurerm_key_vault_secret.secrets
name = each.value.name
key_vault_id = data.azurerm_key_vault.new.id
certificate {
contents = each.value.value
}
}
wrote a post here as well

Resources