Using Azure CDN with Terraform

This is a brief post about how I've configured and used an Azure CDN to serve a static website, it briefly looks at what a CDN is and how I've used some of the rules to enfroce HTTPS.

What is a CDN

Think of a CDN (content delivery network) as a bunch of servers thath hold copies of your content around the globe.

The aim of a CDN is to improve the load speed of static content, so images, CSS, JavaScript files etc. It does this by copying the assets stored in blob storage then distributing it across it's network of server globally.

When a user then makes a request for the content, instead of grabbing stuff from the same server as the website the CDN would serve up it up instead, from a more "local" server.

This means that the content get's to the user faster and therefore improves the load speed of site pages etc, firstly by reducing the "distance" traveled, but also by reducing the load on the webserver.

There are more features to a CDN than this and I will cover the ones I've used here, but for now the above explination is a oversimplification of what a CDN does.

A more thorough explanation can bee found in the microsoft documentation

Configuring an Azure CDN in Terraform

An Azure CDN consists of two parts, the profile and the endpoint.

The profiles main purpose is to have a central location to manage the different endpoints, as well as manage some of the billing aspects.

resource "azurerm_cdn_profile" "jevason-cdn-profile" {
  name                = "jevason"
  location            = "North Europe"
  resource_group_name = azurerm_resource_group.jevason-blog-rg.name
  sku                 = "Standard_Microsoft"

  tags = {
    environment = "Production"
    cost_center = "jevason"
  }
}

The next step is to include the Endpoint definition:

resource "azurerm_cdn_endpoint" "jevason-cdn-endpoint" {
  name                = "jevason"
  profile_name        = azurerm_cdn_profile.jevason-cdn-profile.name
  location            = azurerm_cdn_profile.jevason-cdn-profile.location
  resource_group_name = azurerm_resource_group.jevason-blog-rg.name
  is_http_allowed     = true
  is_https_allowed    = true

  delivery_rule {
    name = "EnforceHttps"
    order = 1
    request_scheme_condition {
      operator = "Equal"
      match_values = ["HTTP"]
    }
    url_redirect_action {
      redirect_type = "Found"
      protocol = "Https"
    }

  }

  origin {
    name      = "StaticSite"
    host_name = azurerm_storage_account.static-site.primary_web_host
  }

  origin_host_header = azurerm_storage_account.static-site.primary_web_host
}

What's going on here then?

The profile is quiet simple, it gets attached onto the resource group and in this case I've used the Microsoft SKU, as it's the cheaper option.

The really interesting stuff is in the endpoint definition.

In the usual way I've configured those shared parts, like the resource group and the CDN profile details that the Endpoint is being added to.

The next part is the is_http_allowed = true this is true by default, but in my experimentation I had been playing with the options of setting it to false, but this means that contnet simply isn't served on that port and a generic Azure error is returned.

Underneath this I then have a deliver rule block.

The block has a name and an order to which is should run. As it's the only rule I've set the value to 1 but the documentation suggests anything greater than 1 will be ran as a preference.

The othe two blocks are the request_scheme_condition and the url_redirect_action these work together to basically take and HTTP request and perform a 302 "Found" redirect to a HTTPS endpoint.

This menas that all insecure http requests will be redirected and upgraded to HTTPS.

Cool.

The last sections are to do with the content that's being fed into the CDN, if you've read my earlier posts you'll hopefully recognise some of those bits.

If not I'll briefly go over it.

This site is developed in React, I use Azure DevOps to run NPM and build the site, the resultant html, css and javascript in the build folder I then copy over to blob storage (also managed in Terraform) that I have configured to aact as a webserver.

It's this "webserver" whose primarry web address is being requested on this line

host_name = azurerm_storage_account.static-site.primary_web_host

BUT that's not all, I've also included the same endpoint for the origin_host_header arguement. Terraform docs state that it's optional, but in my experience it's not. I had a bit of trouble getting it to run in Azure DevOps, including this section fixed that.

Final thoughts

So there you have it, using Terraform to configure a CDN to server up a static web site and enfore HTTPS on any incoming requests.