You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# Create private DNS zone for Web Apps
az network private-dns zone create \
--resource-group $RESOURCE_GROUP \
--name "privatelink.azurewebsites.net"
az network private-dns link vnet create \
--resource-group $RESOURCE_GROUP \
--zone-name "privatelink.azurewebsites.net" \
--name WebsitesPrivateDNSLink \
--virtual-network $VNET_NAME \
--registration-enabled false# Set Backend to deny public traffic
az webapp config access-restriction set \
--name $BACKEND_APP \
--resource-group $RESOURCE_GROUP \
--default-action Deny \
--use-same-restrictions-for-scm-site true# Create Backend private endpoint
BACKEND_APP_ID=$(az webapp show \ --name $BACKEND_APP \ --resource-group $RESOURCE_GROUP \ --query id -o tsv)
PE_BACKEND_SUBNET_ID=$(az network vnet subnet show \ --resource-group "$RESOURCE_GROUP" \ --vnet-name "$VNET_NAME" \ --name "$PE_SUBNET_NAME" \ --query id -o tsv)
az network private-endpoint create \
--name "pe-${BACKEND_APP}" \
--resource-group $RESOURCE_GROUP \
--subnet $PE_BACKEND_SUBNET_ID \
--private-connection-resource-id $BACKEND_APP_ID \
--group-id sites \
--connection-name "pe-${BACKEND_APP}-conn" \
--location $LOCATION
az network private-endpoint dns-zone-group create \
--endpoint-name "pe-${BACKEND_APP}" \
--name zonegroup \
--resource-group $RESOURCE_GROUP \
--private-dns-zone "privatelink.azurewebsites.net" \
--zone-name "privatelink.azurewebsites.net"# Create Frontend private endpoint
FRONTEND_APP_ID=$(az webapp show \ --name $FRONTEND_APP \ --resource-group $RESOURCE_GROUP \ --query id -o tsv)
PE_FRONTEND_SUBNET_ID=$(az network vnet subnet show \ --resource-group "$RESOURCE_GROUP" \ --vnet-name "$VNET_NAME" \ --name "$PE_UI_SUBNET_NAME" \ --query id -o tsv)
az network private-endpoint create \
--name "pe-${FRONTEND_APP}" \
--resource-group $RESOURCE_GROUP \
--subnet $PE_FRONTEND_SUBNET_ID \
--private-connection-resource-id $FRONTEND_APP_ID \
--group-id sites \
--connection-name "pe-${FRONTEND_APP}-conn" \
--location $LOCATION
az network private-endpoint dns-zone-group create \
--endpoint-name "pe-${FRONTEND_APP}" \
--name zonegroup \
--resource-group $RESOURCE_GROUP \
--private-dns-zone "privatelink.azurewebsites.net" \
--zone-name "privatelink.azurewebsites.net"# Allow Backend to be accessed from Frontend subnet
az webapp config access-restriction add \
--name $BACKEND_APP \
--resource-group $RESOURCE_GROUP \
--rule-name allow-frontend-vnet \
--action Allow \
--subnet $FRONTEND_SUBNET_ID \
--priority 100
12. Setup Azure Bastion
# Create Bastion subnet
az network vnet subnet create \
--resource-group $RESOURCE_GROUP \
--vnet-name $VNET_NAME \
--name $BAS_SUBNET_NAME \
--address-prefix $BAS_SUBNET_PREFIX# Create public IP for Bastion
az network public-ip create \
--resource-group $RESOURCE_GROUP \
--name $BAS_PIP_NAME \
--sku Standard \
--allocation-method Static
# Create Bastion host
az network bastion create \
--name $BAS_HOST_NAME \
--resource-group $RESOURCE_GROUP \
--vnet-name $VNET_NAME \
--public-ip-address $BAS_PIP_NAME \
--location $LOCATION \
--sku Standard
# Enable tunneling for Bastion
az network bastion update \
--resource-group $RESOURCE_GROUP \
--name $BAS_HOST_NAME \
--sku name=Standard \
--enable-tunneling true
13. Configure Logging
# Configure logging for both appsforAPPin$BACKEND_APP$FRONTEND_APP;do
az webapp log config \
--name $APP \
--resource-group $RESOURCE_GROUP \
--application-logging filesystem \
--detailed-error-messages true \
--failed-request-tracing true \
--docker-container-logging filesystem \
--level Information
done# Build frontend with ARGs
docker buildx build --no-cache --platform linux/amd64 \
--build-arg NEXT_PUBLIC_API_URL="https://${BACKEND_APP}.azurewebsites.net" \
--build-arg AZURE_STORAGE_ACCOUNT_NAME="$STORAGE_ACCOUNT_NAME" \
--build-arg AZURE_STORAGE_ACCOUNT_KEY="$STORAGE_KEY" \
--build-arg AZURE_STORAGE_CONTAINER_NAME="$CONTAINER_NAME" \
-t "${ACR_LOGIN_SERVER}/frontend:latest" \
--push .# Restart apps to apply all changes
az webapp restart -g $RESOURCE_GROUP -n $BACKEND_APP
az webapp restart -g $RESOURCE_GROUP -n $FRONTEND_APP
Fixes
az webapp config access-restriction set \
--name "$FRONTEND_APP" \
--resource-group "$RESOURCE_GROUP" \
--default-action Allow
az webapp config access-restriction show \
--name "$FRONTEND_APP" \
--resource-group "$RESOURCE_GROUP" \
--query ipSecurityRestrictions
az webapp config access-restriction remove \
--name "$FRONTEND_APP" \
--resource-group "$RESOURCE_GROUP" \
--rule-name "DenyAll"
az webapp show \
--name "$FRONTEND_APP" \
--resource-group "$RESOURCE_GROUP" \
--query publicNetworkAccess
az webapp update \
--name "$FRONTEND_APP" \
--resource-group "$RESOURCE_GROUP" \
--set publicNetworkAccess=Enabled
az webapp restart -g "$RESOURCE_GROUP" -n "$FRONTEND_APP"
Front-end App Service
# 1. Check IP access restrictions
az webapp config access-restriction show \
--name "$FRONTEND_APP" \
--resource-group "$RESOURCE_GROUP" \
--query ipSecurityRestrictions
# 2. Check whether public access is on
az webapp show \
--name "$FRONTEND_APP" \
--resource-group "$RESOURCE_GROUP" \
--query publicNetworkAccess
# 3. Turn public access back ON
az webapp update \
--name "$FRONTEND_APP" \
--resource-group "$RESOURCE_GROUP" \
--set publicNetworkAccess=Enabled
# 4. Restart the front-end to pick up the change
az webapp restart -g "$RESOURCE_GROUP" -n "$FRONTEND_APP"
VNet / Subnet Hardening
# 5. Add Service Endpoints to the front-end subnet
az network vnet subnet update \
--resource-group "$RESOURCE_GROUP" \
--vnet-name "$VNET_NAME" \
--name "$FRONTEND_SUBNET_NAME" \
--service-endpoints Microsoft.Web Microsoft.Storage
# 6. Allow that subnet to reach the Storage account
az storage account network-rule add \
--resource-group "$RESOURCE_GROUP" \
--account-name "$STORAGE_ACCOUNT_NAME" \
--vnet-name "$VNET_NAME" \
--subnet "$FRONTEND_SUBNET_NAME"# 7. Add Service Endpoints to the back-end subnet
az network vnet subnet update \
--resource-group "$RESOURCE_GROUP" \
--vnet-name "$VNET_NAME" \
--name "$BACKEND_SUBNET_NAME" \
--service-endpoints Microsoft.Web Microsoft.Storage
# 8. Allow the back-end subnet to reach the Storage account
az storage account network-rule add \
--resource-group "$RESOURCE_GROUP" \
--account-name "$STORAGE_ACCOUNT_NAME" \
--vnet-name "$VNET_NAME" \
--subnet "$BACKEND_SUBNET_NAME"# 9. Quick report: which subnets are allowed?
az storage account network-rule list \
-g "$RESOURCE_GROUP" -n "$STORAGE_ACCOUNT_NAME" \
--query "[].{subnet:id,action:action}" -o table
# 10. Slam the public door on the Storage account
az storage account update \
--resource-group "$RESOURCE_GROUP" \
--name "$STORAGE_ACCOUNT_NAME" \
--default-action Deny
Back-end App Service
# 11. Restart the back-end to adopt new network rules
az webapp restart -g "$RESOURCE_GROUP" -n "$BACKEND_APP"
Bastion Hardening & Tunnelling
# 12. Switch on Bastion "IP Connect" (required for private-IP tunnels)
az network bastion update \
--name "$BAS_HOST_NAME" \
--resource-group "$RESOURCE_GROUP" \
--enable-ip-connect true
Management Subnet & Jump-host VM
# -- basic variables (example) --
MGMT_SUBNET_NAME="mgmt-subnet2"
MGMT_SUBNET_PREFIX="10.0.8.0/24"
NSG_NAME="${MGMT_SUBNET_NAME}-nsg"
VM_NAME="log-admin-vm"
NIC_NAME="${VM_NAME}-nic"
VM_SIZE="Standard_B2s"# 13. Create an NSG for the management subnet
az network nsg create \
--resource-group "$RESOURCE_GROUP" \
--name "$NSG_NAME" \
--location "$LOCATION"# 14. Let anything inside the VNet SSH to anything else (port 22)
az network nsg rule create \
--resource-group "$RESOURCE_GROUP" \
--nsg-name "$NSG_NAME" \
--name allow-ssh-from-vnet \
--priority 100 \
--access Allow \
--direction Inbound \
--protocol Tcp \
--source-address-prefixes VirtualNetwork \
--destination-port-ranges 22
# 15. Carve out the mgmt subnet and attach the NSG
az network vnet subnet create \
--resource-group "$RESOURCE_GROUP" \
--vnet-name "$VNET_NAME" \
--name "$MGMT_SUBNET_NAME" \
--address-prefixes "$MGMT_SUBNET_PREFIX" \
--network-security-group "$NSG_NAME"# 16. Sanity-check: show the new subnet's CIDR
az network vnet subnet show \
--resource-group "$RESOURCE_GROUP" \
--vnet-name "$VNET_NAME" \
--name "$MGMT_SUBNET_NAME" \
--query "{name:name, prefix:addressPrefix}"# 17. Private NIC for the jump host
az network nic create \
--resource-group "$RESOURCE_GROUP" \
--name "$NIC_NAME" \
--vnet-name "$VNET_NAME" \
--subnet "$MGMT_SUBNET_NAME"# 18. Deploy the jump-host VM (no public IP)
az vm create \
--resource-group "$RESOURCE_GROUP" \
--name "$VM_NAME" \
--nics "$NIC_NAME" \
--image Ubuntu2204 \
--size "$VM_SIZE" \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-address ""# 19. SSH into the VM through Bastion
az network bastion ssh \
--name "$BAS_HOST_NAME" \
--resource-group "$RESOURCE_GROUP" \
--target-resource-id $(az vm show -g "$RESOURCE_GROUP" -n "$VM_NAME" --query id -o tsv) \
--username azureuser \
--ssh-key "$HOME/.ssh/id_rsa" \
--auth-type ssh-key