Azure CDN returns "InvalidUri" when using Terraform
In hindsight, I could have probably solved this issue a lot quicker, but the error was so vague and the fact that CDN changes take some time to propagate, it took me more effort than I wanted. So, here we are, I'm writing this blog post in the hopes of saving you some time. I ran into some connectivity errors when connecting an Azure CDN to an Azure blob storage. But this might also apply to your web apps.
TL;DR: Go to solution
The setup
Our infrastructure setup is straightforward, centred around using Azure Blob Storage to host files like software installers, which are then distributed via Azure CDN to ensure global accessibility with low latency. A custom DNS was integrated with Azure CDN (and an Azure-managed SSL) to provide a branded and easy-to-remember access point for these files. The entire setup, from storage accounts to CDN configurations and custom DNS mapping, was provisioned and managed using Terraform. As a first setup, this is quite straightforward, and the terraform is relatively easy. It validates, plans and applies correctly, and even tfsec didn't warn me about any errors. So we deployed the terraform with success.
resource "azurerm_resource_group" "downloads" {
name = local.resource_group_name_with_location
location = var.location
}
resource "azurerm_storage_account" "downloads" {
name = local.escaped_random_name_24
resource_group_name = azurerm_resource_group.downloads.name
location = var.location
account_tier = "Standard"
account_replication_type = "LRS"
min_tls_version = "TLS1_2"
}
resource "azurerm_cdn_profile" "resources" {
name = "${local.name}-cdn"
location = var.location
resource_group_name = azurerm_resource_group.downloads.name
sku = "Standard_Verizon"
}
resource "azurerm_cdn_endpoint" "resources" {
name = "${local.name_with_suffix}-cdne"
profile_name = azurerm_cdn_profile.resources.name
location = var.location
resource_group_name = azurerm_resource_group.downloads.name
is_http_allowed = false
is_compression_enabled = true
content_types_to_compress = [
"application/x-msdownload" # For .exe files
]
origin {
name = "blob"
host_name = azurerm_storage_account.downloads.primary_blob_host
}
}
The problem
After it was provisioned we had to wait a couple of minutes, and sometimes a couple of hours while CDN got its configuration propagated. First, you will see HTTP 404 response and after a while, it should work. However, this was not the case. The error we were presented with was the following "InvalidUri" message.
<Error>
<Code>InvalidUri</Code>
<Message>The request URI is invalid. RequestId:bc46aeba-601e-0065-54b2-76b9e0000000 Time:2024-03-15T08:25:55.7419100Z
</Message>
</Error>
Diagnosis
So, first things first, we checked if the blob storage was working and if we could download the files using the storage account URL ***.blob.core.windows.net/downloads/installer.exe
and these were working just fine. So we can establish that the link between the CDN and the storage account is not correct. Thus I assumed a configuration error from my side.
Different Options
Another thing that didn't help to wrap my head around the problem is that there are multiple CDN options in Azure. Furthermore, everything has been consolidated in "Azure Front Door and CDN profiles", so whenever you create one manually. The options are:
- Microsoft CDN (classic)
- Microsoft Standard Edgio
- Microsoft Premium Edgio
- Microsoft Azure Front Door (more expansive and feature richt)
So I tried all of these options, as you can manually create them from your blob storage. And obviously, these were working out of the box. 😅
Having identified that 1; there is a configuration error, and 2; it is most likely caused by the way we create the Azure CDN profiles, we can narrow down our search. My previous searches (and assistance of ChatGPT) didn't get a hit, but having identified Terraform as a potential configuration problem, I was able to narrow down the search.
The solution
The problem is that the terraform template is missing the property origin_host_header
. My next step would have been to manually compare the different endpoint configurations between the one created by Terraform and the one manually created, but I was able to find a Github issue with the same problem. It was one single property that I unfortunately overlooked. As you can see in the picture below, it is explained by inspecting this property in the Azure Portal that the Blob Storage and Web Apps require the origin host header to be specified. This is done for you when you manually create the CDN.
Thus, the correct terraform template should be like this for your CDN:
resource "azurerm_cdn_endpoint" "resources" {
name = "${local.name_with_suffix}-cdne"
profile_name = azurerm_cdn_profile.resources.name
location = var.location
resource_group_name = azurerm_resource_group.downloads.name
is_http_allowed = false
is_compression_enabled = true
content_types_to_compress = [
"application/x-msdownload" # For .exe files
]
# ORIGIN_HOST_HEADER is required for connecting with Web Apps, Blob Storage and Cloud Services
origin_host_header = azurerm_storage_account.downloads.primary_blob_host
origin {
name = "blob"
host_name = azurerm_storage_account.downloads.primary_blob_host
}
}
Conclusion
It felt a bit counterintuitive to link two properties to the primary blob host, but it is necessary for it to work.
I hope this post has helped you identify your problem working with Azure CDN and blob storage, provisioned by Terraform. However, I must give credit to Félix Prado as he was the one who was able to resolve this in his own Github issue.