Durante el mes de mayo se anunció, en public preview, un nuevo servicio llamado Azure DNS Private Resolver, que nos va a permitir despreocuparnos de tener que mantener esta pieza en nuestras arquitecturas y poder delegar esta tarea, que te mostré por ejemplo en este artículo anterior, donde usé bind para dar solución a un escenario común en entornos híbridos. Hoy quiero contarte cómo usarlo en el escenario que conté hace tiempo, donde quería acceder a un App Service con private endpoint desde una VPN P2S.
Preparación del entorno de demo
Antes de adentrarnos en este nuevo servicio, voy a desplegar un entorno similar al que te mostré de un App Service con Private Endpoint:
################################
# Create a private App Service #
################################
# Variables
RESOURCE_GROUP="az-dns-resolver-demo"
LOCATION="northeurope"
APP_SERVICE_PLAN_NAME="az-dns-resolver-demo-plan"
APP_SERVICE_NAME="internalweb"
VNET_NAME="vnet"
WEBAPP_SUBNET="frontend"
VNET_CIDR=10.10.0.0/16
WEBAPP_SUBNET_CIDR=10.10.1.0/24
# Create a resource group
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create App Service Plan
az appservice plan create \
--name $APP_SERVICE_PLAN_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION --sku S1
# Create App Service
az webapp create \
--name $APP_SERVICE_NAME \
--resource-group $RESOURCE_GROUP \
--plan $APP_SERVICE_PLAN_NAME
# Create a vnet
az network vnet create \
--name $VNET_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--address-prefixes $VNET_CIDR \
--subnet-name $WEBAPP_SUBNET \
--subnet-prefixes $WEBAPP_SUBNET_CIDR
# You need to update the subnet to disable private endpoint network policies.
az network vnet subnet update \
--name $WEBAPP_SUBNET \
--resource-group $RESOURCE_GROUP \
--vnet-name $VNET_NAME \
--disable-private-endpoint-network-policies true
# Create a Private Endpoint for the Web App
# Get the web app ID
WEBAPP_ID=$(az webapp show --name $APP_SERVICE_NAME --resource-group $RESOURCE_GROUP --query id --output tsv)
WEB_APP_PRIVATE_ENDPOINT="webapp-private-endpoint"
# Create a Private Endpoint
az network private-endpoint create \
--name $WEB_APP_PRIVATE_ENDPOINT \
--resource-group $RESOURCE_GROUP \
--vnet-name $VNET_NAME \
--subnet $WEBAPP_SUBNET \
--connection-name "webapp-connection" \
--private-connection-resource-id $WEBAPP_ID \
--group-id sites
# Create Private DNS Zone
az network private-dns zone create \
--name privatelink.azurewebsites.net \
--resource-group $RESOURCE_GROUP
# Link between my VNET and the Private DNS Zone
az network private-dns link vnet create \
--name "${VNET_NAME}-link" \
--resource-group $RESOURCE_GROUP \
--registration-enabled false \
--virtual-network $VNET_NAME \
--zone-name privatelink.azurewebsites.net
# Create a DNS zone group
az network private-endpoint dns-zone-group create \
--name "webapp-group" \
--resource-group $RESOURCE_GROUP \
--endpoint-name $WEB_APP_PRIVATE_ENDPOINT \
--private-dns-zone privatelink.azurewebsites.net \
--zone-name privatelink.azurewebsites.net
# Create a VPN Gateway to connect wit my machine
VPN_GATEWAY_NAME="gateway"
VPN_GATEWAY_CIDR=10.10.2.0/24
# Create a subnet for the VPN Gateway
az network vnet subnet create \
--vnet-name $VNET_NAME \
--name GatewaySubnet \
--resource-group $RESOURCE_GROUP \
--address-prefix $VPN_GATEWAY_CIDR
# Create a public IP for the VPN Gateway
az network public-ip create \
--name "${VPN_GATEWAY_NAME}-ip" \
--resource-group $RESOURCE_GROUP \
--allocation-method Dynamic
# Define CIDR block for the VPN clients
ADDRESS_POOL_FOR_VPN_CLIENTS=10.20.0.0/16
# Get tenant ID
TENANT_ID=$(az account show --query tenantId --output tsv)
AZURE_VPN_CLIENT_ID="41b23e61-6c1e-4545-b367-cd054e0ed4b4"
#You have to consent Azure VPN application in your tenant first:
https://login.microsoftonline.com/common/oauth2/authorize?client_id=41b23e61-6c1e-4545-b367-cd054e0ed4b4&response_type=code&redirect_uri=https://portal.azure.com&nonce=1234&prompt=admin_consent
# Create a VPN Gateway
az network vnet-gateway create \
--name $VPN_GATEWAY_NAME \
--location $LOCATION \
--public-ip-address "${VPN_GATEWAY_NAME}-ip" \
--resource-group $RESOURCE_GROUP \
--vnet $VNET_NAME \
--gateway-type Vpn \
--sku VpnGw2 \
--vpn-type RouteBased \
--address-prefixes $ADDRESS_POOL_FOR_VPN_CLIENTS \
--client-protocol OpenVPN \
--vpn-auth-type AAD \
--aad-tenant "https://login.microsoftonline.com/${TENANT_ID}" \
--aad-audience $AZURE_VPN_CLIENT_ID \
--aad-issuer "https://sts.windows.net/${TENANT_ID}/"
# Get VPN client configuration
az network vnet-gateway vpn-client generate \
--resource-group $RESOURCE_GROUP \
--name $VPN_GATEWAY_NAME
Para este artículo he simplificado la foto, sin tener redes intermedias, como te mostré en el artículo original «Acceder a un App Service con Private Endpoint a través de una Vnet intermedia con VPN Point-to-Site», pero es más que suficiente demostrar que con un App Service privado necesitamos poder resolver su IP privada a través de su hostname para poder acceder a ella.
Llegados a este punto, si nos conectamos a través del perfil VPN descargado, veremos que si intentamos acceder a través de https://internalweb.azurewebsites.net no funcionará porque resolverá la IP pública del servicio y está bloqueado y si intentamos hacerlo directamente a través de la IP privada, 10.10.1.4, tampoco funcionará (aunque llegará a la web) ya que App Service necesita recibir las peticiones a través del hostname para saber a dónde tiene que enrutar. En ese artículo anterior lo que hacía para resolver esta situación era modificar el archivo /etc/hosts, mapeando la IP privada con el hostname de mi sitio. Ahora vamos a hacer uso de Azure DNS Private Resolver para que sea él quien lo resuelva.
Crear un Azure DNS Private Resolver
A día de hoy podemos crear este servicio usando tanto el portal como un nuevo módulo de PowerShell disponible desde la fase preview. Por lo tanto, lo primero que debemos hacer es instalarlo:
# Install Az.DnsResolver module
Install-Module Az.DnsResolver
# Confirm that the module is installed
Get-InstalledModule -Name Az.DnsResolver
Si todavía no lo has hecho, inicia sesión en tu suscripción de Azure desde PowerShell:
# Connect PowerShell to the Azure Cloud
Connect-AzAccount -Environment AzureCloud
Inicializa las siguientes variables de PowerShell las mismas que utilicé para montar el entorno de demo, además del nombre que vas a darle al servicio:
# Variables
$RESOURCE_GROUP = "az-dns-resolver-demo"
$LOCATION = "northeurope"
$VNET_NAME = "vnet"
$DNS_RESOLVER_NAME = "dnsresolver"
Creo el servicio con los siguientes comandos:
# Get subscription id
$SUBSCRIPTION_ID = (Get-AzContext).Subscription.Id
# Create a DNS resolver in the virtual network that you created.
New-AzDnsResolver -Name $DNS_RESOLVER_NAME `
-ResourceGroupName $RESOURCE_GROUP -Location $LOCATION `
-VirtualNetworkId "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/$VNET_NAME"
Una vez creado, lo siguiente que necesitas es dar de alta un inbound endpoint. Este tendrá una IP dentro de tu red, en una subnet dedicada que vamos a llamar inbound-subnet, a la que podremos lanzar peticiones para resolver nombres.
# Create a subnet in the virtual network
# The subnet needs to be at least /28 in size (16 IP addresses).
$SUBNET_NAME = "inbound-subnet"
$virtualNetwork = Get-AzVirtualNetwork -Name $VNET_NAME -ResourceGroupName $RESOURCE_GROUP
Add-AzVirtualNetworkSubnetConfig -Name $SUBNET_NAME -VirtualNetwork $virtualNetwork -AddressPrefix "10.10.3.0/28"
$virtualNetwork | Set-AzVirtualNetwork
# Create an inbound endpoint to enable name resolution from on-premises or another private location using an IP address
# that is part of your private virtual network address space.
$INBOUND_ENDPOINT_NAME = "inbound-endpoint"
$IP_CONFIG = New-AzDnsResolverIPConfigurationObject -PrivateIPAllocationMethod Dynamic -SubnetId /subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/$VNET_NAME/subnets/$SUBNET_NAME
New-AzDnsResolverInboundEndpoint -DnsResolverName $DNS_RESOLVER_NAME `
-Name $INBOUND_ENDPOINT_NAME -ResourceGroupName `
$RESOURCE_GROUP -Location $LOCATION -IpConfiguration $IP_CONFIG
Una vez creado, podrás recuperar la IP privada asociada a este enpoint:
# Get Inbound Endpoint IP
$inboundEndpoint = Get-AzDnsResolverInboundEndpoint -Name $INBOUND_ENDPOINT_NAME -DnsResolverName $DNS_RESOLVER_NAME -ResourceGroupName $RESOURCE_GROUP
$inboundEndpoint.ToJsonString() | jq .properties.ipConfigurations[0].privateIpAddress
En este ejemplo será la 10.10.3.4. Esta será la que nos ayudará a resolver el hostname con la IP privada y podremos acceder desde nuestra VPN P2S.
Configurar el perfil de Azure VPN Client
Si a mitad de este artículo descargaste la configuración del VPN Gateway y lo importaste en el Azure VPN Client comprobarías que tal cual está ahora mismo no funciona, no sabe resolver correctamente el private endpoint. Para solucionarlo debemos hacer un cambio en el archivo azurevpnconfig.xml: Busca la sección <clientconfig i:nil=»true» /> y reemplázala por esto:
<clientconfig>
<dnsservers>
<dnsserver>10.10.3.4</dnsserver>
</dnsservers>
</clientconfig>
Como ves, he añadido a la configuración del perfil un servidor DNS, que es la IP privada asociada al inbound endpoint generado para el Azure DNS Private Resolver. Si ahora vuelvo a importar este perfil y me conecto a él verás que esta información se refleja en el cliente:

Si ahora intentas acceder a https://internalweb.azurewebsites.net podrás acceder gracias a la resolución del nombre a través de Azure DNS Private Resolver.
Los comandos del ejemplo completo lo tienes en mi GitHub.
¡Saludos!