Build an Azure Application Gateway with Terragrunt

Build an Azure Application Gateway with Terragrunt

Introduction :-

We will see here how to build with Terragrunt an #Azure Application Gateway with:

An #Azure Application Gateway is a PaaS service that acts as a web traffic load balancer (layer 4 and layer 7), all its features are available here for information.

The following diagram illustrates a sample network topology of an #Azure Application Gateway. In the article, this resource is shown as a shared service managed by a unique Cyber Security team.

High-Level View

Script Workflow

The following chapters enumerate the steps used to build the Application Gateway.

Set up variables

Since these variables are re-used, a local block makes this more maintainable.

project.hcl (this is where basically we define the real values as a variable )

locals {
project_name = "<project_name>"
network_ddos_protection_plan_name = "ddos-leapwork"
zones    = ["1", "2", "3"]
}

Now let’s define the files that are basically required to create the application gateway resource,

terragrunt.hcl (which will call the value that we had defined as part of the local variable)

locals {environment_vars = read_terragrunt_config(find_in_parent_folders("env.hcl"))
   env_name = local.environment_vars.locals.environment
   account_vars = read_terragrunt_config(find_in_parent_folders("account.hcl"))
   local_dir = local.account_vars.locals.local_dir
   azure_account_id = local.account_vars.locals.azure_account_id
   azure_account_short_id = local.account_vars.locals.azure_account_short_id
   project_vars = read_terragrunt_config(find_in_parent_folders("project.hcl"))
   network_ddos_protection_plan_name= local.project_vars.locals.network_ddos_protection_plan_name
   project_name = local.project_vars.locals.project_name
   region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl"))
   azure_region = local.region_vars.locals.azure_region
   account_name   = local.account_vars.locals.account_name}terraform {
   source = "${path_relative_from_include()}"
}
include {
   path = find_in_parent_folders()
}
inputs = {
   env_name = local.env_name
   local_dir = local.local_dir
   location = local.azure_region
   azure_account_id = local.azure_account_id
   azure_account_short_id = local.azure_account_short_id
   project_name = local.project_name
   network_ddos_protection_plan_name = local.network_ddos_protection_plan_name
   account_name   = local.account_name
}

variable.tf (this file will populate the values from terragrunt.hcl and will pass it to the resource creation file)

variable "network_ddos_protection_plan_name" {
  type            = string
  description  = ""
}
variable "location" {
  type            = string
  description     = "The location where the resource group should be created. For a list of all Azure location"
}
variable "env_name" {
   type        = string
   description = "Type of environment ex: dev, stage or prod"
}
variable "local_dir" {
  type        = string
  description = "local account directory name"
}
variable "azure_region" {
  type        = string
  description = "azure location"
}
variable "project_name" {
  type        = string
  description = "project name"
}
variable "account_name" {
  type        = string
  description = "account name"
}

Public IP and Application Gateway

The Static public IP with the Standard SKU is a requirement when using Application Gateway v2 and Availability Zone-aware resources.

The Application Gateway with the upper resources with HTTP to HTTPS redirection.

Note:

Please do check as well how you can access ssl-certificate as a file that is required as part of https listeners creation.

data "terraform_remote_state" "app-gateway-subnet" {
  backend = "azurerm"
  config = {
   resource_group_name  = "terraform-state"
   storage_account_name = "<your storage account name>"
   container_name       = "terragruntbackned"
   key                  = "${var.env_name}/${var.local_dir}/${var.location}/${var.account_name}/subnets/terraform.tfstate"
  }
}
data "terraform_remote_state" "rg" {
   backend = "azurerm"
   config = {
   resource_group_name  = "terraform-state"
   storage_account_name = "<your storage account name>"
   container_name       = "terragruntbackned"
   key                  = "${var.env_name}/${var.local_dir}/${var.location}/${var.account_name}/resource-group/terraform.tfstate"
  }
}
data "terraform_remote_state" "controller-vm-nic" {
   backend = "azurerm"
   config = {
   resource_group_name  = "terraform-state"
   storage_account_name = "<your storage account name>"
   container_name       = "terragruntbackned"
   key                  = "${var.env_name}/${var.local_dir}/${var.location}/${var.account_name}/vms/controller-vm/terraform.tfstate"
  }
}
data "terraform_remote_state" "agent-vm-nic" {
   backend = "azurerm"
   config = {
   resource_group_name  = "terraform-state"
   storage_account_name = "<your storage account name>"
   container_name       = "terragruntbackned"
   key                  = "${var.env_name}/${var.local_dir}/${var.location}/${var.account_name}/vms/agent-vm/terraform.tfstate"
 }
}
resource "azurerm_public_ip" "app-gateway-public-ip" {
   name                = "app-gateway-public-ip-${var.project_name}-${var.env_name}"
   resource_group_name = data.terraform_remote_state.rg.outputs.rg_name
   location            = var.location
   allocation_method   = "Static"
   sku                 = "Standard"
 }
resource "azurerm_application_gateway" "app-gateway" {
  name                = "appgateway-${var.project_name}-${var.env_name}"
  resource_group_name = data.terraform_remote_state.rg.outputs.rg_name
  location            = var.location
  sku {
    name     = "Standard_v2"
    tier     = "Standard_v2"
   }
  autoscale_configuration {
    min_capacity = 0
    max_capacity = 10
   }
  gateway_ip_configuration {
    name      = "gateway-ip-configuration-${var.project_name}-${var.env_name}"
    subnet_id = data.terraform_remote_state.app-gateway-subnet.outputs.app_gateway_subnet_id
  }
  frontend_port {
    name = "http-frontend-port-${var.project_name}-${var.env_name}"
    port = 80
   }
  frontend_port {
    name = "https-frontend-port-${var.project_name}-${var.env_name}"
    port = 443
   }
  frontend_ip_configuration {
    name                 = "frontend-ip-configuration-${var.project_name}-${var.env_name}"
    public_ip_address_id = azurerm_public_ip.app-gateway-public-ip.id
  }
  backend_address_pool {
    name = "backend-address-pool-${var.project_name}-${var.env_name}"
  }
  ssl_certificate {
   name                = "${var.project_name}-kyvt-${var.env_name}"
   data                = filebase64("your wild card key")
  }
  backend_http_settings {
    name                  = "backend-https-setting-${var.project_name}-${var.env_name}"
    cookie_based_affinity = "Disabled"
    #path                  = "/"
    port                  = 443
    protocol              = "Https"
    request_timeout       = 60
  }
  backend_http_settings {
     name                  = "backend-http-setting-${var.project_name}-${var.env_name}"
     cookie_based_affinity = "Disabled"
     #path                  = "/"
     port                  = 80
     protocol              = "Http"
    request_timeout       = 60
   }
  http_listener {
    name                           = "http-listener-${var.project_name}-${var.env_name}"
    frontend_ip_configuration_name = "frontend-ip-configuration-${var.project_name}-${var.env_name}"
    frontend_port_name             = "http-frontend-port-${var.project_name}-${var.env_name}"
    protocol                       = "Http"
   }
  http_listener {
    name                           = "https-listener-${var.project_name}-${var.env_name}"
    frontend_ip_configuration_name = "frontend-ip-configuration-${var.project_name}-${var.env_name}"
    frontend_port_name             = "https-frontend-port-${var.project_name}-${var.env_name}"
    protocol                       = "Https"
    ssl_certificate_name           = "${var.project_name}-kyvt-${var.env_name}"
   }
  request_routing_rule {
    name                       = "routing-rule-${var.project_name}-${var.env_name}"
    rule_type                  = "Basic"
    http_listener_name         = "http-listener-${var.project_name}-${var.env_name}"
    backend_address_pool_name  = "backend-address-pool-${var.project_name}-${var.env_name}"
    backend_http_settings_name = "backend-http-setting-${var.project_name}-${var.env_name}"
  }
  request_routing_rule {
    name                       = "routing-rule-https-${var.project_name}-${var.env_name}"
    rule_type                  = "Basic"
    http_listener_name         = "https-listener-${var.project_name}-${var.env_name}"
    backend_address_pool_name  = "backend-address-pool-${var.project_name}-${var.env_name}"
    backend_http_settings_name = "backend-https-setting-${var.project_name}-${var.env_name}"
  }
}

Allow Application Gateway to point to a specific network interface

The Application Gateway will work as a load balancer for that particular VM which will receive HTTPS requests and send them to the backend as HTTP.

resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "controller_vm_backend_pool" {
  network_interface_id    = data.terraform_remote_state.controller-vm-nic.outputs.controller_vm_nic_id
  ip_configuration_name   = data.terraform_remote_state.controller-vm-nic.outputs.controller_vm_ip_configuration_name
  backend_address_pool_id = azurerm_application_gateway.app-gateway.backend_address_pool[0].id
}
resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "agent_vm_backend_pool" {
   network_interface_id    = data.terraform_remote_state.agent-vm-nic.outputs.agent_vm_nic_id
   ip_configuration_name   = data.terraform_remote_state.agent-vm-nic.outputs.agent_vm_ip_configuration_name
   backend_address_pool_id = azurerm_application_gateway.app-gateway.backend_address_pool[0].id
}

To monitor the application gateway, please use the below codes,

resource "azurerm_log_analytics_workspace" "leapwork-wks" {
 name = "${var.project_name}-hub-logaw-${var.env_name}" 
 location = var.location 
 resource_group_name = data.terraform_remote_state.rg.outputs.rg_name 
 sku   = "PerGB2018" #(Required) Specifies the Sku of the Log Analytics Workspace. Possible values are Free, PerNode, Premium, Standard, Standalone, Unlimited, and PerGB2018 (new Sku as of 2018-04-03).
 retention_in_days   = 100         #(Optional) The workspace data retention in days. Possible values range between 30 and 730. 
  #tags                = "leapwork-wks" 
} 

resource "azurerm_log_analytics_solution" "leapwork-agw-solution" {
  solution_name         = "AzureAppGatewayAnalytics"
  location            = var.location 
  resource_group_name = data.terraform_remote_state.rg.outputs.rg_name 
  workspace_resource_id = azurerm_log_analytics_workspace.leapwork-wks.id 
  workspace_name        = azurerm_log_analytics_workspace.leapwork-wks.name  
  plan 
   {    
      publisher = "Microsoft"    
      product   = "OMSGallery/AzureAppGatewayAnalytics"  
    } 
}