Configure Azure Application Gateway Private Link
Published Apr 19 2023 03:47 AM 3,592 Views
Microsoft

Private Link for Application Gateway allows you to connect workloads over a private connection spanning across VNets and subscriptions. When configured, a private endpoint will be placed into a defined virtual network's subnet, providing a private IP address for clients looking to communicate with the gateway. For a list of other PaaS services that support Private Link functionality, see What is Azure Private Link?. For more information on  Application Gateway Private Link, see the following resources:

 

 

The following picture shows the architecture of the demo built by the bash script below. The sample represents a scenario where a SaaS provider exposes a service application to a third party via Application Gateway Private Link.

 

architecture.png

The script creates the following Azure resources for the service provider:

 

  • A virtual network that contains the following subnets:
    • AppGatewaySubnet: this subnet hosts the Application Gateway
    • BackendSubnet: this subnet hosts Private Link and the virtual machines hosting a 'Hello World' Node.js web application installed via cloud-init 
  • Application Gateway WAF2 with Private Link
  • WAF Policy
  • Virtual machines hosting the 'Hello World' Node.js app and related network interfaces

 

The script creates the following Azure resources for the client provider:

 

  • A virtual network that contains the following subnets:
    • AzureBastionSubnet: this subnet hosts the Azure Bastion Host to access a client virtual machine.
    • BackendSubnet: this subnet hosts a client virtual machine used to call the sample web application via a Private Endpoint to the Application Gateway Private Link hosted in the same subnet.
  • A Private DNS Zone with an A record that is used to resolve a hostname (e.g. app.green.internal) to the private IP address of the Private Endpoint used to connect to the Application Gateway Private Link.

Private Link allows you to extend private connectivity to Application Gateway via a Private Endpoint in the following scenarios:

 

  • VNet in the same or different region from Application Gateway
  • VNet in the same or different subscription from Application Gateway
  • VNet in the same or different subscription and the same or different Azure AD tenant from Application Gateway

 

You may also block inbound public (Internet) access to Application Gateway and allow access only via private endpoints. Inbound management traffic still needs to be allowed to the application gateway. For more information, see Application Gateway infrastructure configuration.

All features supported by Application Gateway are supported when accessed through a private endpoint, including support for AGIC. For more information, see How to call an AKS-hosted workload via Application Gateway Private Link and AGIC.

 

Four components are required to implement Private Link with Application Gateway:

  • Application Gateway Private Link Configuration

    A Private link configuration can be associated with an Application Gateway Frontend IP address, which can then be used to establish a connection using a Private Endpoint. The Private Link feature won't be enabled if there's no association to an Application Gateway frontend IP address.

  • Application Gateway Frontend IP address

    The public or private IP address where the Application Gateway Private Link Configuration needs to be associated with enabling the Private Link Capabilities.

  • Private Endpoint

    An Azure network resource that allocates a private IP address in your VNet address space. It connects to the Application Gateway via the private IP address similar to many other Azure Services like Storage, KeyVault, etc., that provide private link access.

  • Private Endpoint Connection

    A connection on Application Gateway originated by Private Endpoints. You can auto-approve, manually approve, or reject connections to grant or deny access.

 

Limitations

  • API version 2020-03-01 or later should be used to configure Private Link configurations.
  • The static IP allocation method in the Private Link Configuration object isn't supported.
  • The subnet used for PrivateLinkConfiguration cannot be the same as the Application Gateway subnet.
  • Private link configuration for Application Gateway doesn't expose the "Alias" property and must be referenced via resource URI.
  • Private Endpoint creation doesn't create a *.privatelink DNS record/zone. All DNS records should be entered in existing zones used for your Application Gateway.
  • Azure Front Door and Application Gateway do not support chaining via Private Link.
  • Source IP address and x-forwarded-for headers will contain the Private link IP addresses.

 

Prerequisites

 

The following cloud-init configuration installs the required packages, creates a Node.js app, then initializes and starts the web application.

At your bash prompt or in the Cloud Shell, create a file named cloud-init.txt and paste the following configuration. 

 

#cloud-config
package_upgrade: true
packages:
  - nginx
  - nodejs
  - npm
write_files:
  - owner: www-data:www-data
  - path: /etc/nginx/sites-available/default
    content: |
      server {
        listen 80;
        location / {
          proxy_pass http://localhost:3000;
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection keep-alive;
          proxy_set_header Host $host;
          proxy_cache_bypass $http_upgrade;
        }
      }
  - owner: azureuser:azureuser
  - path: /home/azureuser/myapp/index.js
    content: |
      var express = require('express')
      var app = express()
      var os = require('os');
      app.get('/', function (req, res) {
        res.send('Hello World from host ' + os.hostname() + '!')
      })
      app.listen(3000, function () {
        console.log('Hello world app listening on port 3000!')
      })
runcmd:
  - service nginx restart
  - cd "/home/azureuser/myapp"
  - npm init
  - npm install express -y
  - nodejs index.js

 

Then create the bash script called deploy.sh and paste the following code. Choose a value for the prefix variable. Before running the script, customize the az vm create  that creates the client virtual machine and refer your private SSH key in the --ssh-key-values parameter. This will be necessary as credentials when connecting to the virtual machine using the azureuser admin user via Azure Bastion Host.

 

#!/bin/bash

# Variables
prefix="Green"
resourceGroupName="${prefix}RG"
bastionName="${prefix}Bastion"
bastionPublicIpName="${prefix}BastionPublicIp"
applicationGatewayName="${prefix}ApplicationGateway"
applicationGatewayProbeName="httpDefaultProbe"
applicationGatewayHttpSettingsName="appGatewayBackendHttpSettings"
applicationGatewayPublicIpName="${prefix}ApplicationGatewayPublicIp"
applicationGatewaySubnetName="AppGatewaySubnet"
applicationGatewaySubnetPrefix="10.21.0.0/24"
backendSubnetName="BackendSubnet"
backendSubnetPrefix="10.21.1.0/24"
defaultSubnetName="DefaultSubnet"
defaultSubnetPrefix="10.22.0.0/24"
bastionSubnetName="AzureBastionSubnet"
bastionSubnetPrefix="10.22.1.0/24"
serverVirtualNetworkName="${prefix}ServerVNet"
serverVirtualNetworkAddressPrefix="10.21.0.0/16"
clientVirtualNetworkName="${prefix}ClientVNet"
clientVirtualNetworkAddressPrefix="10.22.0.0/16"
virtualMachinePrefix="${prefix}"
location="eastus2"
privateLinkServiceName="${prefix}PrivateLink"
privateEndpointName="${prefix}PrivateEndpoint"
privateDnsZoneName="${prefix,,}.internal"
privateDnsZoneVirtualNetworkLinkName="LinkTo${serverVirtualNetworkName}"
aRecordName="app"
wafPolicyName="${prefix}WafPolicy"

# Subscription id, subscription name, and tenant id of the current subscription
subscriptionId=$(az account show --query id --output tsv)
subscriptionName=$(az account show --query name --output tsv)
tenantId=$(az account show --query tenantId --output tsv)

# Check if the resource group already exists
echo "Checking if [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription..."

az group show --name $resourceGroupName &>/dev/null

if [[ $? != 0 ]]; then
  echo "No [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription"
  echo "Creating [$resourceGroupName] resource group in the [$subscriptionName] subscription..."

  # Create the resource group
  az group create --name $resourceGroupName --location $location 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$resourceGroupName] resource group successfully created in the [$subscriptionName] subscription"
  else
    echo "Failed to create [$resourceGroupName] resource group in the [$subscriptionName] subscription"
    exit
  fi
else
  echo "[$resourceGroupName] resource group already exists in the [$subscriptionName] subscription"
fi

# Check if the server virtual network already exists
echo "Checking if [$serverVirtualNetworkName] virtual network actually exists in the [$resourceGroupName] resource group..."
az network vnet show \
  --name $serverVirtualNetworkName \
  --only-show-errors \
  --resource-group $resourceGroupName &>/dev/null

if [[ $? != 0 ]]; then
  echo "No [$serverVirtualNetworkName] virtual network actually exists in the [$resourceGroupName] resource group"
  echo "Creating [$serverVirtualNetworkName] virtual network in the [$resourceGroupName] resource group..."

  # Create the server virtual network
  az network vnet create \
    --name $serverVirtualNetworkName \
    --resource-group $resourceGroupName \
    --address-prefixes $serverVirtualNetworkAddressPrefix \
    --subnet-name $applicationGatewaySubnetName \
    --subnet-prefix $applicationGatewaySubnetPrefix \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$serverVirtualNetworkName] virtual network successfully created in the [$resourceGroupName] resource group"
  else
    echo "Failed to create [$serverVirtualNetworkName] virtual network in the [$resourceGroupName] resource group"
    exit
  fi

  # Check if the backend subnet already exists
  echo "Checking if [$backendSubnetName] backend subnet actually exists in the [$serverVirtualNetworkName] virtual network..."
  az network vnet subnet show \
    --name $backendSubnetName \
    --vnet-name $serverVirtualNetworkName \
    --resource-group $resourceGroupName \
    --only-show-errors &>/dev/null

  if [[ $? != 0 ]]; then
    echo "No [$backendSubnetName] backend subnet actually exists in the [$serverVirtualNetworkName] virtual network"
    echo "Creating [$backendSubnetName] backend subnet in the [$serverVirtualNetworkName] virtual network..."

    # Create the backend subnet
    az network vnet subnet create \
      --name $backendSubnetName \
      --vnet-name $serverVirtualNetworkName \
      --resource-group $resourceGroupName \
      --address-prefix $backendSubnetPrefix \
      --only-show-errors 1>/dev/null

    if [[ $? == 0 ]]; then
      echo "[$backendSubnetName] backend subnet successfully created in the [$serverVirtualNetworkName] virtual network"
    else
      echo "Failed to create [$backendSubnetName] backend subnet in the [$serverVirtualNetworkName] virtual network"
      exit
    fi
  else
    echo "[$backendSubnetName] backend subnet already exists in the [$serverVirtualNetworkName] virtual network"
  fi
else
  echo "[$serverVirtualNetworkName] virtual network already exists in the [$resourceGroupName] resource group"
fi

# Check if the client virtual network already exists
echo "Checking if [$clientVirtualNetworkName] virtual network actually exists in the [$resourceGroupName] resource group..."
az network vnet show \
  --name $clientVirtualNetworkName \
  --resource-group $resourceGroupName \
  --only-show-errors &>/dev/null

if [[ $? != 0 ]]; then
  echo "No [$clientVirtualNetworkName] virtual network actually exists in the [$resourceGroupName] resource group"
  echo "Creating [$clientVirtualNetworkName] virtual network in the [$resourceGroupName] resource group..."

  # Create the client virtual network
  az network vnet create \
    --name $clientVirtualNetworkName \
    --resource-group $resourceGroupName \
    --address-prefixes $clientVirtualNetworkAddressPrefix \
    --subnet-name $defaultSubnetName \
    --subnet-prefix $defaultSubnetPrefix \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$clientVirtualNetworkName] virtual network successfully created in the [$resourceGroupName] resource group"
  else
    echo "Failed to create [$clientVirtualNetworkName] virtual network in the [$resourceGroupName] resource group"
    exit
  fi

  # Check if the bastion subnet already exists
  echo "Checking if [$bastionSubnetName] bastion subnet actually exists in the [$clientVirtualNetworkName] virtual network..."
  az network vnet subnet show \
    --name $bastionSubnetName \
    --vnet-name $clientVirtualNetworkName \
    --resource-group $resourceGroupName \
    --only-show-errors &>/dev/null

  if [[ $? != 0 ]]; then
    echo "No [$bastionSubnetName] bastion subnet actually exists in the [$clientVirtualNetworkName] virtual network"
    echo "Creating [$bastionSubnetName] bastion subnet in the [$clientVirtualNetworkName] virtual network..."

    # Create the bastion subnet
    az network vnet subnet create \
      --name $bastionSubnetName \
      --vnet-name $clientVirtualNetworkName \
      --resource-group $resourceGroupName \
      --address-prefix $bastionSubnetPrefix \
      --only-show-errors 1>/dev/null

    if [[ $? == 0 ]]; then
      echo "[$bastionSubnetName] bastion subnet successfully created in the [$clientVirtualNetworkName] virtual network"
    else
      echo "Failed to create [$bastionSubnetName] bastion subnet in the [$clientVirtualNetworkName] virtual network"
      exit
    fi
  else
    echo "[$bastionSubnetName] bastion subnet already exists in the [$clientVirtualNetworkName] virtual network"
  fi
else
  echo "[$clientVirtualNetworkName] virtual network already exists in the [$resourceGroupName] resource group"
fi

# Check if the application gateway public ip address already exists
echo "Checking if [$applicationGatewayPublicIpName] application gateway public ip address actually exists in the [$resourceGroupName] resource group..."
az network public-ip show \
  --name $applicationGatewayPublicIpName \
  --resource-group $resourceGroupName \
  --only-show-errors &>/dev/null

if [[ $? != 0 ]]; then
  echo "No [$applicationGatewayPublicIpName] application gateway public ip address actually exists in the [$resourceGroupName] resource group"
  echo "Creating [$applicationGatewayPublicIpName] application gateway public ip address in the [$resourceGroupName] resource group..."

  # Create the application gateway public ip address
  az network public-ip create \
    --name $applicationGatewayPublicIpName \
    --resource-group $resourceGroupName \
    --sku Standard \
    --allocation-method Static \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$applicationGatewayPublicIpName] application gateway public ip address successfully created in the [$resourceGroupName] resource group"
  else
    echo "Failed to create [$applicationGatewayPublicIpName] application gateway public ip address in the [$resourceGroupName] resource group"
    exit
  fi
else
  echo "[$applicationGatewayPublicIpName] application gateway public ip address already exists in the [$resourceGroupName] resource group"
fi

# Create an eopty array to store the private ip addresses of the backend VMs
privateIpAddresses=()

# Create backend VMs
for i in $(seq 1 2); do
  # Check if the virtual machine already exists
  virtualMachineName="$virtualMachinePrefix${i}Vm"
  echo "Checking if [$virtualMachineName] virtual machine actually exists in the [$resourceGroupName] resource group..."
  az vm show \
    --name $virtualMachineName \
    --resource-group $resourceGroupName &>/dev/null

  if [[ $? != 0 ]]; then
    echo "No [$virtualMachineName] virtual machine actually exists in the [$resourceGroupName] resource group"
    echo "Creating the network interface for the [$virtualMachineName] virtual machine in the [$resourceGroupName] resource group..."

    # Create the virtual machine network interface
    nicName="${virtualMachineName}Nic"
    az network nic create \
      --name $nicName \
      --resource-group $resourceGroupName \
      --vnet-name $serverVirtualNetworkName \
      --subnet $backendSubnetName \
      --only-show-errors 1>/dev/null

    if [[ $? != 0 ]]; then
      echo "Failed to create [$nicName] network interface in the [$resourceGroupName] resource group"
      exit
    fi

    # Retrieve the private ip address of the virtual machine network interface
    privateIpAddress=$(az network nic show \
      --name $nicName \
      --resource-group $resourceGroupName \
      --only-show-errors \
      --query ipConfigurations[0].privateIPAddress \
      --output tsv)
    echo "[$nicName] network interface private ip address: $privateIpAddress"

    # Add private ip address to the array
    privateIpAddresses+=($privateIpAddress)

    echo "Creating the [$virtualMachineName] virtual machine in the [$resourceGroupName] resource group..."

    # Create the virtual machine
    az vm create \
      --name $virtualMachineName \
      --resource-group $resourceGroupName \
      --nics $nicName \
      --image UbuntuLTS \
      --admin-username azureuser \
      --ssh-key-values ~/.ssh/id_rsa.pub \
      --custom-data ./cloud-init.txt \
      --only-show-errors 1>/dev/null

    if [[ $? == 0 ]]; then
      echo "[$virtualMachineName] virtual machine successfully created in the [$resourceGroupName] resource group"
    else
      echo "Failed to create [$virtualMachineName] virtual machine in the [$resourceGroupName] resource group"
      exit
    fi
  else
    # Retrieve the private ip address of the virtual machine network interface
    nicName="${virtualMachineName}Nic"
    privateIpAddress=$(az network nic show \
      --name $nicName \
      --resource-group $resourceGroupName \
      --only-show-errors \
      --query ipConfigurations[0].privateIPAddress \
      --output tsv)
    echo "[$nicName] network interface private ip address: $privateIpAddress"

    # Add private ip address to the array
    privateIpAddresses+=($privateIpAddress)
    echo "[$virtualMachineName] virtual machine already exists in the [$resourceGroupName] resource group"
  fi
done

# Create a space-separated list of IP addresses of private IP addresses of the backend VMs from the array
privateIpAddressesList=$(
  IFS=' '
  echo "${privateIpAddresses[*]}"
)

# Check if the waf policy already exists
echo "Checking if [$wafPolicyName] waf policy actually exists in the [$resourceGroupName] resource group..."
az network application-gateway waf-policy show \
  --name $wafPolicyName \
  --resource-group $resourceGroupName \
  --only-show-errors &>/dev/null

if [[ $? != 0 ]]; then
  echo "No [$wafPolicyName] waf policy actually exists in the [$resourceGroupName] resource group"
  echo "Creating [$wafPolicyName] waf policy in the [$resourceGroupName] resource group..."

  # Create the waf policy
  az network application-gateway waf-policy create \
    --name $wafPolicyName \
    --resource-group $resourceGroupName \
    --location $location \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$wafPolicyName] waf policy successfully created in the [$resourceGroupName] resource group"
  else
    echo "Failed to create [$wafPolicyName] waf policy in the [$resourceGroupName] resource group"
    exit
  fi
else
  echo "[$wafPolicyName] waf policy already exists in the [$resourceGroupName] resource group"
fi

# Check if the application gateway already exists
echo "Checking if [$applicationGatewayName] application gateway actually exists in the [$resourceGroupName] resource group..."
az network application-gateway show \
  --name $applicationGatewayName \
  --resource-group $resourceGroupName \
  --only-show-errors &>/dev/null

if [[ $? != 0 ]]; then
  echo "No [$applicationGatewayName] application gateway actually exists in the [$resourceGroupName] resource group"
  echo "Creating [$applicationGatewayName] application gateway in the [$resourceGroupName] resource group..."

  # Create the application gateway
  az network application-gateway create \
    --name $applicationGatewayName \
    --resource-group $resourceGroupName \
    --location $location \
    --capacity 2 \
    --sku WAF_v2 \
    --waf-policy $wafPolicyName \
    --public-ip-address $applicationGatewayPublicIpName \
    --vnet-name $serverVirtualNetworkName \
    --subnet $applicationGatewaySubnetName \
    --servers $privateIpAddressesList \
    --priority 100 \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$applicationGatewayName] application gateway successfully created in the [$resourceGroupName] resource group"
  else
    echo "Failed to create [$applicationGatewayName] application gateway in the [$resourceGroupName] resource group"
    exit
  fi

  # Create http probe
  echo "Creating [$applicationGatewayProbeName] http probe in the [$applicationGatewayName] application gateway in the [$resourceGroupName] resource group..."
  az network application-gateway probe create \
    --name $applicationGatewayProbeName \
    --gateway-name $applicationGatewayName \
    --resource-group $resourceGroupName \
    --protocol http \
    --port 80 \
    --host 127.0.0.1 \
    --timeout 30 \
    --path / \
    --only-show-errors 1>/dev/null
  
  if [[ $? == 0 ]]; then
    echo "[$applicationGatewayProbeName] http probe successfully created in the [$applicationGatewayName] application gateway in the [$resourceGroupName] resource group"
  else
    echo "Failed to create [$applicationGatewayProbeName] http probe in the [$applicationGatewayName] application gateway in the [$resourceGroupName] resource group"
    exit
  fi

  # Update the http settings
  echo "Updating [$applicationGatewayHttpSettingsName] http settings in the [$applicationGatewayName] application gateway in the [$resourceGroupName] resource group..."
  az network application-gateway http-settings update \
    --name $applicationGatewayHttpSettingsName \
    --gateway-name $applicationGatewayName \
    --resource-group $resourceGroupName \
    --port 80 \
    --protocol http \
    --probe $applicationGatewayProbeName \
    --only-show-errors 1>/dev/null
  
  if [[ $? == 0 ]]; then
    echo "[$applicationGatewayHttpSettingsName] http settings successfully updated in the [$applicationGatewayName] application gateway in the [$resourceGroupName] resource group"
  else
    echo "Failed to update [$applicationGatewayHttpSettingsName] http settings in the [$applicationGatewayName] application gateway in the [$resourceGroupName] resource group"
    exit
  fi
else
  echo "[$applicationGatewayName] application gateway already exists in the [$resourceGroupName] resource group"
fi

# Get the resource id of the application gateway
echo "Getting the resource id of the [$applicationGatewayName] application gateway in the [$resourceGroupName] resource group..."
applicationGatewayResourceId=$(az network application-gateway show \
  --name $applicationGatewayName \
  --resource-group $resourceGroupName \
  --only-show-errors \
  --query id \
  --output tsv)

if [[ -z $applicationGatewayResourceId ]]; then
  echo "Failed to get the resource id of the [$applicationGatewayName] application gateway in the [$resourceGroupName] resource group"
  exit
else
  echo "[$applicationGatewayName] application gateway resource id successfully retrieved"
fi

# Check if the private link service network policies are disabled on the backend subnet that will host the application gateway private link
echo "Checking if Private Link Service Network Policies are disabled on the [$backendSubnetName] subnet of the [$serverVirtualNetworkName] virtual network in the [$resourceGroupName] resource group..."
result=$(az network vnet subnet show \
  --name $backendSubnetName \
  --vnet-name $serverVirtualNetworkName \
  --resource-group $resourceGroupName \
  --only-show-errors \
  --query privateLinkServiceNetworkPolicies \
  --output tsv)

if [[ $result != "Disabled" ]]; then
  # Disable Private Link Service Network Policies
  # https://learn.microsoft.com/azure/private-link/disable-private-endpoint-network-policy
  echo "Disabling Private Link Service Network Policies on the [$backendSubnetName] subnet of the [$serverVirtualNetworkName] virtual network in the [$resourceGroupName] resource group..."
  az network vnet subnet update \
    --name $backendSubnetName \
    --vnet-name $serverVirtualNetworkName \
    --resource-group $resourceGroupName \
    --only-show-errors \
    --disable-private-link-service-network-policies true 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "Private Link Service Network Policies successfully disabled on the [$backendSubnetName] subnet of the [$serverVirtualNetworkName] virtual network in the [$resourceGroupName] resource group"
  else
    echo "Failed to disable Private Link Service Network Policies on the [$backendSubnetName] subnet of the [$serverVirtualNetworkName] virtual network in the [$resourceGroupName] resource group"
    exit
  fi
else
  echo "Private Link Service Network Policies are already disabled on the [$backendSubnetName] subnet of the [$serverVirtualNetworkName] virtual network in the [$resourceGroupName] resource group"
fi

# Check if the private link service network policies are disabled on the application gateway subnet
echo "Checking if Private Link Service Network Policies are disabled on the [$defaultSubnetName] subnet of the [$clientVirtualNetworkName] virtual network in the [$resourceGroupName] resource group..."
result=$(az network vnet subnet show \
  --name $defaultSubnetName \
  --vnet-name $clientVirtualNetworkName \
  --resource-group $resourceGroupName \
  --only-show-errors \
  --query privateLinkServiceNetworkPolicies \
  --output tsv)

if [[ $result != "Disabled" ]]; then
  # Disable Private Link Service Network Policies
  # https://learn.microsoft.com/azure/private-link/disable-private-endpoint-network-policy
  echo "Disabling Private Link Service Network Policies on the [$defaultSubnetName] subnet of the [$clientVirtualNetworkName] virtual network in the [$resourceGroupName] resource group..."
  az network vnet subnet update \
    --name $defaultSubnetName \
    --vnet-name $clientVirtualNetworkName \
    --resource-group $resourceGroupName \
    --disable-private-link-service-network-policies true \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "Private Link Service Network Policies successfully disabled on the [$defaultSubnetName] subnet of the [$clientVirtualNetworkName] virtual network in the [$resourceGroupName] resource group"
  else
    echo "Failed to disable Private Link Service Network Policies on the [$defaultSubnetName] subnet of the [$clientVirtualNetworkName] virtual network in the [$resourceGroupName] resource group"
    exit
  fi
else
  echo "Private Link Service Network Policies are already disabled on the [$defaultSubnetName] subnet of the [$clientVirtualNetworkName] virtual network in the [$resourceGroupName] resource group"
fi

# Get Application Gateway Frontend IP Name
echo "Getting Application Gateway Frontend IP Configuration name..."
frontendIpConfigurationName=$(az network application-gateway frontend-ip list \
  --gateway-name $applicationGatewayName \
  --resource-group $resourceGroupName \
  --only-show-errors \
  --query [0].name \
  --output tsv)

if [[ -n $frontendIpConfigurationName ]]; then
  echo "Frontend IP configuration [$frontendIpConfigurationName] name successfully retrieved"
else
  echo "Failed to retrieve the name of the frontend IP configuration of the [$applicationGatewayName] application gateway"
  exit
fi

# Get the resource id of the backend subnet that will host the application gateway private link. This subnet should be different from the one hosting the application gateway
echo "Getting the resource id of the [$applicationGatewaySubnetName] subnet of the [$serverVirtualNetworkName] virtual network in the [$resourceGroupName] resource group..."
backendSubnetId=$(az network vnet subnet show \
  --name $backendSubnetName \
  --vnet-name $serverVirtualNetworkName \
  --resource-group $resourceGroupName \
  --only-show-errors \
  --query id \
  --output tsv)

if [[ -n $backendSubnetId ]]; then
  echo "Resource id of the [$backendSubnetName] subnet of the [$serverVirtualNetworkName] virtual network in the [$resourceGroupName] resource group successfully retrieved"
else
  echo "Failed to retrieve the resource id of the [$backendSubnetName] subnet of the [$serverVirtualNetworkName] virtual network in the [$resourceGroupName] resource group"
  exit
fi

# Check if the private link configuration already exists
echo "Checking if the private link configuration [$privateLinkServiceName] already exists..."
privateLinkId=$(az network application-gateway private-link list \
  --gateway-name $applicationGatewayName \
  --resource-group $resourceGroupName \
  --only-show-errors \
  --query "[?name=='$privateLinkServiceName'].id" \
  --output tsv)

if [[ -z $privateLinkId ]]; then
  echo "Private link configuration [$privateLinkServiceName] does not exist"
  echo "Creating private link configuration [$privateLinkServiceName]..."

  # Add a new Private Link configuration and associate it with an existing Frontend IP
  echo "Adding a new private link configuration and associating it with the [$frontendIpConfigurationName] frontend IP configuration of the [$applicationGatewayName] application gateway..."
  az network application-gateway private-link add \
    --frontend-ip $frontendIpConfigurationName \
    --name $privateLinkServiceName \
    --subnet $backendSubnetId \
    --gateway-name $applicationGatewayName \
    --resource-group $resourceGroupName \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "Private link configuration successfully added and associated with the [$frontendIpConfigurationName] frontend IP configuration of the [$applicationGatewayName] application gateway"
  else
    echo "Failed to add a new private link configuration and associate it with the [$frontendIpConfigurationName] frontend IP configuration of the [$applicationGatewayName] application gateway"
    exit
  fi
else
  echo "Private link configuration [$privateLinkServiceName] already exists"
fi

# Get Private Link resource ID
echo "Getting the resource id of the [$privateLinkServiceName] private link configuration of the [$applicationGatewayName] application gateway..."
privateLinkId=(az network application-gateway private-link list
  --gateway-name $applicationGatewayName
  --resource-group $resourceGroupName
  --only-show-errors
  --query "[?name=='$privateLinkServiceName'].id"
  --output tsv)

if [[ -n $privateLinkId ]]; then
  echo "Resource id of the [$privateLinkServiceName] private link configuration of the [$applicationGatewayName] application gateway successfully retrieved"
else
  echo "Failed to retrieve the resource id of the [$privateLinkServiceName] private link configuration of the [$applicationGatewayName] application gateway"
  exit
fi

# Check if the private endpoint already exists
privateEndpointId=$(az network private-endpoint list \
  --resource-group $resourceGroupName \
  --only-show-errors \
  --query "[?name=='$privateEndpointName'].id" \
  --output tsv)

if [[ -z $privateEndpointId ]]; then
  echo "Private endpoint [$privateEndpointName] does not exist"
  echo "Creating a private endpoint for the [$privateLinkServiceName] private link configuration of the [$applicationGatewayName] application gateway..."

  # Create a private endpoint for the private link configuration
  # NOTE: as documented in https://learn.microsoft.com/en-us/azure/application-gateway/private-link-configure?tabs=cli
  # the value of the --group-id parameter needs to be eqaul to the frontend IP configuration of the Application Gateway
  # use by the Private Link. The defalt name of the frontend IP configuration is "appGatewayFrontendIP"
  az network private-endpoint create \
    --name $privateEndpointName \
    --resource-group $resourceGroupName \
    --vnet-name $clientVirtualNetworkName \
    --subnet $defaultSubnetName \
    --group-id appGatewayFrontendIP \
    --private-connection-resource-id $applicationGatewayResourceId \
    --connection-name "${applicationGatewayName}Connection" \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "Private endpoint successfully created for the [$privateLinkServiceName] private link configuration of the [$applicationGatewayName] application gateway"
  else
    echo "Failed to create a private endpoint for the [$privateLinkServiceName] private link configuration of the [$applicationGatewayName] application gateway"
    exit
  fi
else
  echo "Private endpoint [$privateEndpointName] already exists"
fi

# Check if the virtual machine already exists
virtualMachineName="${virtualMachinePrefix}TestVm"
echo "Checking if [$virtualMachineName] virtual machine actually exists in the [$resourceGroupName] resource group..."
az vm show \
  --name $virtualMachineName \
  --resource-group $resourceGroupName &>/dev/null

if [[ $? != 0 ]]; then
  echo "No [$virtualMachineName] virtual machine actually exists in the [$resourceGroupName] resource group"
  echo "Creating the network interface for the [$virtualMachineName] virtual machine in the [$resourceGroupName] resource group..."

  # Create the virtual machine network interface
  nicName="${virtualMachineName}Nic"
  az network nic create \
    --name $nicName \
    --resource-group $resourceGroupName \
    --vnet-name $clientVirtualNetworkName \
    --subnet $defaultSubnetName \
    --only-show-errors 1>/dev/null

  if [[ $? != 0 ]]; then
    echo "Failed to create [$nicName] network interface in the [$resourceGroupName] resource group"
    exit
  fi

  echo "Creating [$virtualMachineName] virtual machine in the [$resourceGroupName] resource group..."

  # Create the virtual machine
  az vm create \
    --name $virtualMachineName \
    --resource-group $resourceGroupName \
    --nics $nicName \
    --image UbuntuLTS \
    --admin-username azureuser \
    --ssh-key-values ~/.ssh/id_rsa.pub 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$virtualMachineName] virtual machine successfully created in the [$resourceGroupName] resource group"
  else
    echo "Failed to create [$virtualMachineName] virtual machine in the [$resourceGroupName] resource group"
    exit
  fi
else
  echo "[$virtualMachineName] virtual machine already exists in the [$resourceGroupName] resource group"
fi

# Check if the bastion public ip address already exists
echo "Checking if [$bastionPublicIpName] bastion public ip address actually exists in the [$resourceGroupName] resource group..."
az network public-ip show \
  --name $bastionPublicIpName \
  --resource-group $resourceGroupName \
  --only-show-errors &>/dev/null

if [[ $? != 0 ]]; then
  echo "No [$bastionPublicIpName] bastion public ip address actually exists in the [$resourceGroupName] resource group"
  echo "Creating [$bastionPublicIpName] bastion public ip address in the [$resourceGroupName] resource group..."

  # Create the bastion public ip address
  az network public-ip create \
    --name $bastionPublicIpName \
    --resource-group $resourceGroupName \
    --sku Standard \
    --allocation-method Static \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$bastionPublicIpName] bastion public ip address successfully created in the [$resourceGroupName] resource group"
  else
    echo "Failed to create [$bastionPublicIpName] bastion public ip address in the [$resourceGroupName] resource group"
    exit
  fi
else
  echo "[$bastionPublicIpName] bastion public ip address already exists in the [$resourceGroupName] resource group"
fi

# Check if bastion exists
echo "Checking if [$bastionName] bastion actually exists in the [$resourceGroupName] resource group..."
az network bastion show \
  --name $bastionName \
  --resource-group $resourceGroupName \
  --only-show-errors &>/dev/null

if [[ $? != 0 ]]; then
  echo "No [$bastionName] bastion actually exists in the [$resourceGroupName] resource group"
  echo "Creating [$bastionName] bastion in the [$resourceGroupName] resource group..."

  # Create the bastion subnet
  az network bastion create \
    --name $bastionName \
    --vnet-name $clientVirtualNetworkName \
    --public-ip-address $bastionPublicIpName \
    --resource-group $resourceGroupName \
    --location $location \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$bastionName] bastion successfully created in the [$resourceGroupName] resource group"
  else
    echo "Failed to create [$bastionName] bastion in the [$resourceGroupName] resource group"
    exit
  fi
else
  echo "[$bastionName] bastion already exists in the [$resourceGroupName] resource group"
fi

# Check if the private DNS Zone already exists
az network private-dns zone show \
  --name $privateDnsZoneName \
  --resource-group $resourceGroupName \
  --only-show-errors &>/dev/null

if [[ $? != 0 ]]; then
  echo "No [$privateDnsZoneName] private DNS zone actually exists in the [$resourceGroupName] resource group"
  echo "Creating [$privateDnsZoneName] private DNS zone in the [$resourceGroupName] resource group..."

  # Create the private DNS Zone
  az network private-dns zone create \
    --name $privateDnsZoneName \
    --resource-group $resourceGroupName \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$privateDnsZoneName] private DNS zone successfully created in the [$resourceGroupName] resource group"
  else
    echo "Failed to create [$privateDnsZoneName] private DNS zone in the [$resourceGroupName] resource group"
    exit
  fi
else
  echo "[$privateDnsZoneName] private DNS zone already exists in the [$resourceGroupName] resource group"
fi

# Check if the private DNS Zone virtual network link already exists
az network private-dns link vnet show \
  --name $privateDnsZoneVirtualNetworkLinkName \
  --resource-group $resourceGroupName \
  --zone-name $privateDnsZoneName \
  --only-show-errors &>/dev/null

if [[ $? != 0 ]]; then
  echo "No [$privateDnsZoneVirtualNetworkLinkName] private DNS zone virtual network link actually exists in the [$resourceGroupName] resource group"

  # Retrieve the client virtual network resource id
  clientVirtualNetworkResourceId=$(az network vnet show \
    --name $clientVirtualNetworkName \
    --resource-group $resourceGroupName \
    --only-show-errors \
    --query id \
    --output tsv)

  if [[ -z $clientVirtualNetworkResourceId ]]; then
    echo "Failed to retrieve [$clientVirtualNetworkName] client virtual network resource id in the [$resourceGroupName] resource group"
    exit
  fi

  echo "Creating [$privateDnsZoneVirtualNetworkLinkName] private DNS zone virtual network link in the [$resourceGroupName] resource group..."

  # Create the private DNS Zone virtual network link
  az network private-dns link vnet create \
    --name $privateDnsZoneVirtualNetworkLinkName \
    --resource-group $resourceGroupName \
    --zone-name $privateDnsZoneName \
    --virtual-network $clientVirtualNetworkResourceId \
    --registration-enabled false \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$privateDnsZoneVirtualNetworkLinkName] private DNS zone virtual network link successfully created in the [$resourceGroupName] resource group"
  else
    echo "Failed to create [$privateDnsZoneVirtualNetworkLinkName] private DNS zone virtual network link in the [$resourceGroupName] resource group"
    exit
  fi
else
  echo "[$privateDnsZoneVirtualNetworkLinkName] private DNS zone virtual network link already exists in the [$resourceGroupName] resource group"
fi

# Check if the A record already exists
az network private-dns record-set a show \
  --name $aRecordName \
  --resource-group $resourceGroupName \
  --zone-name $privateDnsZoneName \
  --only-show-errors &>/dev/null

if [[ $? != 0 ]]; then
  echo "No [$aRecordName] A record actually exists in the [$resourceGroupName] resource group"

  # Get the resource id of the network interface used by the private endpoint
  echo "Retrieving [$privateEndpointName] private endpoint network interface resource id in the [$resourceGroupName] resource group..."
  privateEndpointNetworkInterfaceResourceId=$(az network private-endpoint show \
    --name $privateEndpointName \
    --resource-group $resourceGroupName \
    --only-show-errors \
    --query "networkInterfaces[0].id" \
    --output tsv)

  if [[ -z $privateEndpointNetworkInterfaceResourceId ]]; then
    echo "Failed to retrieve [$privateEndpointName] private endpoint network interface resource id in the [$resourceGroupName] resource group"
    exit
  fi

  echo $privateEndpointNetworkInterfaceResourceId

  # Get the private IP address of the network interface used by the private endpoint
  echo "Retrieving [$privateEndpointName] private endpoint network interface private IP address in the [$resourceGroupName] resource group..."
  privateEndpointNetworkInterfacePrivateIpAddress=$(az network nic show \
    --ids $privateEndpointNetworkInterfaceResourceId \
    --only-show-errors \
    --query "ipConfigurations[0].privateIPAddress" \
    --output tsv)

  if [[ -z $privateEndpointNetworkInterfacePrivateIpAddress ]]; then
    echo "Failed to retrieve [$privateEndpointName] private endpoint network interface private IP address in the [$resourceGroupName] resource group"
    exit
  fi

  # Create the A record
  echo "Creating [$aRecordName] A record in the [$resourceGroupName] resource group..."
  az network private-dns record-set a create \
    --name $aRecordName \
    --resource-group $resourceGroupName \
    --zone-name $privateDnsZoneName \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$aRecordName] A record successfully created in the [$resourceGroupName] resource group"
  else
    echo "Failed to create [$aRecordName] A record in the [$resourceGroupName] resource group"
    exit
  fi

  # Add record to record set
  echo "Adding [$privateEndpointNetworkInterfacePrivateIpAddress] private IP address to the [$aRecordName] A record..."
  az network private-dns record-set a add-record \
    --record-set-name $aRecordName \
    --resource-group $resourceGroupName \
    --zone-name $privateDnsZoneName \
    --ipv4-address $privateEndpointNetworkInterfacePrivateIpAddress \
    --only-show-errors 1>/dev/null

  if [[ $? == 0 ]]; then
    echo "[$privateEndpointNetworkInterfacePrivateIpAddress] private IP address successfully added to the [$aRecordName] A record"
  else
    echo "Failed to add [$privateEndpointNetworkInterfacePrivateIpAddress] private IP address to the [$aRecordName] A record"
    exit
  fi
else
  echo "[$aRecordName] A record already exists in the [$resourceGroupName] resource group"
fi

 

When done, run the deploy.sh script to create Azure resources in a resource group.

 

Test the Sample

If the deployment succeeds, proceed as follows to test the sample:

  • Navigate to Azure Portal and connect to the client virtual machine via Azure Bastion.
  • Run the the nslookup app.<prefix>.internal command. The name of the Private DNS zone depends on the value you chose for the prefix variable in the deploy.sh script. 

nslookup.png

  • Run the curl app.<prefix>.internal; echo command.  If the call succeeds, you should see a result like the one in the following figure.

curl.png

 

Review deployed resources

Use the Azure portal, Azure CLI, or Azure PowerShell to list the deployed resources in the resource group.

 

Azure CLI

 

az resource list --resource-group <resource-group-name>

 

 

PowerShell

 

Get-AzResource -ResourceGroupName <resource-group-name>

 

Clean up resources

Delete the resource group when you no longer need the resources you created. This will remove all the Azure resources.

 

Azure CLI

 

az group delete --name <resource-group-name>

 

PowerShell

 

Remove-AzResourceGroup -Name <resource-group-name>

 

 

Co-Authors
Version history
Last update:
‎Apr 19 2023 03:49 AM
Updated by: