Azure Bastion: Solving VM Connection problems caused by NSG on AzureBastionSubnet when using the Azure Bastion Premium SKU

In this blog post, you’ll learn how to solve VM connection issues caused by an NSG associated with the AzureBastionSubnet when using the Azure Bastion Premium SKU. Additionally, you’ll find an Azure PowerShell script to create and attach an NSG with the correct inbound and outbound rules for a private-only Bastion deployment.

Table of Contents

Identifying the issue

When deploying Azure Bastion as a private-only setup using the Premium SKU (currently in Public Preview), you should also associate a network security group (NSG) with the Azure Bastion subnet for enhanced network security.

You might assume that this NSG should include all necessary inbound and outbound security rules, as it does with the Basic and Standard SKUs, and as shown in the pictures below.

However, when used in this way, you will not be able to connect to any Azure VMs via your Premium Bastion host. The open tab for the Bastion connection will remain “Loading” for a long time before eventually displaying an “ERR_CONNECTION_TIMED_OUT” error, indicating that the page took too long to respond. In this case, it means the VM can’t be reached by the Bastion host.

Solving the issue

After some trial and error, initially disconnecting the NSG from the AzureBastionSubnet directly resolved the issue. However, this approach isn’t ideal from a network security perspective.

After more testing and making adjustments to the inbound rules, I found the specific configurations needed to restore VM connections through the private-only Bastion host, which are displayed below.

The first two rules (100 and 110) are always required; otherwise, you can’t associate the NSG with the AzureBastionSubnet.

The third rule (120) allows all other necessary inbound traffic from the VM VNets through the Bastion VNet. It mirrors one of the default inbound rules, specifically the rule with priority 65000, which also uses the service tag “Virtual Network” for the source and destination port ranges.

However, for management, maintenance, and security purposes, I prefer not to use any of the default inbound or outbound rules.

With this adjusted or newly created NSG now associated with the AzureBastionSubnet, connecting to any of the Azure VMs in the different VM-related subnets worked without any connection issues.

Azure PowerShell script to create and attach an NSG for a private-only Bastion deployment

For automating the creation and association of this NSG with the AzureBastionSubnet following the deployment of a private-only Bastion host, I’ve developed an Azure PowerShell script that you can utilize.

This Azure PowerShell script, does all of the following:

  • Remove the breaking change warning messages.
  • Change the current context to use a management subscription (a subscription with *management* in the subscription name will be automatically selected).
  • Save the Log Analytics workspace from the management subscription in a variable.
  • Store a specified set of tags in a hash table.
    If it does not already exist, create a resource group for the storage account that will store the NSG flow log data.
  • If it does not already exist, create a general-purpose v2 storage account for storing the flow logs with specific configuration settings. Also apply the necessary tags to this storage account.
  • Create the AzureBastionSubnet with the network security group if it does not already exist. Add the required inbound and outbound security rules. Add specified tags and diagnostic settings.
  • Enable NSG Flow logs (Version 2) and Traffic Analytics for the AzureBastionSubnet NSG.

To use the script, start by saving a copy as “Create-and-Attach-AzureBastion-private-only-NSG.ps1” or downloading it directly from GitHub. Adjust all variables to fit your needs (see the example below), then run the script using Windows TerminalVisual Studio Code, or Windows PowerShell. Alternatively, you can execute it directly from Cloud Shell.

If you’re not using Cloud Shell to run the script, remember to sign in with the Connect-AzAccount cmdlet to link your Azure account. If you have multiple Azure tenants, ensure you select the correct one by running the Set-AzContext -tenantID cmdlet before executing the script.

You can then run the script.

1.\Create-and-Attach-AzureBastion-private-only-NSG.ps1
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318<#.SYNOPSISA script used to create and attach an NSG to the AzureBastionSubnet for an Azure Bastion private-only deployment (Public Preview) in a management subscription..DESCRIPTIONA script used to create and attach an NSG to the AzureBastionSubnet for an Azure Bastion private-only deployment (Public Preview) in a management subscription.The script will do all of the following:Remove the breaking change warning messages.Change the current context to use a management subscription (a subscription with *management* in the subscription name will be automatically selected).Save the Log Analytics workspace from the management subscription in a variable.Store a specified set of tags in a hash table.If it does not already exist, create a resource group for the storage account that will store the NSG flow log data.If it does not already exist, create a general-purpose v2 storage account for storing the flow logs with specific configuration settings. Also apply the necessary tags to this storage account.Create the AzureBastionSubnet with the network security group if it does not already exist. Add the required inbound and outbound security rules. Add specified tags and diagnostic settings.Enable NSG Flow logs (Version 2) and Traffic Analytics for the AzureBastionSubnet NSG..NOTESFilename:       Create-and-Attach-AzureBastion-private-only-NSG.ps1Created:        10/06/2024Last modified:  10/06/2024Author:         Wim MatthyssenVersion:        1.0PowerShell:     Azure PowerShell and Azure Cloud ShellRequires:       PowerShell Az (v10.4.1) and Az.Network (v6.2.0)Action:         Change variables were needed to fit your needs. Disclaimer:     This script is provided "as is" with no warranties..EXAMPLEConnect-AzAccountGet-AzTenant (if not using the default tenant)Set-AzContext -tenantID "xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx" (if not using the default tenant).\Create-and-Attach-AzureBastion-private-only-NSG.ps1.LINKhttps://wmatthyssen.com/2024/06/11/azure-bastion-solving-vm-connection-problems-caused-by-nsg-on-azurebastionsubnet-when-using-the-azure-bastion-premium-sku/#>## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------## Variables$spoke= "hub"$region= #<your region here> The used Azure public region. Example: "westeurope"$purpose= "Bastion"$rgNameNetworking= #<your VNet resource group name here> The name of the Azure resource group in which you're existing VNet is deployed. Example: "rg-hub-myh-networking-01"$rgNameStorage= #<your storage account resource group name here> The name of the Azure resource group in which you're new or existing storage account is deployed. Example: "rg-hub-myh-storage-01"$rgNameNetworkWatcher= #<your Network Watcher resource group name here> The name of the Azure resource group in which you're existing Network Watcher is deployed. Example: "rg-hub-myh-networking-01"$networkWatcherName= #<your Network Watcher name here> The name of your existing Network Watcher. Example: "nw-hub-myh-we-01"$logAnalyticsWorkspaceName= #<your Log Analytics workspace name here> The name of your existing Log Analytics workspace. Example: "law-hub-myh-01"$storageAccountName= #<your storage account name here> The existing or new storage account to store the NSG Flow logs. Example: "sthubmyhlog01"$storageAccountSkuName= "Standard_LRS"$storageAccountType= "StorageV2"$storageMinimumTlsVersion= "TLS1_2"$nsgFlowLogsRetention= "90"$trafficAnalyticsInterval= "60"$vnetName= #<your VNet name here> The existing VNet in which the Bastion resource will be created. Example: "vnet-hub-myh-weu-01"$subnetNameBastion= "AzureBastionSubnet"$subnetAddressBastion= #<your AzureBastionSubnet range here> The subnet must have a minimum subnet size of /26. Example: "10.1.1.128/26"$nsgNameBastion= #<your AzureBastionSubnet NSG name here> The name of the NSG associated with the AzureBastionSubnet. Example: "nsg-AzureBastionSubnet"$nsgBastionDiagnosticsName= #<your NSG Bastion Diagnostics settings name here> The name of the NSG diagnostic settings for Bastion. Example: "diag-nsg-AzureBastionSubnet"$tagSpokeName= #<your environment tag name here> The environment tag name you want to use. Example:"Env"$tagSpokeValue= "$($spoke[0].ToString().ToUpper())$($spoke.SubString(1))"$tagCostCenterName= #<your costCenter tag name here> The costCenter tag name you want to use. Example:"CostCenter"$tagCostCenterValue= #<your costCenter tag value here> The costCenter tag value you want to use. Example: "23"$tagCriticalityName= #<your businessCriticality tag name here> The businessCriticality tag name you want to use. Example: "Criticality"$tagCriticalityValue= #<your businessCriticality tag value here> The businessCriticality tag value you want to use. Example: "High"$tagPurposeName= #<your purpose tag name here> The purpose tag name you want to use. Example:"Purpose"$tagPurposeValueBastion= "$($purpose[0].ToString().ToUpper())$($purpose.SubString(1))"$tagPurposeValueStorage= "Storage"$tagPurposeValueLog= "Log"$tagSkuName= "Sku"$tagSkuValue= $storageAccountSkuName$global:currenttime= Set-PSBreakpoint-Variablecurrenttime-ModeRead-Action{$global:currenttime= Get-Date-UFormat"%A %m/%d/%Y %R"}$foregroundColor1= "Green"$foregroundColor2= "Yellow"$writeEmptyLine= "`n"$writeSeperatorSpaces= " - "# ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------## Remove the breaking change warning messages Set-Item-PathEnv:\SuppressAzurePowerShellBreakingChangeWarnings-Value$true| Out-NullUpdate-AzConfig-DisplayBreakingChangeWarning$false| Out-Null$warningPreference= "SilentlyContinue"## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------## Write script startedWrite-Host($writeEmptyLine+ "# Script started. Without errors, it can take up to 2 minutes to complete"+ $writeSeperatorSpaces+ $currentTime)`-foregroundcolor$foregroundColor1$writeEmptyLine## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------## Change the current context to use a management subscription$subNameManagement= Get-AzSubscription| Where-Object{$_.Name -like"*management*"}Set-AzContext-SubscriptionId$subNameManagement.SubscriptionId | Out-NullWrite-Host($writeEmptyLine+ "# Management subscription in current tenant selected"+ $writeSeperatorSpaces+ $currentTime)`-foregroundcolor$foregroundColor2$writeEmptyLine## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------## Save Log Analytics workspace from the management subscription in a variable$workSpace= Get-AzOperationalInsightsWorkspace| Where-ObjectName -Match$logAnalyticsWorkSpaceNameWrite-Host($writeEmptyLine+ "# Log Analytics workspace variable created"+ $writeSeperatorSpaces+ $currentTime)`-foregroundcolor$foregroundColor2$writeEmptyLine## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------## Store the specified set of tags in a hash table$tags= @{$tagSpokeName=$tagSpokeValue;$tagCostCenterName=$tagCostCenterValue;$tagCriticalityName=$tagCriticalityValue}Write-Host($writeEmptyLine+ "# Specified set of tags available to add"+ $writeSeperatorSpaces+ $currentTime)`-foregroundcolor$foregroundColor2$writeEmptyLine## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------## If it does not already exist, create a resource group for the storage account that will store the NSG flow log data.try {    Get-AzResourceGroup-Name$rgNameStorage-ErrorActionStop | Out-Null} catch {    New-AzResourceGroup-Name$rgNameStorage-Location$region-Force| Out-Null}# Save variable tags in a new variable to add tags$tagsResourceGroup= $tags# Add Purpose tag to tagsResourceGroup$tagsResourceGroup+= @{$tagPurposeName= $tagPurposeValueStorage}# Set tags rg storageSet-AzResourceGroup-Name$rgNameStorage-Tag$tagsResourceGroup| Out-NullWrite-Host($writeEmptyLine+ "# Resource group $rgNameStorage available"+ $writeSeperatorSpaces+ $currentTime)`-foregroundcolor$foregroundColor2$writeEmptyLine## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------## If it does not already exist, create a general-purpose v2 storage account for storing the flow logs with specific configuration settings. ## Also apply the necessary tags to this storage account.try {    Get-AzStorageAccount-ResourceGroupName$rgNameStorage-Name$storageAccountName-ErrorActionStop | Out-Null} catch {    New-AzStorageAccount-ResourceGroupName$rgNameStorage-Name$storageAccountName-SkuName$storageAccountSkuName-Location$region-Kind$storageAccountType`    -AllowBlobPublicAccess$false-MinimumTlsVersion$storageMinimumTlsVersion| Out-Null}# Save variable tags in a new variable to add tags$tagsStorageAccount= $tags# Add Purpose tag to tagsStorageAccount$tagsStorageAccount+= @{$tagPurposeName= $tagPurposeValueLog}# Add Sku tag to tagsStorageAccount$tagsStorageAccount+= @{$tagSkuName= $tagSkuValue}# Set tags storage accountSet-AzStorageAccount-ResourceGroupName$rgNameStorage-Name$storageAccountName-Tag$tagsStorageAccount| Out-NullWrite-Host($writeEmptyLine+ "# Storage account $storageAccountName created"+ $writeSeperatorSpaces+ $currentTime)`-foregroundcolor$foregroundColor2$writeEmptyLine## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------## Create the AzureBastionSubnet with the network security group if it does not already exist. Add the required inbound and outbound security rules. Add specified tags and diagnostic settings# Inbound rules# Rule to allow Ingress Traffic from public Internet$inboundRule1= New-AzNetworkSecurityRuleConfig-Name"Allow_TCP_443_Internet_Inbound"-Description"Allow_TCP_443_Internet_Inbound"`-AccessAllow-ProtocolTCP-DirectionInbound-Priority100-SourceAddressPrefixInternet-SourcePortRange*-DestinationAddressPrefix*-DestinationPortRange443# Rule to allow Ingress Traffic to Azure Bastion data plane$inboundRule2= New-AzNetworkSecurityRuleConfig-Name"Allow_Any_8080_5701_BastionHostCommunication_Inbound"-Description"Allow_Any_8080_5701_BastionHostCommunication_Inbound"`-AccessAllow-Protocol*-DirectionInbound-Priority110-SourceAddressPrefixVirtualNetwork-SourcePortRange*-DestinationAddressPrefixVirtualNetwork `-DestinationPortRange8080,5701# Rule to allow Ingress Traffic to Virtual Network$inboundRule3= New-AzNetworkSecurityRuleConfig-Name"Allow_Any_Any_Vnet_Inbound"-Description"Allow_Any_Any_Vnet_Inbound"`-AccessAllow-Protocol*-DirectionInbound-Priority120-SourceAddressPrefixVirtualNetwork-SourcePortRange*-DestinationAddressPrefixVirtualNetwork `-DestinationPortRange*# Rule to deny all other inbound virtual network traffic$inboundRule4= New-AzNetworkSecurityRuleConfig-Name"Deny_Any_Other_Traffic_Inbound"-Description"Deny_Any_Other_Inbound_Traffic_Inbound"`-AccessDeny-Protocol*-DirectionInbound-Priority900-SourceAddressPrefix*-SourcePortRange*-DestinationAddressPrefix*-DestinationPortRange*# Outbound rules# Rule to allow Egress Traffic to target VMs via RDP (TCP and UDP)$outboundRule1= New-AzNetworkSecurityRuleConfig-Name"Allow_Any_3389_VirtualNetwork_Outbound"-Description"Allow_Any_3389_VirtualNetwork_Outbound"`-AccessAllow-Protocol*-DirectionOutbound-Priority100-SourceAddressPrefix*-SourcePortRange*-DestinationAddressPrefixVirtualNetwork-DestinationPortRange3389# Rule to allow Egress Traffic to target VMs via SSH (TCP and UPD)$outboundRule2= New-AzNetworkSecurityRuleConfig-Name"Allow_Any_22_VirtualNetwork_Outbound"-Description"Allow_Any_22_VirtualNetwork_Outbound"`-AccessAllow-Protocol*-DirectionOutbound-Priority110-SourceAddressPrefix*-SourcePortRange*-DestinationAddressPrefixVirtualNetwork-DestinationPortRange22# Rule to allow Egress Traffic to other public endpoints in Azure (e.g., for storing diagnostics logs and metering logs)$outboundRule3= New-AzNetworkSecurityRuleConfig-Name"Allow_TCP_443_AzureCloud_Outbound"-Description"Allow_TCP_443_AzureCloud_Outbound"`-AccessAllow-ProtocolTCP-DirectionOutbound-Priority120-SourceAddressPrefix*-SourcePortRange*-DestinationAddressPrefixAzureCloud-DestinationPortRange443# Rule to allow Egress Traffic to Azure Bastion data plane$outboundRule4= New-AzNetworkSecurityRuleConfig-Name"Allow_Any_8080_5701_BastionHostCommunication_Outbound"-Description"Allow_Any_8080_5701_BastionHostCommunication_Outbound"`-AccessAllow-Protocol*-DirectionOutbound-Priority130-SourceAddressPrefixVirtualNetwork-SourcePortRange*-DestinationAddressPrefixVirtualNetwork `-DestinationPortRange8080,5701# Rule to allow Egress Traffic to Internet to allow Azure Bastion to communicate with the Internet for session and certificate validation$outboundRule5= New-AzNetworkSecurityRuleConfig-Name"Allow_Any_80_Internet_Outbound"-Description"Allow_Any_80_Internet_Outbound"`-AccessAllow-Protocol*-DirectionOutbound-Priority140-SourceAddressPrefix*-SourcePortRange*-DestinationAddressPrefixInternet `-DestinationPortRange80# Rule to deny all other outbound virtual network traffic$outboundRule6= New-AzNetworkSecurityRuleConfig-Name"Deny_Any_Other_Traffic_Outbound"-Description"Deny_Any_Other_Outbound_Traffic_Outbound"`-AccessDeny-Protocol*-DirectionOutbound-Priority900-SourceAddressPrefix*-SourcePortRange*-DestinationAddressPrefix*-DestinationPortRange*# Create the NSG if it does not existtry {    Get-AzNetworkSecurityGroup-Name$nsgNameBastion-ResourceGroupName$rgNameNetworking-ErrorActionStop | Out-Null} catch {    New-AzNetworkSecurityGroup-Name$nsgNameBastion-ResourceGroupName$rgNameNetworking-Location$region`    -SecurityRules$inboundRule1,$inboundRule2,$inboundRule3,$inboundRule4,$outboundRule1,$outboundRule2,$outboundRule3,$outboundRule4,$outboundRule5,`    $outboundRule6-Force| Out-Null}# Save variable tags in a new variable to add tags$tagsBastion= $tags# Add Purpose tag to $tagsBastion$tagsBastion+= @{$tagPurposeName= $tagPurposeValueBastion}# Set tags NSG$nsg= Get-AzNetworkSecurityGroup-Name$nsgNameBastion-ResourceGroupName$rgNameNetworking$nsg.Tag = $tagsBastionSet-AzNetworkSecurityGroup-NetworkSecurityGroup$nsg| Out-NullWrite-Host($writeEmptyLine+ "# NSG $nsgNameBastion available"+ $writeSeperatorSpaces+ $currentTime)`-foregroundcolor$foregroundColor2$writeEmptyLine# Set the log settings for the NSG if they don't exist$nsgLog= @()$nsgLog+= New-AzDiagnosticSettingLogSettingsObject-Enabled$true-CategoryGroup"allLogs"try {    Get-AzDiagnosticSetting-Name$nsgBastionDiagnosticsName-ResourceId($nsg.Id)-ErrorActionStop | Out-Null} catch {       New-AzDiagnosticSetting-Name$nsgBastionDiagnosticsName-ResourceId($nsg.Id)-Log$nsgLog`    -WorkspaceId($workSpace.ResourceId) | Out-Null}# Create the AzureBastionSubnet if it does not existtry {    $vnet= Get-AzVirtualNetwork-Name$vnetName-ResourceGroupname$rgNameNetworking    $subnet= Get-AzVirtualNetworkSubnetConfig-Name$subnetNameBastion-VirtualNetwork$vnet-ErrorActionStop | Out-Null} catch {    $subnet= Add-AzVirtualNetworkSubnetConfig-Name$subnetNameBastion-VirtualNetwork$vnet-AddressPrefix$subnetAddressBastion| Out-Null    $vnet| Set-AzVirtualNetwork| Out-Null}# Attach the NSG to the AzureBastionSubnet (also if the AzureBastionSubnet exists but lacks an NSG)$subnet= Get-AzVirtualNetworkSubnetConfig-Name$subnetNameBastion-VirtualNetwork$vnet$nsg= Get-AzNetworkSecurityGroup-Name$nsgNameBastion-ResourceGroupName$rgNameNetworking$subnet.NetworkSecurityGroup = $nsg$vnet| Set-AzVirtualNetwork| Out-NullWrite-Host($writeEmptyLine+ "# Subnet $subnetNameBastion available with attached NSG $nsgNameBastion"+ $writeSeperatorSpaces+ $currentTime)`-foregroundcolor$foregroundColor2$writeEmptyLine## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------## Enable NSG Flow logs (Version 2) and Traffic Analytics for the AzureBastionSubnet NSG$networkWatcher= Get-AzNetworkWatcher-Name$networkWatcherName-ResourceGroupName$rgNameNetworkWatcher$storageAccount= Get-AzStorageAccount-ResourceGroupName$rgNameStorage-Name$storageAccountNametry {    Get-AzNetworkWatcherFlowLog-Name"$($nsg.Name)-flow-log"-NetworkWatcher$networkWatcher-TargetResourceId$nsg.Id-ErrorActionStop | Out-Null} catch {    # Configure Flow log and Traffic Analytics    Set-AzNetworkWatcherFlowLog-Name"$($nsg.Name)-flow-log"-NetworkWatcher$networkWatcher-TargetResourceId$nsg.Id-StorageId$storageAccount.Id-Enabled$true-FormatTypeJson `    -FormatVersion2-EnableTrafficAnalytics-TrafficAnalyticsWorkspaceId($workSpace.ResourceId)-TrafficAnalyticsInterval$trafficAnalyticsInterval-EnableRetention$true`    -RetentionPolicyDays$nsgFlowLogsRetention-Tag$tagsBastion-Force| Out-Null}Write-Host($writeEmptyLine+ "# NSG FLow logs and Traffic Analytics for $($nsg.Name) enabled"+ $writeSeperatorSpaces+ $currentTime)-foregroundcolor$foregroundColor2$writeEmptyLine## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------## Write script completedWrite-Host($writeEmptyLine+ "# Script completed"+ $writeSeperatorSpaces+ $currentTime)`-foregroundcolor$foregroundColor1$writeEmptyLine## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Conclusion

The Azure Bastion Premium SKU, currently in Public Preview, allows for a private-only Bastion deployment that is not internet-routable, granting access exclusively via private IP addresses.

When deploying the Bastion host, it’s important to associate an NSG with the AzureBastionSubnet, just as you would with a Basic or Standard SKU host. However, using the same inbound and outbound rules as those SKUs will prevent connection to your Azure VMs via the Bastion host.

I hope the steps outlined in this blog post, along with the provided Azure PowerShell script, will help simplify the process of fixing this issue or creating and associating the correctly configured NSG in your environment.

This blog is part of Microsoft Azure Week! Find more similar blogs on our Microsoft Azure Landing page here.

About the author:

Wim Matthyssen, MVP

With over fifteen years of experience in Microsoft Technologies, Wim currently focuses primarily on the Microsoft Hybrid Cloud, particularly Microsoft Azure and Azure hybrid services.

His expertise lies in guiding companies through their cloud transformation process by implementing the latest features, services, and solutions.

As an MCT, Wim helps customers unlock their full cloud potential, enhancing the productivity of their employees and teams when using Azure.

Reference:

Matthyssen, W. (2024) Azure Bastion: Solving VM Connection problems caused by NSG on AzureBastionSubnet when using the Azure Bastion Premium SKU. Available at: Azure Bastion: Solving VM Connection problems caused by NSG on AzureBastionSubnet when using the Azure Bastion Premium SKU – Wim Matthyssen (wmatthyssen.com) [Accessed on 24/06/2024]

Share this on...

Rate this Post:

Share:

Topics:

Azure