Most Azure services are accessible via a public URL. However, to provide a higher level of protection and a direct connection, various options are available in Azure. The two most common approaches are Service Endpoints and Private Endpoint/Private Link.

Service endpoints are used to access the service by routing within Azure backbone. For this purpose, the service endpoint for the desired Azure service type (in this case: storage account) must be activated in the outgoing subnet and the subnet for the respective service must be configured as permitted access.

Private endpoints provide Azure services with a private IP address from a specified VNet. Depending on the service, the public IP address is then no longer accessible (app service) or can be additionally configured/used (e.g. storage account). The private endpoint creates an additional alias DNS entry, which contains privatelink as the second part. Both names always resolve to the same IP address. To resolve both names to the new private IP, there must be a link to a Private DNS Zone of the service type in which the service is registered. Sometimes the Private Endpoints are referred as Private Link, which means the same principle. The private link is the link to the Azure service, the private endpoint means the IP from a subnet through which the service can then be reached.

VM1 – No Configuration

VM1 is in the same VNet as the private endpoint of the storage account, but no usage or internal routing is configured in Azure. The fact that peering to the VNet with the linked private DNS zone exists also has no influence, since the VNet of VM1 is not linked to the zone itself. Thus, the name resolution is performed exclusively to the public IP.

~$ nslookup pepstrgtzuehlke.blob.core.windows.net
pepstrgtzuehlke.blob.core.windows.net   canonical name = pepstrgtzuehlke.privatelink.blob.core.windows.net.
pepstrgtzuehlke.privatelink.blob.core.windows.net       canonical name = blob.ams09prdstr05a.store.core.windows.net.
Name:   blob.ams09prdstr05a.store.core.windows.net
Address: 20.60.27.228

~$ nslookup pepstrgtzuehlke.privatelink.blob.core.windows.net
pepstrgtzuehlke.privatelink.blob.core.windows.net       canonical name = blob.ams09prdstr05a.store.core.windows.net.
Name:   blob.ams09prdstr05a.store.core.windows.net
Address: 20.60.27.228

Since the storage account has disabled public access via the firewall (only certain subnets are allowed), access via VM1 fails accordingly:

~$ curl -i -X GET -H "x-ms-date: $(date -u)" "https://pepstrgtzuehlke.blob.core.windows.net/?comp=list&sv=2021-06-08&ss=bfqt&srt=sco&sp=rwdlacupiytfx&se=2022-10-28T22:29:35Z&st=2022-09-16T14:29:35Z&spr=https&sig=hdjNxHoqxBCA4L6ZLe98NFKYzzjroc8YBITgdBnA9Ns%3D"
HTTP/1.1 403 This request is not authorized to perform this operation.
Content-Length: 246
Content-Type: application/xml
Server: Microsoft-HTTPAPI/2.0
x-ms-request-id: ca04b4ec-301e-0055-5f11-d05192000000
x-ms-error-code: AuthorizationFailure
...

If an attempt is made to access the storage account directly via the IP of the private endpoint, the connection is possible, but there is no matching entry in the SSL certificate for the IP address:

 ~$ curl -i -X GET -H "x-ms-date: $(date -u)" "https://10.1.0.4/?comp=list&sv=2021-06-08&ss=bfqt&srt=sco&sp=rwdlacupiytfx&se=2022-10-28T22:29:35Z&st=2022-09-16T14:29:35Z&spr=https&sig=hdjNxHoqxBCA4L6ZLe98NFKYzzjroc8YBITgdBnA9Ns%3D"
...
curl: (60) SSL: no alternative certificate subject name matches target host name '10.1.0.4'
...

The same error occurs even when the correct host is included in the header:

~$ curl -k -i -X GET -H "x-ms-date: $(date -u)" -H "Host: pepstrgtzuehlke.blob.core.windows.net" "https://10.1.0.4/?comp=list&sv=2021-06-08&ss=bfqt&srt=sco&sp=rwdlacupiytfx&se=2022-10-28T22:29:35Z&st=2022-09-16T14:29:35Z&spr=https&sig=hdjNxHoqxBCA4L6ZLe98NFKYzzjroc8YBITgdBnA9Ns%3D"
...
curl: (60) SSL: no alternative certificate subject name matches target host name '10.1.0.4'
...

On the same call, but with the -k parameter to ignore the certification error, there is a 400 response:

~$ curl -k -i -X GET -H "x-ms-date: $(date -u)" "https://10.1.0.4/?comp=list&sv=2021-06-08&ss=bfqt&srt=sco&sp=rwdlacupiytfx&se=2022-10-28T22:29:35Z&st=2022-09-16T14:29:35Z&spr=https&sig=hdjNxHoqxBCA4L6ZLe98NFKYzzjroc8YBITgdBnA9Ns%3D"
HTTP/1.1 400 Bad Request
Content-Type: text/html; charset=us-ascii
Server: Microsoft-HTTPAPI/2.0
Date: Sat, 24 Sep 2022 12:39:46 GMT
Connection: close
Content-Length: 334

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Bad Request</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Bad Request - Invalid Hostname</h2>
<hr><p>HTTP Error 400. The request hostname is invalid.</p>
</BODY></HTML>
...

But if we combine the ignoring of the certificate error, the header with the correct host and access via the private IP, we succeed:

~$ curl -k -i -X GET -H "x-ms-date: $(date -u)" -H "Host: pepstrgtzuehlke.blob.core.windows.net" "https://10.1.0.4/?comp=list&sv=2021-06-08&ss=bfqt&srt=sco&sp=rwdlacupiytfx&se=2022-10-28T22:29:35Z&st=2022-09-16T14:29:35Z&spr=https&sig=hdjNxHoqxBCA4L6ZLe98NFKYzzjroc8YBITgdBnA9Ns%3D"
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/xml
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: e831ac18-e01e-0046-7813-d0759e000000
x-ms-version: 2021-06-08
Date: Sat, 24 Sep 2022 12:43:39 GMT

<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="https://pepstrgtzuehlke.blob.core.windows.net/"><Containers /><NextMarker /></EnumerationResults>

VM2 – Service Endpoint

In this scenario, subnet-2 is configured for service endpoint communication to storage accounts. Access to the subnet-2 is enabled for the storage account. VNet peering is not necessary.

The values for nslookup return the same results as VM1, i.e. the public IP. The call can simply be made via the public DNS name, which is now internally routed and responds with 200:

~$ curl -i -X GET -H "x-ms-date: $(date -u)" "https://pepstrgtzuehlke.blob.core.windows.net/?comp=list&sv=2021-06-08&ss=b&srt=s&sp=l&se=2023-09-24T22:30:40Z&st=2022-09-24T14:30:40Z&spr=https&sig=%2BAUaqJQWiZDDFWES8xMixFjAOAHiNHvJT5hoSuzs9xA%3D"
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/xml
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: e0a637ef-001e-002c-6322-d0adb6000000
x-ms-version: 2021-06-08
Date: Sat, 24 Sep 2022 14:33:24 GMT

<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="https://pepstrgtzuehlke.blob.core.windows.net/"><Containers /><NextMarker /></EnumerationResults>

Service Endpoint Policy

The configuration options for service endpoints are very limited. The service allows access from the whole subnet and the whole subnet can reach any endpoint of the specified type.

To restrict access more, a service endpoint policy can be configured and assigned. It lists the service instances that can be accessed, all other service instances are not accessible. If you assign an empty service endpoint policy, access to all services are allowed. The service endpoint policy is a separate resource that lists the allowed services and is assigned to the service endpoint in the subnet:

VM3 – Private Link/Endpoint and Private DNS Zone

This scenario can be used to access Azure services from OnPrem. However, it can also be used within Azure.

The storage account has obtained a private endpoint from the subnet-pep. The VNet with VM3 is peered to the VNet with the private endpoint. In addition, the VNet with VM3 is linked to a private DNS zone where the private endpoint of the storage account is registered. Therefore, VM3 resolves both the regular DNS name and the DNS name with privatelink to the private IP address:

~$ nslookup pepstrgtzuehlke.blob.core.windows.net
pepstrgtzuehlke.blob.core.windows.net	canonical name = pepstrgtzuehlke.privatelink.blob.core.windows.net.
Name:	pepstrgtzuehlke.privatelink.blob.core.windows.net
Address: 10.1.0.4

~$ nslookup pepstrgtzuehlke.privatelink.blob.core.windows.net
Name:	pepstrgtzuehlke.privatelink.blob.core.windows.net
Address: 10.1.0.4

This resolves the public DNS name to the private IP and the call using the default DNS name is successful. This should be the way to access the azure service:

~$ curl -i -X GET -H "x-ms-date: $(date -u)" "https://pepstrgtzuehlke.blob.core.windows.net/?comp=list&sv=2021-06-08&ss=b&srt=s&sp=l&se=2023-09-24T22:30:40Z&st=2022-09-24T14:30:40Z&spr=https&sig=%2BAUaqJQWiZDDFWES8xMixFjAOAHiNHvJT5hoSuzs9xA%3D"
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/xml
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: cf5ab8d1-401e-002d-0228-d0f26a000000
x-ms-version: 2021-06-08
Date: Sat, 24 Sep 2022 15:13:58 GMT

<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="https://pepstrgtzuehlke.blob.core.windows.net/"><Containers /><NextMarker /></EnumerationResults>

Even if the call via the privatelink returns the same private IP, the data retrieval fails because the SSL certificate does not match the privatelink DNS:

~$ curl -i -X GET -H "x-ms-date: $(date -u)" "https://pepstrgtzuehlke.privatelink.blob.core.windows.net/?comp=list&sv=2021-06-08&ss=b&srt=s&sp=l&se=2023-09-24T22:30:40Z&st=2022-09-24T14:30:40Z&spr=https&sig=%2BAUaqJQWiZDDFWES8xMixFjAOAHiNHvJT5hoSuzs9xA%3D"
...
curl: (60) SSL: no alternative certificate subject name matches target host name 'pepstrgtzuehlke.privatelink.blob.core.windows.net'
...

If the certificate conflict is ignored, the call via the privatelink DNS also works:

~$ curl -k -i -X GET -H "x-ms-date: $(date -u)" "https://pepstrgtzuehlke.privatelink.blob.core.windows.net/?comp=list&sv=2021-06-08&ss=b&srt=s&sp=l&se=2023-09-24T22:30:40Z&st=2022-09-24T14:30:40Z&spr=https&sig=%2BAUaqJQWiZDDFWES8xMixFjAOAHiNHvJT5hoSuzs9xA%3D"
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/xml
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 0428e229-901e-003e-1328-d0d666000000
x-ms-version: 2021-06-08
Date: Sat, 24 Sep 2022 15:18:45 GMT

<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="https://pepstrgtzuehlke.privatelink.blob.core.windows.net/"><Containers /><NextMarker /></EnumerationResults>

Private Endpoint Network Policy

If the subnet from which the private endpoint for the service is obtained has an NSG to restrict access to the service, this NSG is ignored by the private link. The configuration of the NSG therefore does not take effect if access is gained via private link.

To enforce the existing NSG of the subnet for the private endpoint as well, you can simply enable the corresponding setting in the subnet. Specifically, the Network Policy for Private Endpoints setting must be set to Enabled.