Skip to main content
emnode
Compliance Medium severity MCSB NS-2

Microsoft Defender for Cloud · Azure Event Grid

Azure Event Grid domains should use private link

Written and reviewed by Emnode · Last reviewed

What does the recommendation "Azure Event Grid domains should use private link" check?

Audits each Event Grid domain for at least one approved private endpoint connection, evaluating the 'privateEndpointConnections' property and its 'privateLinkServiceConnectionState.status'. A domain with no private endpoint, or one stuck in pending or rejected state, is flagged as non-compliant. This is an audit-only policy: it reports posture but does not block deployments or change the resource. Note its scope is the presence of private link, not whether public network access is switched off, so a domain can pass this check while its 'publicNetworkAccess' is still set to enabled. It also does not cover Event Grid custom topics, partner topics or system topics, which have their own separate recommendations.

Why does "Azure Event Grid domains should use private link" matter?

An Event Grid domain is a single ingress point that can fan events out to thousands of topics, often carrying order data, audit signals or other business events. Reachable over its public endpoint, that publish surface is exposed to the internet and protected only by access keys or SAS tokens, which leak through code, logs and CI pipelines. A leaked key lets an outsider inject forged events or enumerate the domain from anywhere. Routing ingress through a private endpoint keeps event traffic on the Azure backbone and inside your virtual network, so a stolen credential is useless without network access too. For regulated workloads, demonstrable private connectivity is frequently a control auditors expect to see evidenced.

How do I fix "Azure Event Grid domains should use private link"?

  1. Create a private endpoint for the domain so private link traffic has a path: az network private-endpoint create --resource-group <rg> --name <pe-name> --vnet-name <vnet> --subnet <subnet> --private-connection-resource-id $(az eventgrid domain show -g <rg> -n <domain> --query id -o tsv) --connection-name <conn-name> --group-ids domain, then approve the connection.
  2. Close the public path once the private endpoint is approved and your publishers are reattached: az eventgrid domain update --resource-group <rg> --name <domain> --public-network-access disabled. This satisfies the related defence-in-depth posture rather than the audit itself, which only checks for the private endpoint.
  3. Enforce the audit at scale by assigning the built-in policy by display name so no GUID is hardcoded: pid=$(az policy definition list --query "[?displayName=='Azure Event Grid domains should use private link'].name" -o tsv); az policy assignment create --name eg-domains-private-link --policy "$pid" --scope /subscriptions/<sub-id>.

Remediation script · bash

# 1. Stand up a private endpoint for a storage account's blob sub-resource,
#    then wire private DNS so the existing hostname resolves to the private IP.
SA_ID=$(az storage account show -g rg-data -n custexportsprod --query id -o tsv)

az network private-endpoint create \
  --name pe-blob-prod \
  --resource-group rg-data \
  --vnet-name vnet-app \
  --subnet snet-privatelink \
  --private-connection-resource-id "$SA_ID" \
  --group-id blob \
  --connection-name pe-blob-prod-conn

# Link the matching private DNS zone and register the A record automatically.
az network private-dns zone create -g rg-data -n privatelink.blob.core.windows.net
az network private-dns link vnet create -g rg-data \
  --zone-name privatelink.blob.core.windows.net \
  --name link-vnet-app --virtual-network vnet-app --registration-enabled false
az network private-endpoint dns-zone-group create \
  --resource-group rg-data --endpoint-name pe-blob-prod \
  --name zg-blob --private-dns-zone privatelink.blob.core.windows.net --zone-name blob

# 2. Only after verifying the app resolves and connects privately, close the public door.
az storage account update -g rg-data -n custexportsprod --public-network-access Disabled

# 3. Ratchet it shut: assign the AUDIT private-link policy by its exact display name,
#    looking up the definition id at runtime (never paste a hardcoded GUID).
pid=$(az policy definition list \
  --query "[?displayName=='Storage account should use a private link connection'].name" -o tsv)
az policy assignment create \
  --name audit-storage-private-link \
  --policy "$pid" \
  --scope "/subscriptions/00000000-0000-0000-0000-000000000000"

Full walkthrough (console steps, edge cases and verification) in the lesson Require private endpoints for Azure PaaS services.

Is "Azure Event Grid domains should use private link" a false positive?

A domain used only by services that cannot terminate on a private endpoint, such as certain partner or cross-tenant publishers that must reach the public endpoint, will keep flagging because no private link exists. Where you have deliberately accepted that and locked the domain down with an IP firewall allowlist plus key rotation, set the policy assignment effect to Disabled at that scope, or exclude the resource, and record the compensating controls so the exception is auditable rather than silently ignored.