How to automate Azure AD Application Proxy?

This is part 1 of a 2 part series. Part 2 will be about configuring the applications for use with the proxy.

Zero trust security is a concept that has gained significant attention in recent years. It is a security model that assumes that every request, whether internal or external, is a potential threat. To mitigate these threats, zero trust security requires strict identity verification and access controls.

One tool that can help implement zero trust security is Azure AD Application Proxy.

In this blog post, we will explore how Azure AD Application Proxy can be used to implement zero trust security and provide secure remote access to internal web applications.

Azure AD Application proxy is often thought of as just for on-premises applications but it works perfect for anything running in your virtual network too whether that's something running on a Virtual Machine or on a Kubernetes cluster.

In this blog series I will expose a demo virtual machine using Azure AD Application proxy and it will all be automated end to end.

  • I will be setting up the connector on a Windows Server Core 2022 Virtual Machine.
  • I will use Terraform as my Infrastructure as Code poison of choice for the Azure resources.
  • I am deploying to a tenant with the required license for AzureAD application proxy (Azure AD Premium P1 or Premium P2)

For the full configuration see the GitHub repository: timja/azuread-application-proxy-demo

Application proxy connector

Prerequisites

💡
As far as I can tell the token for registering with Azure AD needs to be generated by a user account and not a service principal. I haven't managed to get the scopes required with service principal

Creating the connector

Start by creating a directory to work in and entering it:

mkdir azuread-application-proxy
cd azuread-application-proxy

Then we are going to start by creating some variables, change them according to your needs:

variable "prefix" {
  default = "app-proxy"
}

variable "location" {
  default = "uksouth"
}

variable "vnet_address_space" {
  default = "10.1.0.0/24"
}

variable "subnet_address_space" {
  default = "10.1.0.0/25"
}

variable "size" {
  default = "Standard_D2ds_v5"
}

locals {
  tags = {}
}
variables.tf

Next we will create a resource group:

resource "azurerm_resource_group" "this" {
  name     = "${var.prefix}-rg"
  location = var.location

  tags = local.tags
}
resource-group.tf

Then a Virtual Network and Subnet:

resource "azurerm_virtual_network" "this" {
  name     = "${var.prefix}-vnet"
  location = var.location

  address_space       = [var.vnet_address_space]
  resource_group_name = azurerm_resource_group.this.name

  tags = local.tags
}

resource "azurerm_subnet" "this" {
  name = "app-proxy"

  address_prefixes     = [var.subnet_address_space]
  resource_group_name  = azurerm_virtual_network.this.resource_group_name
  virtual_network_name = azurerm_virtual_network.this.name
}

resource "azurerm_network_interface" "this" {
  name                          = "${var.prefix}-nic"
  location                      = var.location
  resource_group_name           = azurerm_virtual_network.this.resource_group_name
  enable_accelerated_networking = true

  ip_configuration {
    name                          = "${var.prefix}-ipconfig"
    subnet_id                     = azurerm_subnet.this.id
    private_ip_address_allocation = "Dynamic"
  }

  tags = local.tags
}
network.tf

Now that we have all the boiler plate out of the way we all create a Virtual Machine:

resource "azurerm_windows_virtual_machine" "this" {
  name                  = "${var.prefix}-vm"
  admin_username        = var.prefix
  admin_password        = random_password.this.result
  location              = var.location
  network_interface_ids = [azurerm_network_interface.this.id]
  resource_group_name   = azurerm_subnet.this.resource_group_name
  size                  = var.size

  patch_mode = "AutomaticByPlatform"

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2022-datacenter-azure-edition-core"
    version   = "latest"
  }

  tags = local.tags
}
virtual-machine.tf

Looks simple doesn't it, well it is!

We aren't done yet though we need to install the connector on the Virtual Machine, we will do that with a Virtual Machine extension:

resource "azurerm_virtual_machine_extension" "this" {
  name                 = "app-proxy-onboarding"
  virtual_machine_id   = azurerm_windows_virtual_machine.this.id
  publisher            = "Microsoft.Compute"
  type                 = "CustomScriptExtension"
  type_handler_version = "1.9"
  protected_settings   = <<PROTECTED_SETTINGS
    {
      "fileUris": ["${var.script_url}"],
      "commandToExecute": "powershell -ExecutionPolicy Unrestricted -File bootstrap-app-proxy.ps1 -TenantId ${data.azurerm_client_config.this.tenant_id} -Token ${data.external.this.result.accessToken}"
    }
    PROTECTED_SETTINGS

  tags = local.tags
}

variable "script_url" {
  default = "https://raw.githubusercontent.com/timja/azuread-application-proxy-demo/HEAD/Bootstrap-Application-Proxy.ps1"
}

data "azurerm_client_config" "this" {}


data "external" "this" {
  program = ["bash", "${path.module}/get-access-token.sh"]
}
extension.tf

You can examine the script yourself at Bootstrap-Application-Proxy.ps1.

The access token is be retrieved by running the get-access-token.sh script which runs:

az account get-access-token \
  --resource-type 'aad-graph' \
  --scope 'https://proxy.cloudwebappproxy.net/registerapp/user_impersonation'
get-access-token.sh

There's other methods of getting the access token documented on Create an unattended installation script for the Azure Active Directory Application Proxy connector but they are all a lot more complicated and difficult if you aren't running on Windows.

Running the terraform

You should now have all the files needed and if you don't you can just grab them from timja/azuread-application-proxy-demo.

We're now going to run the terraform:

terraform init
terraform plan -out tfplan

Review the plan and if it looks good approve it:

terraform apply "tfplan"

All going well it should succeed and if you go to Application Proxy in the portal then you should see:

Application proxy page in Azure AD showing app-proxy-vm in the default group with status Active

Part 2

Coming soon, subscribe to get notified when it's available

References

These are posts I came across that were helpful to me:

Azure Active Directory Application Proxy Implementation
Are you running internal web-based applications that you want to give access to users working remotely securely, without the need for a VPN or firewalls? Do you want to enforce or use Azure Conditional Access policies to protect and manage access?
Silent install Azure Active Directory Application Proxy connector - Microsoft Entra
Covers how to perform an unattended installation of Azure Active Directory Application Proxy Connector to provide secure remote access to your on-premises apps.
Tim Jacomb

Tim Jacomb

United Kingdom