If you need to ensure that all endpoints are private, you can integrate with a virtual network and restrict access to each resource via the same VNet.
Example network:
The resources in Azure should be accessible only via private IP addresses, accessible optionally via another private, on-premises network.
In this mode, the system does have some limitations around data collection and configuration.
- The installer does not take into account private endpoint resources, so once an installation is converted to private endpoints, manual upgrades are needed if version updates are deployed. The manual process for installation and upgrade is documented.
- Also, due to how calls are tracked with Graph pushing notifications to the app service, this won’t be possible with privately protected endpoint.
The lack of calls can be mitigated if the only app service has a public endpoint, but assuming that too is private, it’s impossible to receive webhook notifications from Graph API for calls and therefore we can’t log call data.
For these steps to work, we need a virtual network (VNet) pre-configured with x2 subnets – a default one and then one for app services.
All the dependencies will use the default subnet, and the app service will integrate with a second one.
Private endpoints are created on the VNet and add DNS zones for each resource as needed, so that each service has a private, internal IP address.
Example: for App Insights, when we create the endpoint, we get a new network interface card for it with a private IP address (10.0.0.X) + a DNS “westeurope-5.in.ai.privatelink.monitor.azure.com” (same as the public address).
In this case, the browser will now route requests to the private address, will pass internally to the App Insights instance and therefore be accepted.
In our example we’re using Azure DNS, so resource name registration happens automatically.
Your own infrastructure may have different requirements.
Important: if you have your own DNS infrastructure to integrate with, please add verification steps for each Azure service added below.
In this guide, we assume that you have a working system created by either the installer or the manual configuration method.
Add a private endpoint so access to it is restricted. In the “networking” blade of the app service:
https://learn.microsoft.com/en-us/azure/private-link/private-endpoint-overview
Next, we need to integrate the app service into our VNet so the web-jobs can access private resources. Back in the networking blade click “VNet integration”
Now, any HTTP calls from outside the private network will not arrive at the app service.
Restrict public access and enable a private endpoint.
This will create a network interface for the private endpoint:
Select the same VNet.
You may wish to create a specific IP address. We’re going for automatic configuration.
Now, apps need to use this internal address to connect to any database on this server.
A similar setup with Redis is needed.
https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-private-link
Private endpoints and network isolation require a Premium tier service-bus. The installer creates a basic tier bus, so this needs to be upgraded first – via a “standard tier” level too.
The service bus created by default is a basic tier one, so we need to migrate through the standard tier too:
…meaning we need to create a standard tier namespace, and also a premium one.
Once your options are configured, you can start the migration from basic to premium.
Once done we can create private endpoints as before:
As before this will create a network interface in the selected VNet + register with DNS if managed by Azure.
For storage, we need to configure private endpoints for table + blob services.
Enable private endpoints for blob & table storage.
Ensure the right sub-resource is selected.
DNS configuration is done if managed by Azure. Manual configuration will be needed if DNS is handled by your own infrastructure.
Repeat the same for blob storage.
Language service is optionally used for Teams channel sentiment analysis and other areas of M365. If your configuration doesn’t include this, you don’t need to follow this step.
Disable public access and save changes.
There’s only one sub-resource:
Configure how you want the endpoint to appear on the VNet:
Specify DNS
No changes should be needed for configuration thanks to the CNAME above mapping the public DNS to the private DNS.
Access to App Insights ingestion and tracking is limited via “Azure Monitor Private Link Scopes” on the “Network Isolation” blade.
Create new:
Add a network interface for private endpoint scope:
Configure DNS. There are a few services that App Insights need to have DNS for:
One the scope & endpoint are added, you can configure Application Insights to use them.
With public-network ingestion disabled, any attempts to log data will be denied. Here’s an example response:
Equally with reading the data:
You can only now read/write telemetry data from a device on the same VNet.
Configure the workspace in the same way as Application Insights.
Important: if you don’t configure log analytics with the same Azure Monitor Private Link Scope, no telemetry data will be readable.
Once components have been configured, the app service configuration will need to be updated.
As public endpoints are needed for webhooks and this is precisely what we’ve disabled, we should stop the solution from even trying to setup the webhook as it will fail and generate errors.
In the app-services configuration, disable the calls import:
Save changes.
Changing the SQL server connection will start the web-jobs to crash as they’ll no longer be able to access SQL from the previous DNS with this error:
SqlException: Reason: An instance-specific error occurred while establishing a connection to SQL Server. Connection was denied since Deny Public Network Access is set to Yes. To connect to this server, use the Private Endpoint from inside your virtual network.
In the app services configuration, change the SQL connection DNS:
Check app-service web job logs from a virtual machine on same VNet:
Verify web-jobs are running:
Both web-jobs should be in the “running” state, without restarts.
Redis doesn’t require a change as a CNAME record is added so, in my example, “advanalyticsdev.redis.cache.windows.net” points to “advanalyticsdev.privatelink.redis.cache.windows.net”, which is pointed at the local VNet IP address.
This means that no changes should be needed for the app-service to work with the old, public DNS.
Equally the service-bus DNS should still work for the same reasons.
The Power BI service will need to access the private endpoint for SQL if you’re using PBI reports - https://learn.microsoft.com/en-us/power-bi/enterprise/service-security-private-links