Private Endpoint

Private Endpoint (Azure SQL Data base) (DNS Azure)

Neste post iremos demonstrar a utilização de Private Endpoint com Azure SQL PaaS.
Nossa aplicação roda em cima de duas VM com Windows Server 2016 com IIS e DFS para replicação de dados, utilizando banco de dados SQL Server (Azure Data Base SQL PaaS)
Nossa infraestrutura possui Load Balance para alta disponibilidade da aplicação além de cada VM estar em uma zona de disponibilidade.
Este post visa demonstrar a utilização do Private Endpoint com DNS Azure.

*Obs: O foco deste post é o private endpoint e não de como expor um serviço na internet com segurança.

Vamos à mão na massa!!!! (Nosso Topologia)

O script abaixo criará os seguintes objetos

Load Balancer
lb-corp-web-shd-eastus

Network interface
int-eth0-vmcorpwebshd01
int-eth0-vmcorpwebshd02
pe-sql-corp-sql-shd-eastus.nic.bf0f2b6b-5785-40d2-a9bf-851187f3e158

Network security group
nsg-snet-corp-shd-eastus-LoadBalance
nsg-snet-corp-shd-eastus-database
nsg-snet-corp-shd-eastus-webserver

Private DNS zone
privatelink.database.windows.net

Private endpoint
pe-sql-corp-sql-shd-eastus

Public IP address
pip-bastion-1

SQL database
db-corp-sql-shd-eastus (sql-corp-sql-shd-eastus/db-corp-sql-shd-eastus)

SQL server
sql-corp-sql-shd-eastus

Storage account
stgvmcorpshdeastus

Virtual machine
vmcorpwebshd01
vmcorpwebshd02

Virtual network
vnet-corp-shd-eastus

Vamos ao nosso script (muito atenção as variaveis)

#!/bin/bash

##Declarando Variaveis (Obrigatório)
export Subscription_Name="XXXXXXXXXXXXXXXX" ###Substituir pela sua subscription
export RG="rg"
export Depto_Corp="corp"
export ObjectNameWEB="web"
export ObjectSQL="sql"
export ObjectBastion="bastion"
export Env="shd"
export Location="eastus"
export RGNameCorp="${RG}"-"${Depto_Corp}"-"${ObjectNameWEB}"-"${Env}"-"${Location}"

##Declarando Variaveis de Bastion
export BastionName="${ObjectBastion}"

##Declarando Variaveis de VMs
export VM_Object_Name="vm"
export VMNumber01="01"
export VMNumber02="02"
export VMName01="${VM_Object_Name}""${Depto_Corp}""${ObjectNameWEB}""${Env}""${VMNumber01}"
export VMName02="${VM_Object_Name}""${Depto_Corp}""${ObjectNameWEB}""${Env}""${VMNumber02}"
export SKU="Standard_LRS"
export IMGWindows="Win2016Datacenter"
export OsDisk_Name="disk-os"
export OsDiskName01="${OsDisk_Name}"-"${VMName01}"
export OsDiskName02="${OsDisk_Name}"-"${VMName02}"
export Int_Object_Name01="int"
export Int_Object_Name02="eth"
export Int_Number="0"
export IntName01="${Int_Object_Name01}"-"${Int_Object_Name02}""${Int_Number}"-"${VMName01}"
export IntName02="${Int_Object_Name01}"-"${Int_Object_Name02}""${Int_Number}"-"${VMName02}"
export Zone01="1"
export Zone02="2"
export UserName="azroot"
export Password="[email protected]#"
export VMSize="Standard_DS1_v2"

##Declarando Variaveis de Vnet
export vnet="vnet"
export VnetNameWeb="${vnet}"-"${Depto_Corp}"-"${Env}"-"${Location}"
export CIDR_Web="10.128.12.0/24"
export Prefix_WebServer="10.128.12.0/26"
export Prefix_WebDatabase="10.128.12.64/26"
export Prefix_loadBalance="10.128.12.128/26"
export Prefix_Bastion="10.128.12.192/26"
export snet="snet"
export Snet_WebServer="webserver"
export Snet_Database="database"
export Snet_LoadBalance="LoadBalance"
export Snet_Bastion="Bastion"
export SnetNameWeb="${snet}"-"${Depto_Corp}"-"${Env}"-"${Location}"-"${Snet_WebServer}"
export SnetNameDatabase="${snet}"-"${Depto_Corp}"-"${Env}"-"${Location}"-"${Snet_Database}"
export SnetNameLoadBalance="${snet}"-"${Depto_Corp}"-"${Env}"-"${Location}"-"${Snet_LoadBalance}"
export SnetNameBastion="AzureBastionSubnet"

##Declarando Variaveis NSG
export NSG_Object_Name="nsg"
export NSGNameWEB="${NSG_Object_Name}"-"${snet}"-"${Depto_Corp}"-"${Env}"-"${Location}"-"${Snet_WebServer}"
export NSGNameSQL="${NSG_Object_Name}"-"${snet}"-"${Depto_Corp}"-"${Env}"-"${Location}"-"${SnetNameDatabase}"
export NSGNameLoadBalance="${NSG_Object_Name}"-"${snet}"-"${Depto_Corp}"-"${Env}"-"${Location}"-"${Snet_LoadBalance}"

##Declarando Variaveis de IP Publico
export PublicIPObject="pip"
export PublicIPSku="Standard"
export PublicIPName01="${PublicIPObject}"-"${ObjectBastion}"-1
export PublicIPName02="${PublicIPObject}"-"${ObjectNameLB}"-"${Depto_Corp}"-"${ObjectNameWEB}"-"${Env}"-"${Location}"-1

##Declarando Variaveis de Storage Account
export StorageObjectName="stg"
export StorageNameWEB="${StorageObjectName}""${VM_Object_Name}""${Depto_Corp}""${Env}""${Location}"

##Variaveis de Load Balance
export ObjectNameLB="lb"
export ObjectName_LB="${ObjectNameLB}"-"${Depto_Corp}"-"${ObjectNameWEB}"-"${Env}"-"${Location}"
export SKULB="Standard"
export FE_LB_NAME="fe-lb-web"
export BE_LB_NAME="be-lb-web"
export PoolFE="PoolFE"
export PoolBE="PoolBackend"

##Variaveis de SQL
export SQLName01="sql"
export SQLName02="db"
export SQLName="${SQLName01}"-"${Depto_Corp}"-"${ObjectSQL}"-"${Env}"-"${Location}"
export SQLNameDB01="db"
export SQLNameDB="${SQLName02}"-"${Depto_Corp}"-"${ObjectSQL}"-"${Env}"-"${Location}"
export SQLService="s0"
export SQLPass="Pe7I8xE5dE2i22A3gU1a3c4Do2e"

##Variaveis Private Endpoint SQL
export PrivateEndpointNameObject="pe"
export PrivateEndpointName="${PrivateEndpointNameObject}"-"${SQLName01}"-"${Depto_Corp}"-"${ObjectSQL}"-"${Env}"-"${Location}"

##Variaveis de Tags
export Description="Departamento"
export Value_Description="${Depto_Corp}"
export Cost_Center="Centro de Custo"
export Cost_Center_Value="${Depto_Corp}"
export Support_Description_Description="E-mail Suporte"
export Support_Description_Value="support"-"${Depto_Corp}""@xpto.com"

###Selecionar subscription
az account set --subscription "${Subscription_Name}"

###Criando Resource Group
az group create -n "${RGNameCorp}" -l "${Location}" \
--tags "${Description}"="${Value_Description}" "${Cost_Center}"="${Cost_Center_Value}" "${Support_Description_Description}"="${Support_Description_Value}"

###Criando Storage Account
az storage account create -g "${RGNameCorp}" -n "${StorageNameWEB}" --sku "${SKU}" -l "${Location}" \
--tags "${Description}"="${Value_Description}" "${Cost_Center}"="${Cost_Center_Value}" "${Support_Description_Description}"="${Support_Description_Value}"

###Criando NSG
az network nsg create -g "${RGNameCorp}" -n "${NSGNameWEB}" -l "${Location}" \
--tags "${Description}"="${Value_Description}" "${Cost_Center}"="${Cost_Center_Value}" "${Support_Description_Description}"="${Support_Description_Value}"

az network nsg create -g "${RGNameCorp}" -n "${NSGNameSQL}" -l "${Location}" \
--tags "${Description}"="${Value_Description}" "${Cost_Center}"="${Cost_Center_Value}" "${Support_Description_Description}"="${Support_Description_Value}"

az network nsg create -g "${RGNameCorp}" -n "${NSGNameLoadBalance}" -l "${Location}" \
--tags "${Description}"="${Value_Description}" "${Cost_Center}"="${Cost_Center_Value}" "${Support_Description_Description}"="${Support_Description_Value}"

###Criando Vnet
az network vnet create -g "${RGNameCorp}" -n "${VnetNameWeb}" --address-prefix "${CIDR_Web}" \
-l "${Location}" --tags "${Description}"="${Value_Description}" "${Cost_Center}"="${Cost_Center_Value}" "${Support_Description_Description}"="${Support_Description_Value}"

###Criando Snet
az network vnet subnet create -g "${RGNameCorp}" --vnet-name "${VnetNameWeb}" \
-n "${SnetNameWeb}" --address-prefixes "${Prefix_WebServer}"
az network vnet subnet create -g "${RGNameCorp}" --vnet-name "${VnetNameWeb}" \
-n "${SnetNameDatabase}" --address-prefixes "${Prefix_WebDatabase}"
az network vnet subnet create -g "${RGNameCorp}" --vnet-name "${VnetNameWeb}" \
-n "${SnetNameLoadBalance}" --address-prefixes "${Prefix_loadBalance}"
az network vnet subnet create -g "${RGNameCorp}" --vnet-name "${VnetNameWeb}" \
-n "${SnetNameBastion}" --address-prefixes "${Prefix_Bastion}"

###Anexando NSG a Snet
az network vnet subnet update -g "${RGNameCorp}" --vnet-name "${VnetNameWeb}" \
-n "${SnetNameWeb}" --network-security-group "${NSGNameWEB}"
az network vnet subnet update -g "${RGNameCorp}" --vnet-name "${VnetNameWeb}" \
-n "${SnetNameDatabase}" --network-security-group "${NSGNameSQL}"
az network vnet subnet update -g "${RGNameCorp}" --vnet-name "${VnetNameWeb}" \
-n "${SnetNameLoadBalance}" --network-security-group "${NSGNameLoadBalance}"

###Criando IP Publico
az network public-ip create -g "${RGNameCorp}" -n "${PublicIPName01}" --sku "${PublicIPSku}"
az network public-ip create -g "${RGNameCorp}" -n "${PublicIPName02}" --sku "${PublicIPSku}"

###Criando Bastion Host
az network bastion create -g "${RGNameCorp}" --name "${BastionName}" \
--public-ip-address "${PublicIPName01}" --vnet-name "${VnetNameWeb}" --location "${Location}"

##Declarando Variaveis para utilizar Snet existente
Snet01=$(az network vnet subnet show -n "${SnetNameWeb}" --vnet-name "${VnetNameWeb}" -g "${RGNameCorp}" --query id --output tsv)
Snet02=$(az network vnet subnet show -n "${SnetNameDatabase}" --vnet-name "${VnetNameWeb}" -g "${RGNameCorp}" --query id --output tsv)

###Criando Interface de rede
az network nic create -g "${RGNameCorp}" -n "${IntName01}" -g "${RGNameCorp}" --subnet $Snet01 \
--accelerated-networking false --tags "${Description}"="${Value_Description}" "${Cost_Center}"="${Cost_Center_Value}" "${Support_Description_Description}"="${Support_Description_Value}"

az network nic create -g "${RGNameCorp}" -n "${IntName02}" -g "${RGNameCorp}" --subnet $Snet01 \
--accelerated-networking false --tags "${Description}"="${Value_Description}" "${Cost_Center}"="${Cost_Center_Value}" "${Support_Description_Description}"="${Support_Description_Value}"

##Declarando varivel para utilizar IP Fixo existente
FixIP01=$(az network nic ip-config show -g "${RGNameCorp}" -n ipconfig1 --nic-name "${IntName01}" --query privateIpAddress --output tsv)
FixIP02=$(az network nic ip-config show -g "${RGNameCorp}" -n ipconfig1 --nic-name "${IntName02}" --query privateIpAddress --output tsv)


###Fixando IP nas interfaces
az network nic ip-config update -g "${RGNameCorp}" --nic-name "${IntName01}" \
-n ipconfig1 --private-ip-address $FixIP01

az network nic ip-config update -g "${RGNameCorp}" --nic-name "${IntName02}" \
-n ipconfig1 --private-ip-address $FixIP02

##Variavel para criacao da VM
NIC01=$(az network nic show -n "${IntName01}" -g "${RGNameCorp}" --query id --output tsv)
NIC02=$(az network nic show -n "${IntName02}" -g "${RGNameCorp}" --query id --output tsv)

###Criando as VMs
az vm create -n "${VMName01}" -g "${RGNameCorp}" -l "${Location}" --zone "${Zone01}" \
--boot-diagnostics-storage "${StorageNameWEB}" \
--os-disk-name "${OsDiskName01}" --image "${IMGWindows}" --nics $NIC01 --admin-username "${UserName}" \
--admin-password "${Password}" --size "${VMSize}" \
--tags "${Description}"="${Value_Description}" "${Cost_Center}"="${Cost_Center_Value}" "${Support_Description_Description}"="${Support_Description_Value}"

az vm create -n "${VMName02}" -g "${RGNameCorp}" -l "${Location}" --zone "${Zone02}" \
--boot-diagnostics-storage "${StorageNameWEB}" \
--os-disk-name "${OsDiskName02}" --image "${IMGWindows}" --nics $NIC02 --admin-username "${UserName}" \
--admin-password "${Password}" --size "${VMSize}" --tags "${Description}"="${Value_Description}" "${Cost_Center}"="${Cost_Center_Value}" "${Support_Description_Description}"="${Support_Description_Value}"

## Instalanado IIS VMs WEB
az vm extension set --publisher Microsoft.Compute --version 1.8 --name CustomScriptExtension \
--vm-name "${VMName01}" --resource-group "${RGNameCorp}" \
--settings '{"commandToExecute":"powershell Add-WindowsFeature Web-Server -IncludeManagementTools; powershell Add-Content -Path \"C:\\inetpub\\wwwroot\\Default.htm\" -Value $($env:computername)"}'

az vm extension set --publisher Microsoft.Compute --version 1.8 --name CustomScriptExtension \
--vm-name "${VMName02}" --resource-group "${RGNameCorp}" \
--settings '{"commandToExecute":"powershell Add-WindowsFeature Web-Server -IncludeManagementTools; powershell Add-Content -Path \"C:\\inetpub\\wwwroot\\Default.htm\" -Value $($env:computername)"}'

##Criando Load Balance
az network lb create -g "${RGNameCorp}" -n "${ObjectName_LB}" --sku "${SKULB}" \
--public-ip-address "${PublicIPName02}"  \
--frontend-ip-name "${PoolFE}" --backend-pool-name "${PoolBE}" \
--tags "${Description}"="${Value_Description}" "${Cost_Center}"="${Cost_Center_Value}" "${Support_Description_Description}"="${Support_Description_Value}"

#Create health probe on port 80/443
az network lb probe create -g "${RGNameCorp}" --lb-name "${ObjectName_LB}" \
--name "Probe-HTTPS" --protocol tcp --port 443

az network lb probe create -g "${RGNameCorp}" --lb-name "${ObjectName_LB}" \
--name "Probe-HTTP" --protocol tcp --port 80

#Create load balancer rule for port 80/443
az network lb rule create -g "${RGNameCorp}" --lb-name "${ObjectName_LB}" \
--name "HTTP" --protocol "tcp" --frontend-port 80 --backend-port 80 --frontend-ip-name "${PoolFE}" \
--backend-pool-name "${PoolBE}" --probe-name Probe-HTTP

az network lb rule create -g "${RGNameCorp}" --lb-name "${ObjectName_LB}" \
--name "HTTPS" --protocol "tcp" --frontend-port 443 --backend-port 443 --frontend-ip-name "${PoolFE}" \
--backend-pool-name "${PoolBE}" --probe-name Probe-HTTPS

#Adicionando Inteface REDE ao pool de backend Load Balance
az network nic ip-config address-pool add \
--address-pool "${PoolBE}" \
--ip-config-name "ipconfig1" \
--nic-name "${IntName01}" \
--resource-group "${RGNameCorp}" \
--lb-name "${ObjectName_LB}"

az network nic ip-config address-pool add \
--address-pool "${PoolBE}" \
--ip-config-name "ipconfig1" \
--nic-name "${IntName02}" \
--resource-group "${RGNameCorp}" \
--lb-name "${ObjectName_LB}"

##Criando Azure Data Base SQL Server
az sql server create -g "${RGNameCorp}" -n "${SQLName}" --admin-user "azroot" --admin-password "${SQLPass}" --enable-public-network "false" -l "${Location}" --minimal-tls-version "1.2"

##Criando Azure Data Base SQL DB
az sql db create -g "${RGNameCorp}" -s "${SQLName}"  -n "${SQLNameDB}" --service-objective "${SQLService}" 

###https://docs.microsoft.com/en-us/azure/templates/microsoft.sql/servers?tabs=bicep
###Variaveis de PEP
SQL_ID=$(az resource show -g "${RGNameCorp}" -n "${SQLName}" --resource-type "Microsoft.Sql/servers" --query "id" -o tsv)

##Segunda Opção para conseguri o ID
SQL_ID2=$(az sql server list \
    --resource-group "${RGNameCorp}" \
    --query '[].[id]' \
    --output tsv)

##Desabilitando Politica de rede
#https://docs.microsoft.com/pt-br/azure/private-link/disable-private-endpoint-network-policy
az network vnet subnet update --disable-private-endpoint-network-policies "true" \
--name "${SnetNameDatabase}" \
--resource-group "${RGNameCorp}" \
--vnet-name "${VnetNameWeb}"

###Variavel para GroupID
GroupID_SQL=$(az network private-link-resource list -g "${RGNameCorp}" -n "${SQLName}" --type "Microsoft.Sql/servers" --query "[]".properties[].groupId -o tsv)

##Criando Private Endpoint SQL
az network private-endpoint create --connection-name "connect-private-sql" \
--group-id $GroupID_SQL --name "${PrivateEndpointName}" \
--private-connection-resource-id $SQL_ID2 -g "${RGNameCorp}" \
--subnet "${SnetNameDatabase}" --vnet-name "${VnetNameWeb}" \
--tags "${Description}"="${Value_Description}" "${Cost_Center}"="${Cost_Center_Value}" "${Support_Description_Description}"="${Support_Description_Value}"

###Variaveis para Zone Names
ZoneName_SQL=$(az network private-link-resource list -g "${RGNameCorp}" -n "${SQLName}" --type "Microsoft.Sql/servers" --query "[]".properties[].requiredZoneNames -o tsv)

##Somente se não existir uma private zone igual a desejada na mesma subscription

#Create Private DNS Zone
az network private-dns zone create -g "${RGNameCorp}" \
--name $ZoneName_SQL

#Create DNS records 
az network private-dns record-set a create --name "${SQLName}" \
--zone-name $ZoneName_SQL --resource-group "${RGNameCorp}"

###Variaveis para utilizar IP da interface Private Endpoint
V01=$(az network nic list -g "${RGNameCorp}" --query "[].ipConfigurations[?contains (name, 'private')].name" -o tsv)
V02=$(az network nic list -g "${RGNameCorp}" --query "[?contains(name, 'pe-sql')].name" -o tsv)
V03=$(az network nic ip-config show -g "${RGNameCorp}" -n $V01 --nic-name $V02 --query privateIpAddress --output tsv)

##Informando IP na Zona de DNS Privada
az network private-dns record-set a add-record --record-set-name "${SQLName}" \
--zone-name $ZoneName_SQL --resource-group "${RGNameCorp}" -a $V03


#Link private vnet
az network private-dns link vnet create -g "${RGNameCorp}" \
--zone-name  $ZoneName_SQL \
--name "link-sql-dns" --virtual-network "${VnetNameWeb}" \
--registration-enabled false

###Fim do Script

Após execução do script acima, todos os objetos foram criados, vamos ver o funcinamento

Vamos acessar uma das VMs via “Bastion Host” e ver as informação de rede

Vamos ver nosso WebServer

Vamos para a outra VM

Vamos ver nosso WebServer

Agora vamos acessar via LB

Vamos ver nosso Banco de dados

Nosso server de banco de dados tem esse endereço “sql-corp-sql-shd-eastus.database.windows.net” e private link “sql-corp-sql-shd-eastus.privatelink.database.windows.net”, vamos confirmar o DNS que estamos utilizando

o DNS utilizado é “168.63.129.16” DNS padrão do azure, vamos ver como ele resolver nosso private link:

Vamos ai teste de acesso por dentro de uma das VM WEB

Essa Feature é muito util para não expor seus objetos para internet, isso server para Azure Cache Redis, Storare Account, etc…

Espero ter ajudado.

Seja Feliz!!!!