Terraform bind SSL Certificate to Azure WebApp - azure

I have recently been trying to bind a domain and an SSL certificate to a web app using Terraform in Azure.
I am having no luck in doing this and the documentation is a bit confusing / light on the ground.
The error I am getting when just doing a plan is:
Error: parsing "/subscriptions/<SUB-ID>/resourceGroups/Testing_Prod_KeyVault_JC/providers/Microsoft.KeyVault/vaults/secrets-testingprodjc": KeyVault Nested Item should contain 2 or 3 segments, got 8 from "subscriptions/<SUB-ID>/resourceGroups/Testing_Prod_KeyVault_JC/providers/Microsoft.KeyVault/vaults/secrets-testingprodjc"
I was wondering if anyone had been able to do this so far?
Here is my code for the Certificate and Domain bind:
//First Read the External Key Vault
data "azurerm_key_vault" "production_keyvault" {
name = "secrets-testingprodjc"
resource_group_name = "Testing_Prod_KeyVault_JC"
}
// Now Read the Certificate
data "azurerm_key_vault_certificate" "prod_certificate" {
name = "testing-certificate-for-cic"
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" {
hostname = "portal-staging-westeurope.jasoncontenttestingdomain.com"
app_service_name = azurerm_app_service.website_app.name
resource_group_name = azurerm_resource_group.Terraform.name
ssl_state = "SniEnabled"
thumbprint = azurerm_app_service_certificate.cert.thumbprint
}
/* // Following block NOT BEING USED
resource "azurerm_app_service_certificate_binding" "bind_certificate_to_webapp" {
hostname_binding_id = azurerm_app_service_custom_hostname_binding.website_app_hostname_bind.id
ssl_state = "SniEnabled"
thumbprint = azurerm_app_service_certificate.cert.thumbprint
}
*/
// Get Certificate from External KeyVault
resource "azurerm_app_service_certificate" "cert" {
name = "testing-certificate-for-cic"
resource_group_name = azurerm_resource_group.Terraform.name
location = azurerm_resource_group.Terraform.location
key_vault_secret_id = data.azurerm_key_vault.production_keyvault.id
}
I am just for now doing this with my logged-in user account, not a service principle I am aware of the service principal part but for now I am just testing this. My logged-in account does have access to the external keyvault with full rights.

I actually fixed this myself the other day with the following code, I found my answer on a GitHub repo for HashiCorp but I cant find the link now. It has to do with the resource azurerm_app_service_certificate if you use the key_vault_secret_id part it doesn't work you need to use pfx_blob.
Here is the code for anyone's ref:
//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
}
What I also noticed in my testing is you have to put the cert resource as a depends on on the bind. Its in my code but for clarity here is this piece of code:
depends_on = [
azurerm_app_service_certificate.cert,
]

You can try with this:
// First Read the External Key Vault
data "azurerm_key_vault" "production_keyvault" {
name = "secrets-testingprodjc"
resource_group_name = "Testing_Prod_KeyVault_JC"
}
// Now Read the Certificate
data "azurerm_key_vault_certificate" "prod_certificate" {
name = "testing-certificate-for-cic"
key_vault_id = data.azurerm_key_vault.production_keyvault.id
}
// Get Certificate from External KeyVault
resource "azurerm_app_service_certificate" "cert" {
name = "testing-certificate-for-cic"
resource_group_name = azurerm_resource_group.Terraform.name
location = azurerm_resource_group.Terraform.location
key_vault_secret_id = data.azurerm_key_vault.production_keyvault.id
}
// Now bind the webapp to the domain.
resource "azurerm_app_service_custom_hostname_binding" "website_app_hostname_bind" {
hostname = "portal-staging-westeurope.jasoncontenttestingdomain.com"
app_service_name = azurerm_app_service.website_app.name
resource_group_name = azurerm_resource_group.Terraform.name
}
// Now bind certificate to the webapp.
resource "azurerm_app_service_certificate_binding" "bind_certificate_to_webapp" {
hostname_binding_id = azurerm_app_service_custom_hostname_binding.website_app_hostname_bind.id
ssl_state = "SniEnabled"
certificate_id = azurerm_app_service_certificate.cert.id
}

may i know if this below solution working ?
// First Read the External Key Vault
data "azurerm_key_vault" "production_keyvault" {
name = "secrets-testingprodjc"
resource_group_name = "Testing_Prod_KeyVault_JC"
}
// Now Read the Certificate
data "azurerm_key_vault_certificate" "prod_certificate" {
name = "testing-certificate-for-cic"
key_vault_id = data.azurerm_key_vault.production_keyvault.id
}
// Get Certificate from External KeyVault
resource "azurerm_app_service_certificate" "cert" {
name = "testing-certificate-for-cic"
resource_group_name = azurerm_resource_group.Terraform.name
location = azurerm_resource_group.Terraform.location
key_vault_secret_id = data.azurerm_key_vault.production_keyvault.id
}
// Now bind the webapp to the domain.
resource "azurerm_app_service_custom_hostname_binding" "website_app_hostname_bind" {
hostname = "portal-staging-westeurope.jasoncontenttestingdomain.com"
app_service_name = azurerm_app_service.website_app.name
resource_group_name = azurerm_resource_group.Terraform.name
}
// Now bind certificate to the webapp.
resource "azurerm_app_service_certificate_binding" "bind_certificate_to_webapp" {
hostname_binding_id = azurerm_app_service_custom_hostname_binding.website_app_hostname_bind.id
ssl_state = "SniEnabled"
certificate_id = azurerm_app_service_certificate.cert.id
}

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 self signed certificates for Azure APIM cannot find

I have some self signed certificates and I want to use it with the APIM management, developer and proxy domains as below:
But I am getting this error:
creating/updating API Management Service "jananath-apim" (Resource Group "apim-appGw-RG"): apimanagement.ServiceClient#CreateOrUpdate: Failure sending request: StatusCode=400 -- Original
Error: Code="InvalidParameters" Message="Invalid parameter: Invalid certificate associated with DeveloperPortal. Error Message: Cannot find the requested object.\r\n."
And here's my terraform code:
apim.tf
resource "azurerm_api_management" "example" {
name = "jananath-apim"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
publisher_name = "Contoso"
publisher_email = "jbjayarathna#gmai.com"
sku_name = "Developer_1"
virtual_network_type = "Internal"
virtual_network_configuration {
subnet_id = azurerm_subnet.apimSubnet.id
}
hostname_configuration {
management {
host_name = var.managementHostname
certificate = base64encode("jananath-ssl.pfx")
certificate_password = var.managementCertPfxPassword
}
developer_portal {
host_name = var.portalHostname
certificate = base64encode("jananath-ssl.pfx")
certificate_password = var.portalCertPfxPassword
}
proxy {
host_name = var.gatewayHostname
certificate = base64encode("jananath-ssl.pfx")
certificate_password = var.gatewayCertPfxPassword
}
}
}
And the jananath-ssl.pfx is in the same path as the apim.tf
What I am doing wrong? Can someone help me?
base64encode just covers string to base64. It does not read the actual file. To read the file you would have to use:
base64encode(file("jananath-ssl.pfx"))
or filebase64:
filebase64("jananath-ssl.pfx")

Terraform 403 error when creating function app and storage account with private endpoint

I am getting a 403 forbidden when creating a function app that connects to its storage account via private endpoint inside a vnet. Storage account has firewall default action of 'Deny', and of course if I set it to 'Allow' it will work. I want this as 'Deny', however. Following this microsoft link if the function app and storage account are created in the same region with vnet, subnets, and private endpoints then it's supposed to work so I must be doing something wrong. I also tried changing the region for the storage account and it still resulted in a 403.
Error:
Error: web.AppsClient#CreateOrUpdate: Failure sending request: StatusCode=0 -- Original Error: Code="BadRequest" Message="There was a conflict. The remote server returned an error: (403) Forbidden." Details=[{"Message":"There was a conflict. The remote server returned an error: (403) Forbidden."},{"Code":"BadRequest"},{"ErrorEntity":{"Code":"BadRequest","ExtendedCode":"01020","Message":"There was a conflict. The remote server returned an error: (403) Forbidden.","MessageTemplate":"There was a conflict. {0}","Parameters":["The remote server returned an error: (403) Forbidden."]}}]
Here is my terraform code
resource "azurerm_function_app" "func" {
name = "${var.func_basics.name}-func"
location = var.func_basics.location
resource_group_name = var.func_basics.resource_group_name
app_service_plan_id = azurerm_app_service_plan.svc_plan.id
storage_account_name = azurerm_storage_account.func_sa.name
storage_account_access_key = azurerm_storage_account.func_sa.primary_access_key
version = var.runtime_version
https_only = true
depends_on = [
azurerm_storage_account.func_sa,
azurerm_app_service_plan.svc_plan,
azurerm_application_insights.func_ai,
azurerm_virtual_network.func_vnet
]
app_settings = merge(var.app_settings, local.additional_app_settings)
}
resource "azurerm_app_service_plan" "svc_plan" {
name = "${var.func_basics.name}-func-plan"
location = var.func_basics.location
resource_group_name = var.func_basics.resource_group_name
kind = "elastic"
sku {
tier = "ElasticPremium"
size = "EP1"
}
}
resource "azurerm_application_insights" "func_ai" {
name = "${var.func_basics.name}-func-appi"
location = var.func_basics.location
resource_group_name = var.func_basics.resource_group_name
application_type = var.ai_app_type
}
resource "azurerm_storage_account" "func_sa" {
name = "st${lower(replace(var.func_basics.name, "/[-_]*/", ""))}"
resource_group_name = var.func_basics.resource_group_name
location = var.func_basics.location
account_tier = var.sa_settings.tier
account_replication_type = var.sa_settings.replication_type
account_kind = "StorageV2"
enable_https_traffic_only = true
min_tls_version = "TLS1_2"
depends_on = [
azurerm_virtual_network.func_vnet
]
network_rules {
default_action = "Deny"
virtual_network_subnet_ids = [azurerm_subnet.func_endpoint_subnet.id]
bypass = [
"Metrics",
"Logging",
"AzureServices"
]
}
}
resource "azurerm_virtual_network" "func_vnet" {
name = "${var.func_basics.name}-func-vnet"
resource_group_name = var.func_basics.resource_group_name
location = var.func_basics.location
address_space = ["10.0.0.0/16"]
}
resource "azurerm_subnet" "func_service_subnet" {
name = "${var.func_basics.name}-func-svc-snet"
resource_group_name = var.func_basics.resource_group_name
virtual_network_name = azurerm_virtual_network.func_vnet.name
address_prefixes = ["10.0.1.0/24"]
enforce_private_link_service_network_policies = true
service_endpoints = ["Microsoft.Storage"]
delegation {
name = "${var.func_basics.name}-func-del"
service_delegation {
name = "Microsoft.Web/serverFarms"
actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
}
}
}
resource "azurerm_subnet" "func_endpoint_subnet" {
name = "${var.func_basics.name}-func-end-snet"
resource_group_name = var.func_basics.resource_group_name
virtual_network_name = azurerm_virtual_network.func_vnet.name
address_prefixes = ["10.0.2.0/24"]
enforce_private_link_endpoint_network_policies = true
}
resource "azurerm_private_endpoint" "func_req_sa_blob_endpoint" {
name = "${var.func_basics.name}-func-req-sa-blob-end"
resource_group_name = var.func_basics.resource_group_name
location = var.func_basics.location
subnet_id = azurerm_subnet.func_endpoint_subnet.id
private_service_connection {
name = "${var.func_basics.name}-func-req-sa-blob-pscon"
private_connection_resource_id = azurerm_storage_account.func_sa.id
is_manual_connection = false
subresource_names = ["blob"]
}
}
resource "azurerm_private_endpoint" "func_req_sa_file_endpoint" {
name = "${var.func_basics.name}-func-req-sa-file-end"
resource_group_name = var.func_basics.resource_group_name
location = var.func_basics.location
subnet_id = azurerm_subnet.func_endpoint_subnet.id
private_service_connection {
name = "${var.func_basics.name}-func-req-sa-file-pscon"
private_connection_resource_id = azurerm_storage_account.func_sa.id
is_manual_connection = false
subresource_names = ["file"]
}
}
resource "azurerm_app_service_virtual_network_swift_connection" "func_vnet_swift" {
app_service_id = azurerm_function_app.func.id
subnet_id = azurerm_subnet.func_service_subnet.id
}
locals {
additional_app_settings = {
"APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_application_insights.func_ai.instrumentation_key
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" = azurerm_storage_account.func_sa.primary_connection_string
"AzureWebJobsStorage" = azurerm_storage_account.func_sa.primary_connection_string
"WEBSITE_VNET_ROUTE_ALL" = "1"
"WEBSITE_CONTENTOVERVNET" = "1"
"WEBSITE_DNS_SERVER" = "168.63.129.16"
}
}
It seems that it's a common error message when you create an Azure function where the storage account of the function is added to the Virtual Network, read here for more details.
To resolve it, you can use the local-exec Provisioner to invoke the az CLI command to deny the traffic after all of the provisions are finished.
az storage account update --name storage_account_name --resource-group reource_group_name --default-action 'Deny' --bypass 'AzureServices', 'Logging', 'Metrics'
Alternatively, you can separately configure the storage account network rules. You may need to allow your client's IP to access the storage account.
resource "azurerm_storage_account_network_rules" "test" {
resource_group_name = var.resourceGroupName
storage_account_name = azurerm_storage_account.func_sa.name
default_action = "Deny"
bypass = [
"Metrics",
"Logging",
"AzureServices"
]
ip_rules = ["x.x.x.x"]
depends_on = [
azurerm_storage_account.func_sa,
azurerm_app_service_plan.svc_plan,
azurerm_application_insights.func_ai,
azurerm_virtual_network.func_vnet,
azurerm_function_app.func
]
}
In addition, there is a possible solution for this similar case on Github.
I've had this issue in the past and found that it can be resolved as follows. I've tested this on v3.3.0 of the provider using the azurerm_windows_function_app resource. I think currently this is an Azure problem, in that it if you don't supply a share it will try and create one but will be denied. You'd expect this to work if Allow Azure services on the trusted services list to access this storage account is enabled, but webapps aren't trusted.
Create your storage account with IP rules and deny
Create a share within this for your function app content
within the function set the following configuration settings
WEBSITE_CONTENTAZUREFILECONNECTIONSTRING = <storage_account.primary_connection_string>
WEBSITE_CONTENTSHARE = <your share>
WEBSITE_CONTENTOVERVNET = 1
In the functions site configuration set the attribute vnet_route_all_enabled = true

Could not read output attribute from remote state datasource

I am new to terraform so I will attempt to explain with the best of my ability. Terraform will not read in the variable/output from the statefile and use that value in another file.
I have tried searching the internet for everything I could find to see if anyone how has had this problem and how they fixed it.
###vnet.tf
#Remote State pulling data from bastion resource group state
data "terraform_remote_state" "network" {
backend = "azurerm"
config = {
storage_account_name = "terraformstatetracking"
container_name = "bastionresourcegroups"
key = "terraform.terraformstate"
}
}
#creating virtual network and putting that network in resource group created by bastion.tf file
module "quannetwork" {
source = "Azure/network/azurerm"
resource_group_name = "data.terraform_remote_state.network.outputs.quan_netwk"
location = "centralus"
vnet_name = "quan"
address_space = "10.0.0.0/16"
subnet_prefixes = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
subnet_names = ["subnet1", "subnet2", "subnet3"]
tags = {
environment = "quan"
costcenter = "it"
}
}
terraform {
backend "azurerm" {
storage_account_name = "terraformstatetracking"
container_name = "quannetwork"
key = "terraform.terraformstate"
}
}
###resourcegroups.tf
# Create a resource group
#Bastion
resource "azurerm_resource_group" "cm" {
name = "${var.prefix}cm.RG"
location = "${var.location}"
tags = "${var.tags}"
}
#Bastion1
resource "azurerm_resource_group" "network" {
name = "${var.prefix}network.RG"
location = "${var.location}"
tags = "${var.tags}"
}
#bastion2
resource "azurerm_resource_group" "storage" {
name = "${var.prefix}storage.RG"
location = "${var.location}"
tags = "${var.tags}"
}
terraform {
backend "azurerm" {
storage_account_name = "terraformstatetracking"
container_name = "bastionresourcegroups"
key = "terraform.terraformstate"
}
}
###outputs.tf
output "quan_netwk" {
description = "Quan Network Resource Group"
value = "${azurerm_resource_group.network.id}"
}
When running the vnet.tf code it should read in the output from the outputs.tf which is stored in the azure backend storage account statefile file and use that value for the resource_group_name in the quannetwork module. Instead it creates a resource group named data.terraform_remote_state.network.outputs.quan_netwk. Any help would be greatly appreciated.
First, you need to input a string for the resource_group_name in your module quannetwork, not the resource group Id.
Second, if you want to quote something in the remote state, do not just put it in the Double quotes, the right format below:
resource_group_name = "${data.terraform_remote_state.network.outputs.quan_netwk}"

Resources