Several coloured data routes converging on a firewall wall, passing through approved openings and a shared gateway node into a protected workspace hexagon, while one dashed amber route is rejected
Back to Blog
Data Engineering

The Firewall Switch Was the Easy Part: Lessons from Securing Microsoft Fabric

Francois de Wet
2026-10-22
14 min read

We recently introduced inbound network restrictions on a Microsoft Fabric workspace that holds our lakehouses and Warehouse. Engineering notebooks, semantic models and an application worker all depended on it.

The change exposed a planning gap: we had treated connectivity as one thing. In practice, browser access, Spark access, SQL activities, model refreshes and application calls followed different paths and used different identities.

We restored and verified those paths with the restrictions enabled. These are the lessons I would use to plan the same change again.

1. Map the caller before choosing the network control

A successful browser session tells you about the browser’s network path. It does not demonstrate that a pipeline runtime or semantic model can reach the same data.

Microsoft documents that communication into an inbound-restricted workspace from another workspace requires a supported managed private endpoint or data gateway route. A private endpoint available to the person running the job does not automatically provide that route to the service executing it. Source: cross-workspace communication.

Our practical connectivity inventory looked like this:

ConsumerRequired operationRoute we verified
Engineering Spark notebookRead lakehouse data in StorageApproved managed private endpoint and workspace-specific OneLake address
Application workerAccess protected OneLake dataVNet integration, workspace private endpoint, DNS and application identity
Pipeline Warehouse activityExecute SQL or a stored procedureSupported Warehouse connection through a VNet data gateway
Import semantic modelRefresh from Warehouse SQLSQL connection through a VNet data gateway, with explicit model binding
Engineer’s browserOpen workspace and lakehousePermitted public route or reachable private route, depending on DNS and policy

This table describes our tested implementation. It is not a claim that every Fabric connector or activity supports every route.

Plan ahead: record the source runtime, destination, protocol, hostname, authentication method, connection owner and acceptance test for every row.

The global *-fpl resource: give the workspace a Private Link target

Before creating the private endpoints, we needed an Azure resource of type Microsoft.Fabric/privateLinkServicesForFabric. We used the suffix fpl in its name; that suffix is our naming choice, not an Azure requirement.

This resource associates the Fabric workspace and tenant with an ARM resource ID that Private Link connections can target. Microsoft’s deployment example sets its location to global. The resource can be hidden in the Azure portal; enable Show hidden resources in the resource group. Source: create the Fabric Private Link resource.

The equivalent illustrative Bicep is:

param fabricPrivateLinkName string = 'analytics-storage-fpl'
param fabricWorkspaceId string
param fabricTenantId string

resource fabricPrivateLink 'Microsoft.Fabric/privateLinkServicesForFabric@2024-06-01' = {
  name: fabricPrivateLinkName
  location: 'global'
  properties: {
    tenantId: fabricTenantId
    workspaceId: fabricWorkspaceId
  }
}

output fabricPrivateLinkResourceId string = fabricPrivateLink.id

Supply the environment identifiers through controlled deployment parameters. global is the location of this Azure resource; it does not select a new Fabric capacity region or relocate the workspace’s data.

The separate VNet private endpoint targets this resource ID using the workspace subresource. The Fabric managed private endpoint also uses this target resource ID; the target owner approves the connection request. Source: managed private endpoint target and approval.

That separation matters: creating the *-fpl resource alone gives neither a private IP in our VNet nor working DNS. We still needed the endpoint, approval, DNS, routes and data permissions. It also does not enable the firewall by itself. We treated those as separate deployment and acceptance steps.

Our planning lesson: include this resource in the initial infrastructure design, before scheduling endpoint approvals and DNS work. Keep the global Fabric resource distinct from regional VNet endpoints and the gateway subnet.

2. The address changes, and the z really matters

One small detail deserves its own heading. Workspace-specific addresses use:

<workspace-id-without-hyphens>.z<first-two-characters>.dfs.fabric.microsoft.com

The z is literal. It is followed by the first two characters of the workspace ID, including digits when those are the first characters. The API and Blob variants use the same workspace prefix. Microsoft also documents a separate Warehouse naming rule: retain the existing SQL server prefix and insert the z label before datawarehouse.fabric.microsoft.com. Source: workspace endpoint formats.

An illustrative helper for notebook configuration:

from uuid import UUID

def workspace_host(workspace_id: str, service: str = "dfs") -> str:
    services = {"dfs", "blob", "w.api"}
    if service not in services:
        raise ValueError("Unsupported service")
    compact = UUID(workspace_id).hex
    return f"{compact}.z{compact[:2]}.{service}.fabric.microsoft.com"

# Read storage_workspace_id from governed environment configuration.
storage_host = workspace_host(storage_workspace_id)

For SQL, start with the actual server name copied from the target Warehouse. Do not reconstruct its encoded prefix from ordinary GUID strings:

def private_sql_host(public_sql_host: str, storage_workspace_id: str) -> str:
    suffix = ".datawarehouse.fabric.microsoft.com"
    host = public_sql_host.strip().lower()
    if "://" in host or not host.endswith(suffix):
        raise ValueError("Supply the SQL hostname without a URL scheme")
    prefix = host[:-len(suffix)]
    if not prefix or "." in prefix:
        raise ValueError("Expected the original, unmodified SQL server prefix")
    compact = UUID(storage_workspace_id).hex
    return f"{prefix}.z{compact[:2]}{suffix}"

SQL clients expect a server hostname, rather than an HTTPS URL. Keep the database identifier supplied by the Warehouse or SQL analytics endpoint. For private OneLake requests, use the destination workspace’s address; the global OneLake hostname does not select that workspace’s private endpoint. Source: OneLake inbound access.

Our lesson was to make endpoint selection shared configuration. A notebook default lakehouse attachment alone did not update explicit addresses embedded in our application or notebook code. We parameterised the destination per environment and retained lakehouse metadata separately.

3. A private endpoint is only useful when DNS and routing agree

During troubleshooting, a general Fabric API call and a global OneLake directory listing succeeded while workspace-specific names failed to resolve. Later, another PC could reach the workspace while the original PC could not. Those observations helped isolate the client DNS path; they did not establish that every service-side workload was healthy.

A name containing privatelink in its CNAME chain is not proof that the final address is private. Look at the final resolved IP and compare it with the approved endpoint’s actual address.

Azure Private DNS zones are authoritative for their namespaces. A missing record in a private zone can produce a negative response rather than the public answer a client previously received. Review forwarding, VNet links and any intended public fallback with the DNS owner. Source: Azure Private Endpoint DNS integration.

Our DNS handoff became a deliverable:

Give the network teamWhy it matters
Environment and target workspacePrevent DEV and PROD records being confused
Approved private endpoint and NIC detailsObtain actual service-to-IP assignments
Exact API, DFS, Blob and SQL namesCover each protocol the workload uses
Required private aliases and record valuesAvoid guessing names or assigning every service the same IP
Client networks and resolver pathsCover desktops, application workers and gateway execution
Test results and rollback ownerMake completion measurable

Fabric’s setup guidance uses privatelink.fabric.microsoft.com and the workspace private endpoint subresource. Its endpoint overview also calls out that the Warehouse FQDN is absent from the endpoint’s DNS configurations. That makes explicit SQL DNS verification important. Sources: private link setup, Warehouse endpoint note.

These PowerShell commands separate name resolution from transport access:

param(
    [Parameter(Mandatory)][guid]$StorageWorkspaceId,
    [Parameter(Mandatory)][string]$PrivateSqlHost
)

$compact = $StorageWorkspaceId.ToString('N')
$prefix = "$compact.z$($compact.Substring(0, 2))"

foreach ($service in @('w.api', 'dfs', 'blob')) {
    $fqdn = "$prefix.$service.fabric.microsoft.com"
    Resolve-DnsName -Name $fqdn -Type A -DnsOnly
    Test-NetConnection -ComputerName $fqdn -Port 443
}

Resolve-DnsName -Name $PrivateSqlHost -Type A -DnsOnly
Test-NetConnection -ComputerName $PrivateSqlHost -Port 1433

Run these from a representative machine with the relevant network path. Then test from the actual application or gateway runtime. A successful TCP probe does not prove TLS, credentials, permissions or query execution.

4. Allowlisted public access and private access can coexist

An allowlisted PC does not necessarily need a connection to the VNet when it uses an allowed public route. Fabric IP rules admit approved public source addresses alongside approved private endpoint access. However, if that PC’s DNS returns a private IP, the PC needs a route to that private address. Source: workspace IP firewall behaviour.

This distinction explained why two PCs could behave differently without any change to the allowlist. We now test at least one private client and one intended public client, and check the externally observed egress address.

5. A working notebook does not prove that SQL will work

We reached a point where notebook connectivity worked, but a pipeline’s Warehouse step and cross-workspace model refreshes failed.

The useful response was to inspect the SQL route and connection binding. We kept the stored procedures and business logic, then routed the supported SQL consumers through a VNet data gateway. Microsoft’s documented Power BI pattern similarly uses a gateway connection to the protected SQL endpoint. Source: Power BI access to an inbound-restricted workspace.

In our implementation we needed two connection objects: a SQL connection for the Import models, and a native Warehouse connection for the pipeline activities. Sharing a destination did not make those connector objects interchangeable.

We verified a constant SQL query first, then a procedure call, then the real business pipeline. We also checked activity output for the effective gateway runtime. A green notebook activity alone would have missed the SQL failure.

6. Prepare the gateway subnet before the cutover

A VNet data gateway is a managed service. Our preparation included a dedicated subnet, capacity and region checks, resource-provider registration, subnet-join permissions, DNS visibility and a path to the target private endpoint. The required delegation is Microsoft.PowerPlatform/vnetaccesslinks. Source: creating VNet data gateways.

This Bicep fragment illustrates the delegation. It assumes an existing VNet and a separately approved, unused subnet prefix:

param vnetName string
param gatewaySubnetName string = 'snet-fabric-gateway'
param gatewaySubnetPrefix string

resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' existing = {
  name: vnetName
}

resource gatewaySubnet 'Microsoft.Network/virtualNetworks/subnets@2024-05-01' = {
  parent: vnet
  name: gatewaySubnetName
  properties: {
    addressPrefix: gatewaySubnetPrefix
    delegations: [
      {
        name: 'dataGwDelegation'
        properties: {
          serviceName: 'Microsoft.PowerPlatform/vnetaccesslinks'
        }
      }
    ]
  }
}

The resource shape follows the Bicep subnet reference. This is an illustrative new-subnet fragment, not a complete production network template. Include the approved NSG, route table and any required service endpoints in the complete definition. Inspect the deployment preview for changes to existing networking. Creating the subnet does not create the Fabric gateway or its authenticated connections.

Authentication also has a network dependency. Microsoft’s gateway FAQ describes Entra connectivity requirements and a Microsoft.AzureActiveDirectory service endpoint remedy for specific OAuth login failures involving private endpoints. Assess that requirement alongside your own routing and egress controls. Source: VNet data gateway troubleshooting.

7. Treat OAuth ownership as a design decision

For the gateway-backed SQL and Warehouse connections we configured, the working option was Organizational account/OAuth2. We initially used an authorised user’s account, while leaving deployment ownership with the existing deployment identity.

The native Warehouse connector documentation lists Organizational account authentication. Microsoft’s protected-workspace SQL example also uses Organizational account/OAuth2. This does not justify saying that all VNet gateway connectors only support OAuth, or that Fabric never supports service principals. Sources: Warehouse connection authentication, protected-workspace SQL example.

We learned to distinguish three identities:

IdentityResponsibility
Deployment principalPublish items and apply supported bindings
Connection’s authenticated accountAuthenticate data access through that connection
Job initiatorRequest pipeline execution or refresh

Granting the deployment principal permission to use a shared connection did not turn its credentials into the connection’s OAuth credentials.

Before cutover, agree who owns each connection, how it is shared, which Conditional Access policies apply, how reauthentication is handled, and what happens when the owner leaves. Do this while the engineers can still work.

8. "Allow trusted resources" needs a workload-specific check

We also considered whether the inbound trusted-resources option could remove the need for a gateway.

Resource Instance Rules allow supported Azure resource identities through OneLake network restrictions; the caller still needs data permissions. This is not a general exemption for every pipeline, SQL activity or semantic model. Source: OneLake Resource Instance Rules.

There is also a separately documented feature called trusted workspace access for firewall-protected Azure Blob Storage and ADLS accounts. Its name should not be taken as evidence that it solves every Fabric-to-Fabric connection. Source: trusted workspace access.

9. Source control must include the bindings

Our first instinct was to ask whether the infrastructure was deployed. The better question was whether the next ordinary release would preserve the working configuration.

For us, reproducibility required endpoint selection in notebook code, environment-specific Warehouse and gateway settings, activity connection references, model source updates and post-deployment gateway bindings. Sensitive environment values and credentials stayed outside public source.

We preserved live executable item IDs, model ownership, RLS and unrelated author changes. We exported a known-working activity definition rather than assuming the shape of its connection reference.

One release exposed a stale report mapping after the required model bindings had succeeded. We retained the failed release result, corrected the obsolete configuration in source, and independently verified the live refreshes. A partially successful deployment deserves a precise status report.

The delivery lesson was equally practical: prepare one coherent, reviewable change for the cutover. A long chain of tiny PRs and approval pauses can extend an outage even when every individual change is straightforward.

10. Define acceptance before enabling restrictions

These are the acceptance checks I would agree before the next rollout:

PathRequired evidence
Private DNSExact API, DFS, Blob and SQL names resolve to intended addresses
Public accessIntended allowlisted client succeeds; controlled non-allowlisted test is rejected
NotebookActual Engineering runtime reads protected lakehouse data
Write path, where neededAuthorised synthetic write/merge in an isolated location, with scoped cleanup
WarehouseQuery, procedure and real pipeline succeed through the intended gateway
Semantic modelsFull refresh reaches Completed; source and gateway bindings are read back
ApplicationA real sample completes upload, security checks and processing
DeploymentReviewed source recreates the endpoint selection and bindings
Final policyRestrictions remain enabled and the approved rule set is unchanged

An accepted refresh request is only the start of the test. A report that opens may still be showing previously imported data. We waited for terminal refresh results and checked the relevant tables.

We used read-only probes first, kept job IDs and timestamps, and removed only the isolated test items whose ownership and terminal state we could verify.

How I would plan the change now

  1. Inventory every caller. Include applications, SQL activities, scheduled refreshes and developer access alongside notebooks.
  2. Confirm the supported route and authentication per connector. Record the current documentation and test the precise activity type.
  3. Reserve networking and ownership early. Agree subnets, DNS authority, private endpoint approval and the ongoing OAuth owner.
  4. Build the whole path in DEV. Prepare infrastructure, endpoint changes, connections, permissions and recurring deployment bindings together.
  5. Establish a baseline, then test with restrictions enabled. Use a controlled window and an explicit recovery decision if acceptance fails.
  6. Promote the same reviewed implementation to PROD. Supply environment values and complete any interactive authentication steps separately.
  7. Close on evidence. Require actual jobs, refreshes and application processing to complete with the intended policy still in force.

The most useful lesson was that network security is part of the data platform’s execution design. Plan the hostname, route, identity and deployment binding for every consumer before changing the inbound policy.

Examples are anonymised and illustrative. Product capabilities vary by workload and change over time; verify the linked support guidance for the connector and activity you intend to use.

I lead data and platform engagements at DW Data. If you are planning inbound network restrictions on your own Fabric estate, I am always happy to compare notes.

Tags:
#Microsoft Fabric#Security#Private Link#OneLake#VNet Data Gateway#DNS#Networking#Data Engineering