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_namelocation            = var.locationallocation_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_namelocation            = var.locationsku {name     = "Standard_v2"tier     = "Standard_v2"}autoscale_configuration {min_capacity = 0max_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                  = 443protocol              = "Https"request_timeout       = 60}backend_http_settings {name                  = "backend-http-setting-${var.project_name}-${var.env_name}"cookie_based_affinity = "Disabled"#path                  = "/"port                  = 80protocol              = "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_idip_configuration_name   = data.terraform_remote_state.controller-vm-nic.outputs.controller_vm_ip_configuration_namebackend_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_idip_configuration_name   = data.terraform_remote_state.agent-vm-nic.outputs.agent_vm_ip_configuration_namebackend_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" { 2  name                = "${var.project_name}-hub-logaw-${var.env_name}" 3  location            = var.location 4  resource_group_name = data.terraform_remote_state.rg.outputs.rg_name 5  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). 6  retention_in_days   = 100         #(Optional) The workspace data retention in days. Possible values range between 30 and 730. 7  #tags                = "leapwork-wks" 8} 9 10resource "azurerm_log_analytics_solution" "leapwork-agw-solution" { 11  solution_name         = "AzureAppGatewayAnalytics" 12  location            = var.location 13  resource_group_name = data.terraform_remote_state.rg.outputs.rg_name 14  workspace_resource_id = azurerm_log_analytics_workspace.leapwork-wks.id 15  workspace_name        = azurerm_log_analytics_workspace.leapwork-wks.name 16 17  plan { 18    publisher = "Microsoft" 19    product   = "OMSGallery/AzureAppGatewayAnalytics" 20  } 21}