Secure Storage with Private Endpoints

Lock down Blob Storage and repair a Private Endpoint DNS failure.

Introduction

30 Second Summary

A locked door only protects the entrance people actually use. A service can have a private entrance while its familiar address still sends traffic the wrong way.

In this project, you will build an isolated Azure Blob Storage path behind a Private Endpoint. You will use a test virtual machine to prove that the same Blob hostname changes from a public address to the private endpoint address after your Private DNS repair.

What You'll Build

Picture running the same diagnostic twice and watching the normal Blob hostname switch from a public IP to the private endpoint's IP after your repair.

By the end of this project, you'll have:

  • A network-isolated storage path that rejects public network traffic while serving Blob requests through a private IP.
  • A before-and-after diagnostic that compares the VM-resolved address with the private endpoint address to expose the DNS mismatch and verify the repair.
  • A portfolio-ready evidence trail that tells the full troubleshooting story from deliberate failure through verified remediation using Bicep and Git.
  • Secret Mission: Refactor the Private DNS repair into a reusable local Bicep module without changing the deployed Azure resources.

Are there any prerequisites?

Your active Azure subscription must permit resource-group deployments and VM commands.

You also need the local AZ104-Portfolio repository from Projects 01 to 03.

Before We Start

Before the hands-on work begins, lock in the isolated Azure Blob Storage path you are building. Your proof will show the same standard Blob FQDN changing from a public address to the Private Endpoint's private IP after Private DNS integration.

Set Up the Project Workspace

Private networking labs can fail before the network design is tested. A missing tool or unavailable regional image can hide the DNS incident you want to study.

This step separates those environment problems from the planned failure. You will verify Windows PowerShell, Azure CLI, Bicep, Git, and Visual Studio Code before creating the lab files.

In this step, get ready to:
  • Complete the local tool preflight.
  • Confirm the Denmark East location plus its Windows Server image.
  • Create the Project 04 workspace plus its Git checkpoint.
Verify the toolchain and region

The deployment depends on local commands plus an Azure image in Denmark East. This preflight proves those dependencies work before any resources incur charges.

  • Start Windows PowerShell in your existing AZ104-Portfolio folder.
  • Confirm the PowerShell prompt ends with AZ104-Portfolio.
  • Check the installed Bicep CLI version by running:
az bicep version

Bicep Check

This command asks Azure CLI for the installed Bicep version. You should see version details without an error.

  • Check the installed Git version by running:
git --version

Git Check

This command confirms that Git is available from Windows PowerShell. You should see a Git version number.

  • Check the installed Visual Studio Code version by running:
code --version

VS Code Check

This command confirms that the Visual Studio Code command-line launcher is on your system path. You should see version details across several lines.

  • Check the Denmark East Windows Server image by running:
az vm image show --location denmarkeast --urn MicrosoftWindowsServer:WindowsServer:2022-datacenter-azure-edition:latest

Image Check

This query checks whether the required Windows Server 2022 Datacenter Azure Edition image is available in denmarkeast. You should see image details without an error.

  • List the Azure locations available to your subscription by running:
az account list-locations -o table

Region Check

This command prints the Azure locations your subscription can use. Confirm that you can see a row named Denmark East.

Preflight Check Failing?

  • Confirm that Windows PowerShell is running from the existing AZ104-Portfolio folder.
  • Repair any local tool that Windows PowerShell cannot find before continuing.
  • Confirm that your active Azure subscription can access Denmark East if the location or image query fails.
  • Help me diagnose my Project 04 preflight failure.
Create the project files

The workspace separates the baseline deployment from the later DNS repair. A PowerShell diagnostic script gives both states the same repeatable test.

  • Open the repository in Visual Studio Code by running:
code .

What Does This Command Do?

This command opens the current AZ104-Portfolio folder in Visual Studio Code. The existing repository should appear in the Explorer sidebar.

  • Click the Explorer icon in the left sidebar.
  • Select the AZ104-Portfolio folder in the Explorer sidebar.
  • Click the New Folder button at the top of the Explorer sidebar.
  • Enter project-04-secure-storage-private-endpoint as the folder name.
  • Press Enter to create the folder.

You should see project-04-secure-storage-private-endpoint under the repository root.

  • Select the project-04-secure-storage-private-endpoint folder.
  • Click the New File button at the top of the Explorer sidebar.
  • Enter main.bicep as the file name.
  • Press Enter to create the file.
  • Click the New File button again.
  • Enter dns-fix.bicep as the file name.
  • Press Enter to create the file.

You should now see both Bicep files inside the Project 04 folder.

  • Select the project-04-secure-storage-private-endpoint folder.
  • Click the New Folder button.
  • Enter scripts as the folder name.
  • Press Enter to create the folder.
  • Select the new scripts folder.
  • Click the New File button.
  • Enter Test-PrivateEndpoint.ps1 as the file name.
  • Press Enter to create the file.

The Explorer sidebar should now show two Bicep files plus scripts/Test-PrivateEndpoint.ps1.

  • Select main.bicep in the Explorer sidebar.
  • Populate the empty file using the complete reference in the second tab.

✔️ main.bicep is complete

Great. Save main.bicep before compiling it.

ⓧ I'd like to double check the full code

  • Compare main.bicep with this complete file.
  • Replace any missing or different content with the matching reference below:
targetScope = 'resourceGroup'

@description('Deployment location for Project 04 resources.')
param location string = resourceGroup().location

@description('Administrator username for the Windows test VM.')
param adminUsername string = 'az104admin'

@description('Administrator password for the Windows test VM. Enter it only at the Azure CLI prompt.')
@minLength(12)
@secure()
param adminPassword string

@description('VM size for the short-lived diagnostic host.')
param vmSize string = 'Standard_B1s'

var vnetName = 'vnet-p04-storage'
var workloadSubnetName = 'snet-workload'
var privateEndpointSubnetName = 'snet-private-endpoints'
var nsgName = 'nsg-p04-workload'
var publicIpName = 'pip-p04-testvm'
var nicName = 'nic-p04-testvm'
var vmName = 'vm-p04-dns-test'
var privateEndpointName = 'pe-p04-storage-blob'
var storageAccountName = 'staz104p04${uniqueString(subscription().id, resourceGroup().id)}'

resource workloadNsg 'Microsoft.Network/networkSecurityGroups@2022-05-01' = {
  name: nsgName
  location: location
  properties: {
    securityRules: []
  }
}

resource testVmPublicIp 'Microsoft.Network/publicIPAddresses@2022-05-01' = {
  name: publicIpName
  location: location
  sku: {
    name: 'Standard'
  }
  properties: {
    publicIPAllocationMethod: 'Static'
  }
}

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2025-01-01' = {
  name: vnetName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.40.0.0/16'
      ]
    }
  }

  resource workloadSubnet 'subnets' = {
    name: workloadSubnetName
    properties: {
      addressPrefix: '10.40.1.0/24'
      networkSecurityGroup: {
        id: workloadNsg.id
      }
    }
  }

  resource privateEndpointSubnet 'subnets' = {
    name: privateEndpointSubnetName
    properties: {
      addressPrefix: '10.40.2.0/24'
    }
  }
}

resource testVmNic 'Microsoft.Network/networkInterfaces@2022-05-01' = {
  name: nicName
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: {
            id: testVmPublicIp.id
          }
          subnet: {
            id: virtualNetwork::workloadSubnet.id
          }
        }
      }
    ]
  }
}

resource testVm 'Microsoft.Compute/virtualMachines@2022-03-01' = {
  name: vmName
  location: location
  properties: {
    hardwareProfile: {
      vmSize: vmSize
    }
    osProfile: {
      computerName: vmName
      adminUsername: adminUsername
      adminPassword: adminPassword
    }
    storageProfile: {
      imageReference: {
        publisher: 'MicrosoftWindowsServer'
        offer: 'WindowsServer'
        sku: '2022-datacenter-azure-edition'
        version: 'latest'
      }
      osDisk: {
        createOption: 'FromImage'
        managedDisk: {
          storageAccountType: 'StandardSSD_LRS'
        }
      }
      dataDisks: []
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: testVmNic.id
        }
      ]
    }
  }
}

resource storageAccount 'Microsoft.Storage/storageAccounts@2025-06-01' = {
  name: storageAccountName
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
  }
  properties: {
    allowBlobPublicAccess: false
    minimumTlsVersion: 'TLS1_2'
    supportsHttpsTrafficOnly: true
    publicNetworkAccess: 'Disabled'
    networkAcls: {
      bypass: 'None'
      defaultAction: 'Deny'
    }
  }
}

resource blobPrivateEndpoint 'Microsoft.Network/privateEndpoints@2025-05-01' = {
  name: privateEndpointName
  location: location
  properties: {
    subnet: {
      id: virtualNetwork::privateEndpointSubnet.id
    }
    privateLinkServiceConnections: [
      {
        name: 'blob-connection'
        properties: {
          privateLinkServiceId: storageAccount.id
          groupIds: [
            'blob'
          ]
        }
      }
    ]
  }
}

output storageAccountName string = storageAccount.name
output storageBlobFqdn string = '${storageAccount.name}.blob.core.windows.net'
output vmName string = testVm.name
output vnetName string = virtualNetwork.name
output privateEndpointName string = blobPrivateEndpoint.name

What Does This File Define?

  • The parameters collect the deployment location plus the Windows VM administrator settings.
  • The network resources create separate workload and private endpoint subnets.
  • The Storage account disables public network access.
  • The Blob private endpoint gives Storage a private network path.
  • Save main.bicep by pressing Ctrl+S.
  • Switch back to Windows PowerShell.
  • Compile the baseline deployment file by running:
az bicep build --file .\project-04-secure-storage-private-endpoint\main.bicep

What Does This Build Prove?

Bicep compiles main.bicep into an Azure Resource Manager template. A completed command without a compilation error confirms that the file is structurally valid.

Main Template Not Building?

  • Confirm that the file is saved inside project-04-secure-storage-private-endpoint.
  • Compare every resource brace with the full-file reference.
  • Check that main.bicep uses the exact resource names shown above.
  • Help me fix my main Bicep build.
  • Return to Visual Studio Code.
  • Select dns-fix.bicep in the Explorer sidebar.
  • Populate the empty file using the complete reference in the second tab.

✔️ dns-fix.bicep is complete

Great. Save dns-fix.bicep before compiling it.

ⓧ I'd like to double check the full code

  • Compare dns-fix.bicep with this complete file.
  • Replace any missing or different content with the matching reference below:
targetScope = 'resourceGroup'

@description('Name of the existing virtual network.')
param vnetName string

@description('Name of the existing Blob private endpoint.')
param privateEndpointName string

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2025-01-01' existing = {
  name: vnetName
}

resource privateEndpoint 'Microsoft.Network/privateEndpoints@2025-05-01' existing = {
  name: privateEndpointName
}

resource blobPrivateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = {
  name: 'privatelink.blob.core.windows.net'
  location: 'global'
}

resource blobPrivateDnsVnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = {
  parent: blobPrivateDnsZone
  name: 'link-${vnetName}'
  location: 'global'
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: virtualNetwork.id
    }
  }
}

resource blobPrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2025-05-01' = {
  parent: privateEndpoint
  name: 'default'
  properties: {
    privateDnsZoneConfigs: [
      {
        name: 'blob-private-dns-zone'
        properties: {
          privateDnsZoneId: blobPrivateDnsZone.id
        }
      }
    ]
  }
}

output privateDnsZoneName string = blobPrivateDnsZone.name
output virtualNetworkLinkName string = blobPrivateDnsVnetLink.name
output privateDnsZoneGroupName string = blobPrivateDnsZoneGroup.name

What Does This File Define?

  • The existing resource declarations point to the virtual network plus the Blob private endpoint.
  • The Private DNS Zone stores private records for the Blob service.
  • The virtual network link makes the zone available to the test VM.
  • The zone group associates the Blob private endpoint with that zone.
  • Save dns-fix.bicep by pressing Ctrl+S.
  • Switch back to Windows PowerShell.
  • Compile the DNS repair file by running:
az bicep build --file .\project-04-secure-storage-private-endpoint\dns-fix.bicep

What Does This Build Prove?

This build validates the separate DNS repair template. A completed command without a compilation error confirms that its existing-resource references plus new DNS resources are structurally valid.

DNS Template Not Building?

  • Confirm that the file name is dns-fix.bicep.
  • Check the nested properties against the full-file reference.
  • Confirm that each API version matches the reference exactly.
  • Help me fix my DNS Bicep build.
  • Return to Visual Studio Code.
  • Select scripts/Test-PrivateEndpoint.ps1 in the Explorer sidebar.
  • Populate the empty file using the complete reference in the second tab.

✔️ The diagnostic script is complete

Great. Save scripts/Test-PrivateEndpoint.ps1 so it is ready for the deployed test VM.

ⓧ I'd like to double check the full code

  • Compare scripts/Test-PrivateEndpoint.ps1 with this complete file.
  • Replace any missing or different content with the matching reference below:
param(
    [Parameter(Mandatory = $true)]
    [string]$StorageAccountName
)

$fqdn = "$StorageAccountName.blob.core.windows.net"

Write-Output "Storage FQDN: $fqdn"
Clear-DnsClientCache

Write-Output "DNS A records:"
$dnsResults = Resolve-DnsName -Name $fqdn -Type A -ErrorAction Stop
$dnsResults |
    Where-Object { $_.Type -eq 'A' } |
    Select-Object Name, IPAddress

Write-Output "TCP 443 test:"
Test-NetConnection -ComputerName $fqdn -Port 443

Write-Output "Unauthenticated HTTPS probe:"
try {
    $response = Invoke-WebRequest -Uri "https://$fqdn/" -Method Head -UseBasicParsing -ErrorAction Stop
    Write-Output "HTTP status: $([int]$response.StatusCode)"
}
catch {
    if ($null -ne $_.Exception.Response) {
        Write-Output "HTTP status: $([int]$_.Exception.Response.StatusCode)"
    }
    else {
        Write-Output "HTTPS probe error: $($_.Exception.Message)"
    }
}

What Does This Script Test?

  • The StorageAccountName parameter lets the same script test the generated account name.
  • The DNS query prints every resolved A record after clearing the client cache.
  • The TCP test checks network reachability on port 443.
  • The HTTPS probe records a status or a connection error without requiring Storage data credentials.
  • Save scripts/Test-PrivateEndpoint.ps1 by pressing Ctrl+S.

Your workspace now contains the complete baseline template, DNS repair template, and repeatable diagnostic script.

Checkpoint the scaffold in Git

A small first commit gives the project a clean baseline. Later commits can show the deployment evidence plus the DNS repair as separate decisions.

  • Select the root README.md file in the Visual Studio Code Explorer sidebar.
  • Add Project 04 status: In progress as a new status line.
  • Save README.md by pressing Ctrl+S.
  • Switch back to Windows PowerShell.
  • Inspect the new project files plus the README change by running:
git status

What Should Git Show?

Git should list README.md as modified. It should also list the new Project 04 folder as untracked.

  • Stage the Project 04 folder plus the root README by running:
git add project-04-secure-storage-private-endpoint README.md

What Does Staging Do?

This command adds the project folder plus README.md to the next commit. It does not change the file contents.

  • Confirm that the intended files are staged by running:
git status

What Should Be Staged?

Git should show the three project source files plus README.md under the changes ready to commit.

  • Create the scaffold checkpoint by running:
git commit -m "Scaffold Project 04 secure storage lab"

Scaffold Checkpoint Complete

That is the workspace milestone captured. Your Git history now preserves the exact starting point for Project 04.

Git Commit Not Completing?

  • Confirm that Windows PowerShell is still at the AZ104-Portfolio root.
  • Check that the previous Git status showed staged files.
  • Resolve any existing Git identity prompt using the configuration from your earlier portfolio projects.
  • Help me troubleshoot this scaffold commit.

Before you run the final preflight, do you expect every command to return the same successful tool or image details you saw earlier?

  • Recheck the Bicep CLI version by running:
az bicep version

Bicep Verification

You should see the installed Bicep version again. This confirms that the compiler used for both project files remains available.

  • Recheck the Git version by running:
git --version

Git Verification

You should see the Git version again. This confirms that the repository checkpoint tool remains available.

  • Recheck the Visual Studio Code version by running:
code --version

VS Code Verification

You should see Visual Studio Code version details again. This confirms that the command-line launcher still resolves correctly.

  • Recheck the Denmark East Windows Server image by running:
az vm image show --location denmarkeast --urn MicrosoftWindowsServer:WindowsServer:2022-datacenter-azure-edition:latest

Image Verification

You should see the image details without an error. This proves the required VM image is available before the deployment begins.

Strong start. Your tools pass the preflight, both Bicep templates compile, and the scaffold is preserved in Git.

Next up, you will deploy the isolated Storage lab with its deliberate Private DNS gap.

Deploy the Isolated Storage Lab

Your Project 04 files are ready. The compiled Bicep template can now create the live Microsoft Azure lab.

This first deployment creates a locked-down Azure Storage account plus a Blob Private Endpoint. It deliberately leaves Private DNS integration out to create one controlled operational gap.

In this step, get ready to:
  • Create the Denmark East resource group.
  • Deploy the isolated lab after reviewing its what-if preview.
  • Verify the storage controls before documenting the baseline.
Create and preview the baseline

A resource group gives every lab component a shared lifecycle. A what-if preview shows the planned changes before Azure creates the resources.

This Lab Can Incur Charges

This step creates resources that can incur Azure charges. The cleanup section shows how to pause compute or remove the full lab.

  • In the Windows PowerShell window from earlier, create the Denmark East resource group by running these commands:
$resourceGroupName = "rg-az104-p04-secure-storage"
az group create --name $resourceGroupName --location denmarkeast

What Do These Commands Do?

  • The first command stores the resource group name in $resourceGroupName for reuse during this PowerShell session.
  • The second command uses the Azure CLI to create the resource group in denmarkeast.

You should see resource group details with denmarkeast listed as the location.

Resource Group Creation Failed?

Confirm that Azure CLI is still signed in. Check that your active subscription permits resource-group creation.

Ask for help with the exact output if the command still fails: Help me troubleshoot this Azure resource group creation failure.

The what-if operation compares main.bicep with the empty resource group. Azure CLI requests the secure adminPassword parameter while evaluating the VM configuration.

Before you preview, consider whether the template includes any Private DNS resources.

  • Prepare a compliant VM password containing at least 12 characters.
  • Enter the VM password only when Azure CLI requests the secure adminPassword parameter.
  • Move into project-04-secure-storage-private-endpoint before previewing the deployment by running these commands:
Set-Location .\project-04-secure-storage-private-endpoint
az deployment group what-if --name p04-core-preview --resource-group $resourceGroupName --template-file .\main.bicep

What Should the Preview Contain?

  • The network plan contains vnet-p04-storage plus its two subnets.
  • The compute plan contains the NSG plus the public IP. It also contains the NIC plus the VM.
  • The storage plan contains the Storage account plus pe-p04-storage-blob.

You should see these resources marked as additions. No Private DNS resources appear in this preview.

Preview Did Not Complete?

Confirm that PowerShell is inside project-04-secure-storage-private-endpoint. Make sure the password satisfies the template length requirement.

Use the preview output to investigate the failing resource: Help me diagnose my Bicep what-if failure.

Deploy and capture the resource names

The preview is a plan of the proposed Azure state. The create operation now provisions that state inside rg-az104-p04-secure-storage.

Provisioning the Windows VM can take several minutes. A quiet terminal during that wait does not mean the deployment has stalled.

  • Deploy the isolated storage baseline by running this command:
az deployment group create --name p04-core --resource-group $resourceGroupName --template-file .\main.bicep

What Does This Deployment Create?

The deployment creates the network plus the Windows test VM. It also creates the locked-down Storage account plus its Blob private endpoint.

The VM receives a Standard public IP for explicit outbound connectivity in this short-lived lab. The workload NSG has no custom inbound allow rules.

You should see a completed deployment response for p04-core after provisioning finishes.

Deployment Did Not Finish?

Check the deployment output for the first resource that failed. Confirm that the password still satisfies the template requirement.

Share only the non-sensitive failure details when asking for help: Help me troubleshoot the failed p04-core deployment.

  • Display the deployment outputs by running this command:
az deployment group show --resource-group $resourceGroupName --name p04-core --query properties.outputs --output json

Why Capture the Outputs?

Bicep outputs expose the generated Storage account name plus the fixed resource names. Keeping those values in PowerShell makes the next diagnostics repeatable.

You should see values for storageAccountName, storageBlobFqdn, vmName, vnetName, and privateEndpointName.

  • Store the three diagnostic resource names in the current PowerShell session by running these assignments:
$storageAccountName = '[[STORAGE_ACCOUNT_NAME="your storageAccountName output value"]]'
$vmName = '[[VM_NAME="your vmName output value"]]'
$privateEndpointName = '[[PRIVATE_ENDPOINT_NAME="your privateEndpointName output value"]]'

What Do These Variables Hold?

  • $storageAccountName holds the unique Storage account name generated by Bicep.
  • $vmName identifies the Windows test VM.
  • $privateEndpointName identifies the Blob private endpoint.
Verify and document the security baseline

The deployed resources only form a secure baseline when the Storage public path is disabled. The network default action must also deny unmatched traffic.

  • Query both Storage network controls by running this command:
az storage account show --resource-group $resourceGroupName --name $storageAccountName --query "{publicNetworkAccess:publicNetworkAccess,defaultAction:networkRuleSet.defaultAction}" --output json

What Should the Storage Query Prove?

  • publicNetworkAccess returns Disabled.
  • defaultAction returns Deny.

These values confirm that the Storage account rejects the public network path by default.

Storage Controls Look Different?

Confirm that $storageAccountName contains the output from p04-core. Check that the query targets rg-az104-p04-secure-storage.

Ask for help interpreting the returned properties: Help me troubleshoot the Storage network settings query.

The README now needs to preserve the architecture plus the security decisions behind it. This evidence explains why the public IP exists without presenting it as an inbound access path.

  • Switch back to VS Code from earlier.
  • Select README.md from the repository file list.
  • Add the following Project 04 section below the existing status line:
## Project 04: Secure Storage with Private Endpoints and Firewalls

Status: In progress
Region: Denmark East

### Architecture

A Windows Server test VM in `snet-workload` connects to the standard Blob Storage FQDN. The Storage account disables public network access and exposes the `blob` subresource through a Private Endpoint in `snet-private-endpoints`. The initial deployment deliberately omits Private DNS integration.

### Security controls

- Storage public network access is disabled.
- The Storage network default action is deny.
- Blob and container anonymous public access is disabled.
- HTTPS is required and the minimum TLS version is 1.2.
- The test VM has a Standard public IP only to provide explicit outbound connectivity for Azure VM Run Command in this short-lived lab.
- The workload subnet NSG has no custom inbound allow rules, so unsolicited internet inbound traffic is denied by Azure's default NSG rules.

What Does This README Capture?

The architecture section records the normal Blob hostname plus the dedicated private endpoint subnet. It also records the deliberate absence of Private DNS integration.

The security section records disabled public access plus deny-by-default network rules. It also explains the short-lived outbound purpose of the VM public IP.

  • Save README.md.
  • Switch back to the Windows PowerShell window from earlier.
  • Return to the AZ104-Portfolio repository before staging the baseline checkpoint by running these commands:
Set-Location ..
git add project-04-secure-storage-private-endpoint\main.bicep project-04-secure-storage-private-endpoint\scripts\Test-PrivateEndpoint.ps1 README.md
git status

What Does This Checkpoint Stage?

Git stages the README update plus any tracked baseline-file changes. The status output lets you inspect the checkpoint before committing it.

You should see README.md staged for commit. Files with unchanged contents remain absent from the staged changes.

A Git commit now records the deployed baseline separately from the DNS diagnosis that follows.

  • Create the baseline deployment checkpoint by running this command:
git commit -m "Deploy isolated Storage private endpoint baseline"

What Does This Commit Preserve?

The commit preserves the architecture documentation plus the security rationale for the deployed baseline. Later evidence can now build on a clear Git milestone.

You should see a new commit with the message Deploy isolated Storage private endpoint baseline.

Before the final check, predict whether Azure still reports the core deployment as successful.

  • Verify the final deployment state by running this command:
az deployment group show --resource-group rg-az104-p04-secure-storage --name p04-core --query properties.provisioningState --output tsv

What Does This Final Check Prove?

The query reads the provisioning state of the named resource-group deployment. A successful state confirms that Azure completed the baseline deployment.

You should see Succeeded. That is the baseline deployed: the locked-down Storage account plus its Blob private endpoint are ready for diagnosis.

Deployment State Is Not Succeeded?

Inspect the earlier deployment output for the first failed resource. Avoid rerunning the deployment until you understand that failure.

Ask for help using the non-sensitive deployment details: Help me investigate the p04-core provisioning state.

Your isolated storage baseline is live. Next, you will compare the VM-resolved Blob address with the private endpoint address to expose the DNS mismatch.

Diagnose the Missing Private DNS Path

Your isolated storage lab is live. Its Private Endpoint provides a private address for Blob Storage.

The standard Blob hostname still depends on DNS to select that address. This step compares the VM's resolved address with the endpoint address to expose the missing private DNS path.

In this step, get ready to:
  • Retrieve the private endpoint NIC ID and private IP.
  • Run the VM diagnostic to expose the DNS mismatch.
  • Checkpoint the documented failure in Git.
Retrieve the private endpoint address

The private endpoint's network interface holds the address that DNS should return inside the virtual network. You need that address as the baseline for the comparison.

  • Move from the repository root into the project-04-secure-storage-private-endpoint folder by running this command:
Set-Location -Path .\project-04-secure-storage-private-endpoint -PassThru

What does this command do?

PowerShell changes its current location to the Project 04 folder. The printed path confirms that later commands can reach scripts\Test-PrivateEndpoint.ps1.

  • Retrieve the endpoint NIC ID by running this command:
$peNicId = az network private-endpoint show --name $privateEndpointName --resource-group $resourceGroupName --query "networkInterfaces[0].id" --output tsv
$peNicId

What does this query retrieve?

  • The query selects the first network interface attached to pe-p04-storage-blob.
  • The $peNicId variable stores the complete Azure resource ID for that interface.

You should see one network interface resource ID ending with the endpoint NIC name.

  • Retrieve the NIC's private address by running this command:
$privateEndpointIp = az network nic show --ids $peNicId --query "ipConfigurations[0].privateIPAddress" --output tsv
$privateEndpointIp

Why capture this address?

The query reads the private address from the endpoint NIC's first IP configuration. This value is the expected destination for Blob traffic inside vnet-p04-storage.

You should see one private IPv4 address from the 10.40.2.0/24 private endpoint subnet.

  • Record the displayed address here: your private endpoint IP.

Private IP missing?

  • Confirm that $privateEndpointName still contains pe-p04-storage-blob.
  • Confirm that $resourceGroupName still contains rg-az104-p04-secure-storage.
  • Check the first query for a NIC resource ID before running the second query.
  • Help me retrieve the private IP from my Azure private endpoint NIC.
Run the VM diagnostic

The script uses Azure VM Run Command to test resolution from vm-p04-dns-test itself. It clears the VM's DNS cache before resolving the standard Blob FQDN.

Before you run the diagnostic, do you think the resolved A record will match the private endpoint IP?

  • Execute scripts\Test-PrivateEndpoint.ps1 on the VM by running this command:
az vm run-command invoke --command-id RunPowerShellScript --name $vmName --resource-group $resourceGroupName --scripts "@scripts\Test-PrivateEndpoint.ps1" --parameters "StorageAccountName=$storageAccountName"

What does this diagnostic test?

  • The script builds the standard <storageAccountName>.blob.core.windows.net hostname.
  • The DNS lookup prints every returned A record.
  • The TCP test checks connectivity to port 443.
  • The HTTPS probe records an HTTP status or a connection error without using Storage credentials.

You should see a public A record that differs from the private endpoint IP. That mismatch is the intended failure.

Why the output can still vary

The load-bearing evidence is the address mismatch. The TCP result and HTTPS result can vary because network reachability remains separate from Storage data authorization.

A public DNS answer proves that the standard hostname has not moved onto the private endpoint path.

  • Record the public A address here: your public Blob A record.
  • Record the TCP result here: your before-repair TCP result.
  • Record the HTTPS result here: your before-repair HTTPS result.

Run Command not completing?

  • Confirm that vm-p04-dns-test is running.
  • Confirm that PowerShell is inside the project-04-secure-storage-private-endpoint folder.
  • Check that the VM still has outbound connectivity on port 443 for Azure VM Run Command.
  • Help me troubleshoot Azure VM Run Command for this diagnostic script.

✔️ Awesome, I've got everything!

You found the intended fault. The diagnostic script ran unchanged and exposed the public DNS path.

ⓧ I'd like to double check the full code

Compare scripts\Test-PrivateEndpoint.ps1 with this complete reference before continuing.

param(
    [Parameter(Mandatory = $true)]
    [string]$StorageAccountName
)

$fqdn = "$StorageAccountName.blob.core.windows.net"

Write-Output "Storage FQDN: $fqdn"
Clear-DnsClientCache

Write-Output "DNS A records:"
$dnsResults = Resolve-DnsName -Name $fqdn -Type A -ErrorAction Stop
$dnsResults |
    Where-Object { $_.Type -eq 'A' } |
    Select-Object Name, IPAddress

Write-Output "TCP 443 test:"
Test-NetConnection -ComputerName $fqdn -Port 443

Write-Output "Unauthenticated HTTPS probe:"
try {
    $response = Invoke-WebRequest -Uri "https://$fqdn/" -Method Head -UseBasicParsing -ErrorAction Stop
    Write-Output "HTTP status: $([int]$response.StatusCode)"
}
catch {
    if ($null -ne $_.Exception.Response) {
        Write-Output "HTTP status: $([int]$_.Exception.Response.StatusCode)"
    }
    else {
        Write-Output "HTTPS probe error: $($_.Exception.Message)"
    }
}

How does the script gather evidence?

The script clears cached DNS results before resolving the standard Blob hostname. It then tests TCP port 443 and records the unauthenticated HTTPS outcome.

Document the DNS mismatch

The terminal output proves the failure today. The README preserves the comparison so someone else can understand the incident after the lab is gone.

  • Switch back to the README.md tab in Visual Studio Code from earlier.
  • Find the end of the Project 04 security controls section.
  • Add this failure record below that section:
### Designed failure: Private Endpoint DNS mismatch

The Blob private endpoint was provisioned, but Private DNS integration was intentionally omitted. From the VNet test VM, the standard `<storage-account>.blob.core.windows.net` name resolved to a public IP instead of the private endpoint IP. Because the Storage account's public network access was disabled, the public path was not the intended usable data path.

Evidence captured:

- Private endpoint IP: `<paste-private-endpoint-ip>`
- VM-resolved Blob IP before repair: `<paste-public-ip>`
- HTTP result before repair: `<paste-status-or-error>`

What does this evidence establish?

The section records the intended private destination beside the address selected by DNS. It also preserves the HTTPS result without claiming that network reachability grants Storage data access.

  • Replace <paste-private-endpoint-ip> with your private endpoint IP.
  • Replace <paste-public-ip> with your public Blob A record.
  • Replace <paste-status-or-error> with your before-repair HTTPS result.
  • Save README.md.
  • Return PowerShell to the AZ104-Portfolio repository root by running this command:
Set-Location -Path .. -PassThru

What should the path show?

PowerShell moves up one folder. The printed path should now end with AZ104-Portfolio.

  • Stage the diagnostic script and README evidence by running this command:
git add project-04-secure-storage-private-endpoint\scripts\Test-PrivateEndpoint.ps1 README.md

What does Git stage?

Git stages the current contents of both paths. The diagnostic script remains unchanged, so the README is the expected staged difference.

Before you inspect the checkpoint, which file do you expect Git to show as modified?

  • Inspect the staged checkpoint by running this command:
git status

What should be staged?

You should see README.md ready to commit. The diagnostic script should have no content change.

  • Commit the failure evidence by running this command:
git commit -m "Document Private Endpoint DNS failure"

What does this checkpoint preserve?

The commit records the public DNS result before any Private DNS resources exist. This creates a clean before-state for the repair.

Commit not created?

  • Confirm that README.md was saved before staging it.
  • Confirm that PowerShell is at the AZ104-Portfolio repository root.
  • Run the staging command again if Git shows the README as unstaged.
  • Help me troubleshoot this Project 04 Git checkpoint.

Before the final check, do you expect Git to show another uncommitted README change?

  • Verify the checkpoint by running this command:
git status

What should the final check show?

You should see a clean working tree. Your README now records a public Blob A record that does not match the private endpoint IP.

You have captured the missing Private DNS path with repeatable evidence. Next, you will deploy the DNS resources that move the same Blob hostname onto the private endpoint.

Repair Private DNS Resolution

Your last diagnostic proved that the Blob Private Endpoint exists. The standard Storage hostname still resolved to a public address outside that private path.

You will now deploy a Private DNS Zone, a VNet link, and a DNS zone group. Azure DNS can then return the endpoint's private IP for the same Blob FQDN.

In this step, get ready to:
  • Preview the Private DNS repair against the deployed lab.
  • Deploy the DNS resources and prove that the private path works.
  • Document the repaired path and commit the evidence.
Preview the DNS repair

A Bicep what-if operation compares the repair template with the current Azure state. The preview lets you confirm that only the missing DNS resources are added.

  • Move the existing Windows PowerShell session into the project-04-secure-storage-private-endpoint folder by running this command:
Set-Location -Path .\project-04-secure-storage-private-endpoint

What does this command do?

The command changes the current PowerShell location to the folder containing dns-fix.bicep. The deployment commands can now use the template's relative path.

Before you run the preview, which three DNS resources do you expect Azure to propose?

  • Preview the repair by running this command:
az deployment group what-if --name p04-dns-preview --resource-group $resourceGroupName --template-file .\dns-fix.bicep --parameters vnetName=vnet-p04-storage privateEndpointName=$privateEndpointName

What does this preview check?

  • The what-if operation calculates the proposed resource changes without deploying them.
  • The vnetName parameter identifies the existing virtual network.
  • The privateEndpointName parameter identifies the existing Blob private endpoint.
  • Confirm the preview proposes the privatelink.blob.core.windows.net Private DNS Zone.
  • Confirm the preview proposes the link-vnet-p04-storage virtual network link.
  • Confirm the preview proposes the default private DNS zone group.
  • Confirm the preview leaves the existing VM and Storage account unchanged.

That is a clean preview. The proposed changes target the exact DNS gap that caused the public A record.

What-if preview failing?

  • Check that the current PowerShell path ends with project-04-secure-storage-private-endpoint.
  • Check that $resourceGroupName still contains the Project 04 resource group name.
  • Check that $privateEndpointName still contains the Blob private endpoint name.
  • Help me troubleshoot the DNS repair preview.

The existing dns-fix.bicep file should match the direct-resource repair below.

✔️ Awesome, I've got everything!

Your preview contains the expected Private DNS Zone, VNet link, and DNS zone group.

ⓧ I'd like to double check the full code

Compare your dns-fix.bicep file with this complete repair template.

targetScope = 'resourceGroup'

@description('Name of the existing virtual network.')
param vnetName string

@description('Name of the existing Blob private endpoint.')
param privateEndpointName string

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2025-01-01' existing = {
  name: vnetName
}

resource privateEndpoint 'Microsoft.Network/privateEndpoints@2025-05-01' existing = {
  name: privateEndpointName
}

resource blobPrivateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = {
  name: 'privatelink.blob.core.windows.net'
  location: 'global'
}

resource blobPrivateDnsVnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = {
  parent: blobPrivateDnsZone
  name: 'link-${vnetName}'
  location: 'global'
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: virtualNetwork.id
    }
  }
}

resource blobPrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2025-05-01' = {
  parent: privateEndpoint
  name: 'default'
  properties: {
    privateDnsZoneConfigs: [
      {
        name: 'blob-private-dns-zone'
        properties: {
          privateDnsZoneId: blobPrivateDnsZone.id
        }
      }
    ]
  }
}

output privateDnsZoneName string = blobPrivateDnsZone.name
output virtualNetworkLinkName string = blobPrivateDnsVnetLink.name
output privateDnsZoneGroupName string = blobPrivateDnsZoneGroup.name

What should match?

The template creates the global Blob Private DNS Zone. It links the existing virtual network with registration disabled.

The default zone group associates the existing private endpoint with that zone.

Deploy and verify the private path

The preview described the intended changes. Deploying the same template completes private name resolution for clients inside vnet-p04-storage.

Azure may spend a few minutes creating the DNS resources. A short wait during this deployment is expected.

  • Deploy the DNS repair by running this command:
az deployment group create --name p04-dns-fix --resource-group $resourceGroupName --template-file .\dns-fix.bicep --parameters vnetName=vnet-p04-storage privateEndpointName=$privateEndpointName

What does this deployment create?

  • The Private DNS Zone provides the recommended namespace for Blob Private Link resolution.
  • The VNet link makes the zone available to clients inside vnet-p04-storage.
  • The DNS zone group connects pe-p04-storage-blob to the Blob Private DNS Zone.

You should see a successful deployment result for p04-dns-fix. The Private DNS repair is now deployed.

DNS deployment failing?

  • Confirm that the earlier what-if preview completed against rg-az104-p04-secure-storage.
  • Confirm that the parameters name vnet-p04-storage and pe-p04-storage-blob.
  • Confirm that Azure CLI remains signed in to the subscription containing the lab.
  • Help me troubleshoot the Private DNS deployment.

Before you repeat the diagnostic, do you think the resolved A record will now equal $privateEndpointIp?

  • Rerun the diagnostic through Azure VM Run Command by running this command:
az vm run-command invoke --command-id RunPowerShellScript --name $vmName --resource-group $resourceGroupName --scripts "@scripts\Test-PrivateEndpoint.ps1" --parameters "StorageAccountName=$storageAccountName"

What does this diagnostic prove?

  • The script clears the VM's DNS client cache before requesting a fresh answer.
  • The DNS lookup shows which A record the standard Blob FQDN receives.
  • The TCP test checks network reachability on port 443.
  • The HTTPS probe records the service response separately from network reachability.

The resolved A record should exactly match $privateEndpointIp. The TCP result for port 443 should be True.

You have closed the DNS gap. The standard Blob hostname now follows the private endpoint path without a changed hostname or a hard-coded IP.

DNS or TCP still failing?

  • Confirm that the p04-dns-fix deployment completed successfully.
  • Confirm that the what-if preview included the VNet link plus the default DNS zone group.
  • Rerun the same diagnostic so Clear-DnsClientCache removes the earlier DNS answer.
  • Help me diagnose the remaining DNS mismatch.
Document and commit the repair

The before-and-after evidence turns the repair into a reproducible troubleshooting story. Your README can now connect the deployed DNS resources with the result from the VM.

  • Switch back to the AZ104-Portfolio window in Visual Studio Code from earlier.
  • Return to the README.md tab.
  • Find the designed failure section from the previous step.
  • Add this resolution block below the before-repair evidence:
### Resolution

The repair deployed the Private DNS Zone `privatelink.blob.core.windows.net`, linked it to `vnet-p04-storage`, and associated the zone with the Blob private endpoint through a private DNS zone group. The application-facing hostname did not change.

Evidence after repair:

- Standard Blob FQDN: `<storage-account>.blob.core.windows.net`
- VM-resolved Blob IP after repair: `<paste-private-endpoint-ip>`
- Private endpoint NIC IP: `<paste-the-same-private-endpoint-ip>`
- TCP 443 result: `True`

This proves that DNS, not a changed connection string or a hard-coded private IP, moved Storage traffic onto Private Link.

What does this evidence show?

  • The resolution summary records the Private DNS Zone, VNet link, and DNS zone group.
  • The evidence compares the VM-resolved address with the private endpoint NIC address.
  • The final statement explains why the application-facing hostname stayed the same.
  • Replace <storage-account> with the value stored in $storageAccountName.
  • Replace both private IP placeholders with the value stored in $privateEndpointIp.
  • Save README.md.
  • Confirm that every evidence line contains an observed value.

Your resolution section should now show the same private IP for the VM result and the endpoint NIC. The TCP result should remain True.

Evidence values not matching?

  • Copy the storage account name from $storageAccountName.
  • Copy the endpoint NIC address from $privateEndpointIp.
  • Repeat the diagnostic if the VM-resolved address differs from that private IP.
  • Help me compare my Private Endpoint DNS evidence.

A Git checkpoint preserves the repair alongside its after-state evidence. The commit message records the operational change.

  • Return the existing PowerShell session to the AZ104-Portfolio repository root by running this command:
Set-Location -Path ..

What does this command do?

The command moves PowerShell from the Project 04 folder to its parent directory. That parent directory is the AZ104-Portfolio repository root.

  • Stage the DNS repair evidence and create the checkpoint by running these commands:
git add project-04-secure-storage-private-endpoint README.md
git status
git commit -m "Repair Blob private endpoint DNS resolution"

What does this checkpoint record?

  • The first command stages the Project 04 files plus the root README.
  • The status check shows the staged changes before the commit runs.
  • The final command records the DNS repair milestone in Git history.

You should see a new commit summary containing Repair Blob private endpoint DNS resolution. Your DNS repair and after-state evidence are now preserved together.

Commit missing the README update?

  • Confirm that README.md was saved in Visual Studio Code before staging.
  • Confirm that PowerShell points to the AZ104-Portfolio repository root.
  • Run the checkpoint commands again after saving the missing changes.
  • Help me fix the DNS repair checkpoint.

The standard Blob FQDN now resolves to the private endpoint IP with TCP port 443 working. Next up, you will capture the final evidence and finish the portfolio entry.

Capture Evidence and Complete the Portfolio

Your Private Endpoint now uses the repaired Private DNS path. The normal Blob FQDN resolves to the endpoint's private IP.

A final result alone hides the troubleshooting work. Repeatable evidence turns the repair into a story someone else can verify.

You will recheck the Microsoft Azure state against the Bicep templates.

You will complete the README before saving the result in Git.

In this step, get ready to:
  • Re-run the final Azure verification checks.
  • Complete the README with reproducible evidence.
  • Create the final Git checkpoint.
Capture the final Azure evidence

The repaired path already passed its first test. Repeating the same checks proves that the result is reproducible.

  • In Windows PowerShell, switch to the project-04-secure-storage-private-endpoint folder by running this command:
cd .\project-04-secure-storage-private-endpoint

Why Work from This Folder?

The diagnostic script plus both templates live in this folder. This location keeps their relative paths aligned with the verified commands.

Before you run the checks, do you expect the resolved Blob address to match the private endpoint NIC address?

  • Capture the final Storage configuration plus private endpoint evidence by running these commands:
az storage account show --resource-group $resourceGroupName --name $storageAccountName --query "{publicNetworkAccess:publicNetworkAccess,defaultAction:networkRuleSet.defaultAction}" --output json
$peNicId = az network private-endpoint show --name $privateEndpointName --resource-group $resourceGroupName --query "networkInterfaces[0].id" --output tsv
$privateEndpointIp = az network nic show --ids $peNicId --query "ipConfigurations[0].privateIPAddress" --output tsv
az vm run-command invoke --command-id RunPowerShellScript --name $vmName --resource-group $resourceGroupName --scripts "@scripts\Test-PrivateEndpoint.ps1" --parameters "StorageAccountName=$storageAccountName"

What Do These Checks Prove?

  • The Storage query confirms that public network access remains Disabled.
  • The same query confirms that the default network action remains Deny.
  • The private endpoint queries retrieve the endpoint NIC ID. They also retrieve its current private IP.
  • Azure VM Run Command runs the unchanged scripts/Test-PrivateEndpoint.ps1 diagnostic inside the test VM.

You should see Disabled plus Deny in the Storage output. The VM-resolved Blob address should exactly match $privateEndpointIp.

The TCP result should be True for port 443.

Final Diagnostic Not Matching?

  • Check that $resourceGroupName still contains rg-az104-p04-secure-storage.
  • Check that $privateEndpointName still contains pe-p04-storage-blob.
  • Compare the VM-resolved address with the value stored in $privateEndpointIp.
  • Help me diagnose the final Private Endpoint evidence.

The final Bicep what-if checks compare the deployed resources with your local templates. Any unexpected operation would reveal drift before you finish the portfolio entry.

The core preview asks for the secure VM password because the template contains a secure parameter. Azure CLI does not echo the password.

Before you preview the templates, do you expect Azure to propose any unexpected resource changes?

  • Enter a compliant VM password when Azure CLI prompts for the secure parameter.
  • Preview the core template plus DNS repair by running these commands:
az deployment group what-if --name p04-core-preview --resource-group $resourceGroupName --template-file .\main.bicep
az deployment group what-if --name p04-dns-preview --resource-group $resourceGroupName --template-file .\dns-fix.bicep --parameters vnetName=vnet-p04-storage privateEndpointName=$privateEndpointName

What Do the Previews Verify?

  • The core preview compares main.bicep with the complete deployed lab.
  • The DNS preview compares dns-fix.bicep with the repaired Private DNS configuration.
  • Together these previews show whether the verified Azure state still follows your infrastructure code.

You should see no unexpected create, modify, or delete operations. The complete lab should remain deployed with the same verified settings.

Seeing an Unexpected Change?

  • Compare each proposed core operation with main.bicep.
  • Compare each proposed DNS operation with dns-fix.bicep.
  • Stop before deploying an operation that would replace a verified resource.
  • Help me interpret the unexpected Bicep what-if result.
Complete the portfolio entry

The final entry should preserve both sides of the incident. A reader should be able to follow the public DNS mismatch through to the repaired private path.

  • Return to the README.md tab in Visual Studio Code.
  • Find the status line containing Project 04 status: In progress.
  • Replace the status line with Project 04 status: Complete.
  • Move to the end of the Project 04 entry.
  • Add these final portfolio sections:
### Troubleshooting lesson

A Private Endpoint alone does not make an application's existing hostname resolve to a private IP. The client must use DNS that can resolve the service's public FQDN through the recommended `privatelink` zone to the endpoint address. The first diagnostic should compare the client-resolved IP with the private endpoint NIC IP.

### Project files

- `project-04-secure-storage-private-endpoint/main.bicep`
- `project-04-secure-storage-private-endpoint/dns-fix.bicep`
- `project-04-secure-storage-private-endpoint/scripts/Test-PrivateEndpoint.ps1`

### Cleanup decision

`<Keep running, Deallocate VM, or Delete resource group>`

What Does This Add?

  • The troubleshooting lesson records the comparison that exposed the DNS failure.
  • The project file list identifies both Bicep templates. It also identifies the repeatable PowerShell diagnostic.
  • The cleanup section records how you plan to control ongoing Azure costs.
  • Save README.md.
  • Confirm that the three new headings appear at the end of the Project 04 entry.

You should now see sections for the troubleshooting lesson, project files, and cleanup decision.

Final Sections Missing?

  • Check that the new headings sit inside the Project 04 entry.
  • Check that each heading starts with three hash characters.
  • Help me fix the final Project 04 README sections.

Portfolio evidence can expose credentials or account details. This review keeps the finished entry safe to share.

  • Replace every remaining evidence placeholder with the concise output you captured.
  • Add the final Storage settings to the relevant evidence section.
  • Add the private endpoint NIC IP to the after-repair evidence.
  • Record that both final what-if previews showed no unexpected changes.
  • Replace <Keep running, Deallocate VM, or Delete resource group> with your cleanup decision.
  • Search README.md for the VM password.
  • Remove any occurrence of the VM password.

The output may include subscription identifiers that you do not want public. Remove those identifiers before the final save.

  • Review the captured output for unwanted subscription identifiers.
  • Remove each unwanted subscription identifier.
  • Save README.md.

Your README should now contain the architecture, security controls, before-and-after evidence, troubleshooting lesson, project file list, and cleanup decision.

✔️ My portfolio entry is complete

Your Project 04 entry now preserves the repaired state plus the evidence that proves it.

ⓧ I'd like to double check the final entry

Check the project status plus the design evidence first.

  • Confirm the status reads Project 04 status: Complete.
  • Confirm the architecture names snet-workload.
  • Confirm the architecture names snet-private-endpoints.
  • Confirm the security controls record disabled public network access.
  • Confirm the security controls record deny-by-default network rules.
  • Confirm the security controls record minimum TLS version 1.2.
  • Confirm the before-state evidence shows the Blob FQDN resolving to a public address.

Finish by checking the repair evidence plus the publication details.

  • Confirm the after-state evidence shows the same Blob FQDN resolving to the private endpoint IP.
  • Confirm the after-state evidence records successful TCP connectivity on port 443.
  • Confirm the resolution names privatelink.blob.core.windows.net.
  • Confirm the resolution names link-vnet-p04-storage.
  • Confirm the resolution names the default zone group.
  • Confirm the project file list contains both Bicep templates plus the PowerShell diagnostic script.
  • Confirm the entry excludes the VM password plus unwanted subscription identifiers.
Commit the finished project

The final commit connects your completed documentation to the infrastructure state you verified. A clean working tree proves that the evidence is preserved in repository history.

  • Return to the AZ104-Portfolio repository root plus create the final checkpoint by running these commands:
cd ..
git add project-04-secure-storage-private-endpoint README.md
git status
git commit -m "Complete Project 04 secure storage evidence"

What Does This Checkpoint Preserve?

  • The first command returns PowerShell to the AZ104-Portfolio repository root.
  • The staging command includes the Project 04 folder plus the root README.md.
  • The first status check shows the exact paths waiting to be committed.
  • The commit records the completed evidence under Complete Project 04 secure storage evidence.

Git should create the final Project 04 commit using the completed README changes.

Commit Not Created?

  • Confirm PowerShell is at the AZ104-Portfolio repository root.
  • Confirm README.md was saved before staging.
  • Inspect the status output for an unstaged README.
  • Help me complete the final Project 04 Git checkpoint.

Before you run the final status check, do you expect Git to find any uncommitted work?

  • Confirm the final working tree state by running this command:
git status

What Does the Final Status Prove?

A clean working tree means the completed README plus project files are captured in Git history. The final Bicep previews show that the deployed Azure resources still match the templates.

You should see that the working tree is clean. That closes the Project 04 evidence loop.

Working Tree Not Clean?

  • Inspect the status output for the remaining file path.
  • Save the remaining file if it contains an intended project change.
  • Help me clean the final Project 04 Git working tree.

You have finished the portfolio entry with reproducible Azure evidence. Your clean Git history now preserves the complete troubleshooting story.

Secret mission

Refactor the DNS Repair into a Module

Turn the working Private DNS repair into a reusable local Bicep module. You will preserve every deployed Azure resource while making the pattern easier to reuse in future private endpoint projects.

Clean Up Your Resources

Clean Up Your Resources

Your Microsoft Azure lab can continue generating charges while its resources remain deployed. Decide whether to keep everything running, pause the compute, or delete the lab entirely.

Cost warning

Deallocating the Windows virtual machine stops its compute charges. Other deployed services can continue incurring charges.

Deleting rg-az104-p04-secure-storage is the only option here that removes every Azure lab resource.

Resources you used:

  • Resource group rg-az104-p04-secure-storage containing the complete lab.
  • Virtual network vnet-p04-storage.
  • Workload subnet snet-workload.
  • Private endpoint subnet snet-private-endpoints.
  • Network security group nsg-p04-workload.
  • Standard public IP pip-p04-testvm.
  • Network interface nic-p04-testvm.
  • Windows VM vm-p04-dns-test.
  • Managed OS disk for vm-p04-dns-test.
  • Locked-down StorageV2 account.
  • Blob Private Endpoint pe-p04-storage-blob.
  • Endpoint network interface created for pe-p04-storage-blob.
  • Private DNS zone privatelink.blob.core.windows.net.
  • VNet link link-vnet-p04-storage.
  • Private DNS zone group pe-p04-storage-blob/default.

Keep everything running

No action is needed. Choose this option only while you are collecting evidence or reviewing the deployed design.

  • Leave rg-az104-p04-secure-storage deployed with every lab resource running.
  • Keep the Bicep templates in project-04-secure-storage-private-endpoint.
  • Retain the diagnostic script in project-04-secure-storage-private-endpoint/scripts/Test-PrivateEndpoint.ps1.
  • Preserve your README evidence through the existing Git history in AZ104-Portfolio.

Pause - I'll come back to this later

This option stops VM compute charges. It preserves every other lab resource for later.

  • Deallocate vm-p04-dns-test from the Windows PowerShell session that remains open by running this command:
az vm deallocate --resource-group rg-az104-p04-secure-storage --name vm-p04-dns-test

What does this command do?

The Azure CLI releases the VM compute resources. The VM's compute charges stop after deallocation completes.

Every non-compute resource remains deployed. Those resources can continue incurring charges.

  • Return to the vm-p04-dns-test page in the Azure portal.
  • Confirm the VM status reaches Stopped (Deallocated).

Your VM compute is now paused. The private networking lab remains available when you return.

VM not deallocated?

  • Confirm the signed-in Azure account can access rg-az104-p04-secure-storage.
  • Check that the resource group name matches the command exactly.
  • Check that the VM name matches the command exactly.

Help me troubleshoot this VM deallocation.

Delete - I don't want to use this again

This option removes every Azure resource in the lab. Choose it after you have finished reviewing your evidence.

Deleting the live lab is permanent. Your local templates, diagnostic script, README evidence, and Git history remain safe in AZ104-Portfolio.

  • Delete rg-az104-p04-secure-storage from the Windows PowerShell session that remains open by running this command:
az group delete --name rg-az104-p04-secure-storage --yes

What does this command do?

The command deletes the resource group. Azure also deletes every lab resource listed above.

The command leaves your local project-04-secure-storage-private-endpoint folder untouched. Your project evidence remains available after the live lab is gone.

  • Return to the resource groups page in the Azure portal after the command finishes.
  • Search for rg-az104-p04-secure-storage.
  • Confirm that no matching resource group appears.

Your billable Azure lab is now removed. Your local project remains ready to demonstrate.

Resource group still present?

  • Refresh the resource groups page after Azure finishes processing the deletion.
  • Confirm the signed-in account has permission to delete rg-az104-p04-secure-storage.
  • Review the command output for a resource that blocked deletion.

Help me troubleshoot this resource group deletion.

Nice Work!

Nice Work!

Project complete! Your isolated Blob Storage path in Microsoft Azure now reproduces a Private Endpoint DNS failure before proving its code-based repair.

You've learned how to:

  • Built a locked-down StorageV2 account with public network access disabled. Applied deny-by-default network rules before exposing Blob traffic through the private endpoint.
  • Reproduced the designed DNS mismatch from the Windows test VM. Compared the public A record with the endpoint NIC IP to pinpoint the broken private path.
  • Completed the Private DNS repair with privatelink.blob.core.windows.net linked to vnet-p04-storage. Associated the zone through the private endpoint DNS zone group. Proved the standard Blob FQDN resolved to the endpoint private IP. Confirmed TCP port 443 succeeded over the repaired path.
  • Completed the optional Secret Mission by extracting the repair into a reusable local Bicep module. Confirmed the refactor preserved every deployed Azure resource identity.

Ready to quiz yourself?