Securing an Azure Storage File Share behind a Point to Site (P2S) VPN

When working with Azure Storages security of data is a common topic of discussion. With the latest changes in Azure, the security of the data at rest is now by default. It is also possible to set the transfer of files to use TLS 1.2 for further security. However a commonly missed security setting is to lock the network traffic to specific resources. Azure Storages provide 2 mechanisms to lock the network traffic:

  1. By specifying a set of selected networks, within your own configuration, and allowing some services, like Azure trusted services to bypass the locking.
  2. By allowing access only through a Private endpoint. This is the ideal scenario when used with Virtual Machines (VMs).

When a Storage Account is locked to a selected virtual networks (vnets), accessing the account file share or containers from the Azure Portal will not work. Also, if one tries to mount the file share over SMB 3 on their personal machine the mount will fail, as the Storage Account cannot be accessed. To overcome these limitations a Point to Site (P2S) VPN can be used.

The rest of the article provides the steps to setup the P2S VPN and the storage account.

Secure Storage Setup for Article
Network Setup Schematic

Step 1: Declaring some variables that will be used

  1. $AzureEnvironment = [PSCustomObject]@{
  2.   Location = 'North Europe'
  3.   Code     = 'neu'
  4. }
  5.  
  6. $Tags = @{
  7.   scope = 'training'
  8.   practice = 'VPN Access to Storage'
  9. }
  10.  
  11. $VpnDnaLabel = 'kdm-vpn-test'
  12.  
  13. $DomainCN = 'P2SCertificate'
  14.  
  15. $ShareName = 'testshare'
  16.  
  17. $ClientVpnCertificate = 'vpn.pfx'

For the purpose of this article the North Europe region is used for the implementation. Any region can be used.

Note: As naming and region conventions that Microsoft suggested names are followed as close as possible

Step 2: Creating the Resource Groups

As with typical Enterprise setups, different resource groups are used to group resources for a specific functionality. This ensures that it is easy to manage related resources.

Two resource groups will be created, one to contain all the networking related resources while the second will contain the storage account and related resources.

  1. Write-Host -ForegroundColor Yellow "Creating Networking Resource Group"
  2. $RgNetworking = New-AzResourceGroup `
  3.     -Name "rg-$($AzureEnvironment.Code)-net-001" `
  4.     -Location $AzureEnvironment.Location `
  5.     -Tag $Tags
  6.  
  7. Write-Host -ForegroundColor Yellow "Creating Storage Resource Group"
  8. $RgStorage    = New-AzResourceGroup `
  9.     -Name "rg-$($AzureEnvironment.Code)-stor-001" `
  10.     -Location $AzureEnvironment.Location `
  11.     -Tag $Tags
\resource Groups
Resource Groups

Step 3: Creating the VPN Subnet

Later on in the setup the VPN subnet ID is required. The best way to have an ID assigned to a subnet is to create it before creating the virtual network and then assign it as part of the creation command.

  1. Write-Host -ForegroundColor Yellow "Creating VPN Subnet"
  2. $SnetGateway = New-AzVirtualNetworkSubnetConfig `
  3.     -Name "gatewaysubnet" `
  4.     -AddressPrefix "10.1.250.0/27" `
  5.     -ServiceEndpoint Microsoft.Storage

Note the address space that the subnet will use. The CIDR of 27 means that the only 27 IPs will be available in the subnet, since the remaining 5 IPs will be used by Azure.

Step 4: Creating the Virtual Networks (VNets)

  1. Write-Host -ForegroundColor Yellow "Creating Networking VNet (DDos Protection recommended for Enterprise setups)"
  2. $VnetNetwork = New-AzVirtualNetwork `
  3.     -Name "vnet-$($AzureEnvironment.Code)-net-001" `
  4.     -ResourceGroupName $RgNetworking.ResourceGroupName `
  5.     -Location $AzureEnvironment.Location `
  6.     -AddressPrefix 10.1.0.0/16 `
  7.     -Subnet $SnetGateway `
  8.     -Tag $Tags
  9.  
  10. Write-Host -ForegroundColor Yellow "Creating Storage VNet"
  11. $VnetStorage = New-AzVirtualNetwork `
  12.     -Name "vnet-$($AzureEnvironment.Code)-stor-001" `
  13.     -ResourceGroupName $RgNetworking.ResourceGroupName `
  14.     -Location $AzureEnvironment.Location `
  15.     -AddressPrefix 10.2.0.0/24 `
  16.     -Tag $Tags

Step 5: Creating a Public IP resource for the VPN

The VPN is used to connect external resource to the Azure resources. For the VPN to become available over the Internet, it requires a Public IP. For the purpose of this article the Basic Public IP is used. However for enterprise level VPNs a Standard Public IP might be more suited.

  1. Write-Host -ForegroundColor Yellow "Creating VPN Public IP"
  2. $VpnIp = New-AzPublicIpAddress `
  3.     -ResourceGroupName $RgNetworking.ResourceGroupName `
  4.     -Name "pip-$($AzureEnvironment.Code)-vpn-001" `
  5.     -Location $AzureEnvironment.Location `
  6.     -Sku Basic `
  7.     -AllocationMethod Dynamic `
  8.     -DomainNameLabel $VpnDnaLabel `
  9.     -IpAddressVersion IPv6 `
  10.     -Tag $Tags

Step 6 (Optional): Creating Self-Signed Certificates

To build this article proof of concept, a self-signed certificate is created to be used with P2S VPN. However, it might be ideal for enterprise scenarios to use an Authoritative CA Certificate.

  1. Write-Host -ForegroundColor Yellow "Generate VPN Certificate"
  2. $P2SCert = New-SelfSignedCertificate `
  3.     -Type Custom `
  4.     -KeySpec Signature `
  5.     -Subject "CN=$DomainCN" `
  6.     -KeyExportPolicy Exportable `
  7.     -KeyAlgorithm RSA `
  8.     -KeyLength 4096 `
  9.     -CertStoreLocation "Cert:\CurrentUser\My" `
  10.     -KeyUsageProperty Sign `
  11.     -KeyUsage CertSign
  12.  
  13. $CertificateFingerPrint = [System.Convert]::ToBase64String($P2SCert.RawData)
  14.  
  15. $CertificateForVpn = New-AzVpnClientRootCertificate `
  16.     -Name 'SelfSignedCert' `
  17.     -PublicCertData $CertificateFingerPrint
  18.  
  19. Write-Host -ForegroundColor Yellow "Generate Client Certificate"
  20. $ClientCert = New-SelfSignedCertificate `
  21.     -Type Custom `
  22.     -KeySpec Signature `
  23.     -Subject "CN=Client$DomainCN" `
  24.     -KeyExportPolicy Exportable `
  25.     -KeyAlgorithm RSA `
  26.     -KeyLength 4096 `
  27.     -CertStoreLocation "Cert:\CurrentUser\My" `
  28.     -Signer $P2SCert `
  29.     -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2")
  30.  
  31.  
  32. Write-Host -ForegroundColor Yellow "Generate Password for Pfx file"
  33. $CertPass = ConvertTo-SecureString `
  34.     -String ([System.Web.Security.Membership]::GeneratePassword(32, (Get-Random -Minimum 5 -Maximum 16))) `
  35.     -Force `
  36.     -AsPlainText
  37.  
  38. Write-Host -ForegroundColor Yellow "Exporting Password for Pfx file"
  39. Export-PfxCertificate `
  40.     -Cert $ClientCert.PSPath `
  41.     -FilePath $ClientVpnCertificate `
  42.     -Password $CertPass `
  43.     -ChainOption BuildChain
  44.  
  45. $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($CertPass)
  46. $plaintext = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
  47. Write-Host -ForegroundColor Red "Main Certificate Pfx key: $plaintext"
  48.  
  49. Write-Host -ForegroundColor Green "Removing VPN Certificate from Certificates store"
  50. Remove-Item $P2SCert.PSPath -Force

A lot of code is requires to create the self-signed certificates. Starting from the server Fingerprint that will act as a root certificate for the connection. This is created from Line 94 to 106. The next step is to prepare it in the object expected by the P2S VPN, lines 108 to 110.

Once the root certificate has been created the certificate to be used by the remote computer is created, lines 126 to 129. Although for a proof of concept it is typically to run the VPN connection on the same machine that generated the certificates, this is not true for enterprise setups. From line 131 to 140, the client certificate is exported protected with a password. As the password is automatically created and will be required when reinstalled on a remote machine, it is outputted to the screen. It is important to keep the password safe and secure.

The last line of the snippet will remove the root certificate from the machine on which it was created. This step should be commented out if future VPN client certificates are to be created.

Step 7: Creating the VPN

Now that the required resources by the VPN have been created, the VPN itself can be created. The last step required before creating the VPN is to assign the Public IP created in Step 5, is assigned to the VPN being created.

  1. Write-Host -ForegroundColor Yellow "Creating the VPN"
  2. $SnetGateway = Get-AzVirtualNetworkSubnetConfig `
  3.     -Name "gatewaysubnet" `
  4.     -VirtualNetwork $VnetNetwork
  5.  
  6. $GatewayIpConfig = New-AzVirtualNetworkGatewayIpConfig `
  7.     -Name "vpnip-$($AzureEnvironment.Code)-p2s-001" `
  8.     -SubnetId $SnetGateway.Id `
  9.     -PublicIpAddressId $VpnIp.Id
  10.  
  11. $Vpn = New-AzVirtualNetworkGateway `
  12.     -Name "vpn-$($AzureEnvironment.Code)-p2s-001" `
  13.     -ResourceGroupName $RgNetworking.ResourceGroupName `
  14.     -Location $AzureEnvironment.Location `
  15.     -GatewayType Vpn `
  16.     -GatewaySku VpnGw1 `
  17.     -VpnType RouteBased `
  18.     -EnableBgp $true `
  19.     -IpConfigurations $GatewayIpConfig `
  20.     -VpnClientRootCertificates $CertificateForVpn `
  21.     -VpnClientAddressPool '172.16.0.0/24' `
  22.     -Tag $Tags
Local VPN connection

Step 8: Peering the VNets

Up till now, we have 2 separate VNets that are unable to communicate with each other. If the VPN client had to be downloaded and installed, the Storage Account VNet will be unreachable, since there is no route from the VPN to the storage account

C:\>route print 10*
===========================================================================
Interface List
 16...** ** ** ** ** ** ......Realtek PCIe GBE Family Controller
 42...........................vnet-neu-net-001
  3...** ** ** ** ** ** ......Microsoft ******
  6...** ** ** ** ** ** ......Microsoft ******
  7...** ** ** ** ** ** ......Qualcomm *******
 19...** ** ** ** ** ** ......Bluetooth *******
  1...........................Software Loopback Interface 1
===========================================================================
 
IPv4 Route Table
===========================================================================
Active Routes:
Network Destination        Netmask          Gateway       Interface  Metric
         10.1.0.0      255.255.0.0         On-link        172.16.0.2     43
     10.1.255.255  255.255.255.255         On-link        172.16.0.2    281
    104.45.84.174  255.255.255.255      192.168.0.1     192.168.0.23     36
===========================================================================
Persistent Routes:
  None
 
IPv6 Route Table
===========================================================================
Active Routes:
  None
Persistent Routes:
  None

To get the route to the Storage Account accessible form the VPN it is important to peer the 2 VNets in the solution. The peering will create the route required.

Note: If a VNet peer is create after the VPN client has been downloaded, a new version of the VPN client will need to be downloaded to reflect the change.

  1. Write-Host -ForegroundColor Yellow "Enable VNet Peering"
  2. Add-AzVirtualNetworkPeering `
  3.     -Name netPeerPerimeterToStorage `
  4.     -VirtualNetwork $VnetNetwork `
  5.     -RemoteVirtualNetworkId $VnetStorage.Id `
  6.     -AllowGatewayTransit `
  7.     -AllowForwardedTraffic
  8.  
  9. Add-AzVirtualNetworkPeering `
  10.     -Name netPeerStorageToPerimeter `
  11.     -VirtualNetwork $VnetStorage `
  12.     -RemoteVirtualNetworkId $VnetNetwork.Id `
  13.     -UseRemoteGateways `
  14.     -AllowForwardedTraffic
Showing how a peered connection looks from the networking vnet
Showing how a peered connection looks from the networking vnet

Step 9: Creating the File Share

  1. Write-Host -ForegroundColor Yellow "Creating the Storage Account"
  2.  
  3. $StorageAccount = New-AzStorageAccount `
  4.     -Name "stor$($AzureEnvironment.Code)fs001" `
  5.     -ResourceGroupName $RgStorage.ResourceGroupName `
  6.     -SkuName Standard_LRS `
  7.     -Location $AzureEnvironment.Location `
  8.     -Kind StorageV2 `
  9.     -AccessTier Hot `
  10.     -EnableHttpsTrafficOnly $true `
  11.     -EnableLargeFileShare `
  12.     -AllowBlobPublicAccess $false
  13.  
  14. Write-Host -ForegroundColor Yellow "Creating the File Share"
  15.  
  16. $FileShareDrive = New-AzStorageShare `
  17.     -Name $ShareName `
  18.     -Context $StorageAccount.Context
  19.  
  20. Set-AzStorageShareQuota `
  21.     -Share $FileShareDrive.CloudFileShare `
  22.     -Quota 1024
  23.  
  24. Write-Host -ForegroundColor Yellow "Set Storage Account Security"
  25.  
  26. Update-AzStorageAccountNetworkRuleSet `
  27.     -ResourceGroupName $RgStorage.ResourceGroupName `
  28.     -StorageAccountName $StorageAccount.StorageAccountName `
  29.     -Bypass 'AzureServices' `
  30.     -VirtualNetworkRule (@{ `
  31.         VirtualNetworkResourceId="$($VnetNetwork.Subnets.Where({$_.Name -eq 'gatewaysubnet'}).Id)"}) `
  32.     -DefaultAction Deny

Lines 188 to 207, create the Storage Account and a file share called ‘testshare’, in this case, with a quota of 1TB.

Lines 211-213 will secure the Storage Account by limiting which networks are allowed to access the Storage Account content.

Step 10 (Optional): Creating a Private Link

When the file share is to be mounted on Azure VMs, a Private Endpoint can be used. Furthermore, a private endpoint can also be used to provide a custom domain FQDN to the file share instead of the Azure FQDN. This might be more ideal in enterprise setups.

The code snippet below provides the necessary steps to create the private endpoint, a private DNS and to register the the private endpoint with the DNS.

  1. Write-Host -ForegroundColor Yellow "Creating the Storage Private Link"
  2. $FileShareConnection = New-AzPrivateLinkServiceConnection `
  3.         -Name "$($FileShare.StorageAccountName)-Connection" `
  4.         -PrivateLinkServiceId $StorageAccount.Id `
  5.         -GroupId "file" 
  6.  
  7. Add-AzVirtualNetworkSubnetConfig `
  8.     -Name "snet-fileshare" `
  9.     -AddressPrefix "10.2.0.0/27" `
  10.     -VirtualNetwork $VnetStorage `
  11.     -PrivateEndpointNetworkPoliciesFlag Disabled
  12.  
  13. Set-AzVirtualNetwork -VirtualNetwork $VnetStorage
  14.  
  15. # Getting the refreshed subnet details
  16. $SnetFileShare = (Get-AzVirtualNetwork `
  17.     -Name $VnetStorage.Name `
  18.     -ResourceGroupName $RgNetworking.ResourceGroupName).Subnets.Where({$_.Name -eq $SnetFileShare.Name }) `
  19.     | Select -First 1
  20.  
  21. $PrivateEndPoint = New-AzPrivateEndpoint `
  22.     -Name "$($StorageAccount.StorageAccountName)-PrivateEndPoint" `
  23.     -ResourceGroupName $RgStorage.ResourceGroupName `
  24.     -Location $AzureEnvironment.Location `
  25.     -Subnet $SnetFileShare `
  26.     -PrivateLinkServiceConnection $FileShareConnection
  27.  
  28. Write-Host -ForegroundColor Yellow "Creating the Private DNS"
  29. $PrivateDns = New-AzPrivateDnsZone `
  30.     -ResourceGroupName $RgNetworking.ResourceGroupName `
  31.     -Name "pdns-$($AzureEnvironment.Code)-net-001.mydomain.com" `
  32.     -Tag $Tags
  33.  
  34. New-AzPrivateDnsVirtualNetworkLink `
  35.     -ResourceGroupName $RgNetworking.ResourceGroupName `
  36.     -ZoneName $PrivateDns.Name `
  37.     -Name $PrivateEndPoint `
  38.     -VirtualNetwork $VnetStorage
  39.  
  40. $PrivaeDnsConfig = New-AzPrivateDnsZoneConfig `
  41.     -Name $PrivateDns.Name `
  42.     -PrivateDnsZoneId $PrivateDns.ResourceId
  43.  
  44. New-AzPrivateDnsZoneGroup `
  45.     -ResourceGroupName $RgStorage.ResourceGroupName `
  46.     -Name "FileSharePrivateLinkGroup" `
  47.     -PrivateEndpointName $PrivateEndPoint.Name `
  48. -PrivateDnsZoneConfig $PrivaeDnsConfig

tl;dr of the steps

Step 1: Declaring some variables that will be used
Step 2: Creating the Resource Groups
Step 3: Creating the VPN Subnet
Step 4: Creating the Virtual Networks (VNets)
Step 5: Creating a Public IP resource for the VPN
Step 6 (Optional): Creating Self-Signed Certificates
Step 7: Creating the VPN
Step 8: Peering the VNets
Step 9: Creating the File Share
Step 10 (Optional): Creating a Private Link

Full code for the article can be downloaded from https://gist.github.com/kdemanuele/ae26ca958872fbfb86bd8212b16b2cc3

References