A Python-based accelerator designed to automate the synchronization of security policies from different source catalogs with OneLake Security roles. While mirroring is only synchronizing the data, Policy Weaver is adding the missing piece which is mirroring data access policies to ensure consistent security across data platforms.
- Microsoft Fabric Support: Direct integration with Fabric Mirrored Databases/Catalogs and OneLake Security.
- Runs anywhere: It can be run within Fabric Notebook or from anywhere with a Python runtime.
- Effective Policies: Resolves effective read privileges automatically, traversing nested groups and roles as required.
- Pluggable Framework: Supports Azure Databricks, Snowflake, and Dataverse policies, with more connectors planned.
- Secure: Can use Azure Key Vault to securely manage sensitive information like Service Principal credentials and API tokens.
Make sure your Python version is greater or equal than 3.11. Then, install the library:
$ pip install policy-weaverFollow the General Prerequisites and Installation steps below here. Then, depending on your source catalog, follow the specific setup instructions for Databricks, Snowflake, or Dataverse. If you run into any issues, wish for new features or let us know that you like the accelerator, let us know via our feedback form https://aka.ms/pwfeedback
Before installing and running this solution, ensure you have:
- Azure Service Principal with the following Microsoft Graph API permissions (This is not mandatory in every case but recommended, please check the specific source catalog requirements and limitations):
User.Read.Allas application permissions
- A client secret for the Service Principal
- Added the Service Principal as Admin on the Fabric Workspace containing the mirrored database/catalog.
- The Service Principal needs to be able to call public Fabric REST APIs. This is configured in the tenant settings via the following setting service-principals-can-call-fabric-public-apis
📌 Note: Every source catalog has additional pre-requisites
We assume you have an Entra ID integrated Unity Catalog in your Azure Databricks workspace. To set up Entra ID SCIM for Unity Catalog, please follow the steps in Configure Entra ID SCIM for Unity Catalog.
📋 Note that we only sync groups, users and service principals on account level, i.e. specifically no legacy "local" workspace groups. If you still use local workspace groups, please migrate them: Link to Documentation
We also assume you already have a mirrored catalog in Microsoft Fabric. If not, please follow the steps in Create a mirrored catalog in Microsoft Fabric. You need to enable One Lake Security by opening the Item in the Fabric UI and click on "Manage OneLake data access".
To allow Policy Weaver to read the Unity Catalog metadata and access policies, you need to assign the following roles to your Azure Service Principal:
- Go to the Account Admin Console (https://accounts.azuredatabricks.net/) ➡️ User Management ➡️ Add your Azure Service Principal.
- Click on the Service Principal and go to the Roles tab ➡️ Assign the role "Account Admin"
- Go to the "Credentials & Secrets" tab ➡️ Generate an OAuth Secret. Save the secret, you will need it in your config.yaml file as the
account_api_token.
Download this config.yaml file template and update it based on your environment.
In general, you should fill the config file as described here: Config File values.
For Databricks specifically, you will need to provide:
- workspace_url: https://adb-xxxxxxxxxxx.azuredatabricks.net/
- account_id: your databricks account id (You can find it in the URL when you are in the Account Admin Console: https://accounts.azuredatabricks.net/?account_id=<account_id>)
- account_api_token: Depending on the keyvault setting: the keyvault secret name or your databricks secret
This is all the code you need. Just make sure Policy Weaver can access your YAML configuration file.
#import the PolicyWeaver library
from policyweaver.weaver import WeaverAgent
from policyweaver.plugins.databricks.model import DatabricksSourceMap
#Load config
config = DatabricksSourceMap.from_yaml("path_to_your_config.yaml")
#run the PolicyWeaver
await WeaverAgent.run(config)All done! You can now check your Microsoft Fabric Mirrored Azure Databricks catalog´s new One Lake Security policies.
PolicyWeaverDBX.mp4
We assume you have an Entra ID integrated Snowflake workspace, i.e. users in Snowflake have the same login e-mail as in Entra ID and Fabric, ideally imported through a SCIM process. We also assume you already have a mirrored snowflake database in Microsoft Fabric. If not, please follow the steps in Create a mirrored Snowflake Datawarehouse in Microsoft Fabric. You need to enable One Lake Security by opening the Item in the Fabric UI and click on "Manage OneLake data access".
For the Snowflake setup the Service Principal is required to have User.Read.All permissions for the Graph API to look up the Entra ID object id for each user.
To allow Policy Weaver to read the Snowflake metadata and access policies, you need to create a Snowflake user and role and assign the following privileges. Follow the following steps:
- Create a new technical user in Snowflake, e.g. with the name POLICYWEAVER. (Optionally, but recommended: setup key-pair authentication for this user with an encrypted key as described here)
- Create a new role e.g. ACCOUNT_USAGE and assign the following privileges to this role:
- IMPORTED PRIVILEGES on the SNOWFLAKE database
- USAGE on the WAREHOUSE you want to use to run the queries (e.g. COMPUTE_WH)
- Assign the ACCOUNT_USAGE role to the POLICYWEAVER user
You can use the following SQL statements. Replace the role, user and warehouse names as required.
CREATE ROLE "ACCOUNT_USAGE";
GRANT IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE TO ROLE ACCOUNT_USAGE;
GRANT USAGE ON WAREHOUSE COMPUTE_WH TO ROLE ACCOUNT_USAGE;
GRANT ROLE ACCOUNT_USAGE to USER "POLICYWEAVER";Download this config.yaml file template and update it based on your environment.
In general, you should fill the config file as described here: Config File values.
For Snowflake specifically, you will need to provide:
- account_name: your snowflake account name (e.g. KWADKA-AK8207) OR the secret name in the keyvault if you use keyvault
- user_name: the snowflake user name you created for Policy Weaver (e.g. POLICYWEAVER) OR the secret name in the keyvault if you use keyvault
- private_key_file: the path to your private key file if you are using key-pair authentication (e.g. ./builtin/rsa_policyweaver_key.p8)
- password: the password of the snowflake user if you are using password authentication OR the passphrase of your private key if you are using key-pair authentication OR the secret name in the keyvault if you use keyvault
- warehouse: the snowflake warehouse you want to use to run the queries (e.g. COMPUTE_WH)
This is all the code you need. Just make sure Policy Weaver can access your YAML configuration file.
#import the PolicyWeaver library
from policyweaver.weaver import WeaverAgent
from policyweaver.plugins.snowflake.model import SnowflakeSourceMap
#Load config
config = SnowflakeSourceMap.from_yaml("path_to_your_config.yaml")
#run the PolicyWeaver
await WeaverAgent.run(config)All done! You can now check your Microsoft Fabric Mirrored Snowflake Warehouse´s new One Lake Security policies.
PolicyWeaverSnowflakeV2.mp4
⚠️ Beta Notice: The Dataverse connector is currently in beta.Behavior and configuration options may change in future releases. Please provide feedback via https://aka.ms/pwfeedback.
We assume you have a Dataverse environment (e.g. Dynamics 365, Power Platform) with security roles, users, and teams configured.
We also assume you use Dataverse´s Fabric Link to integrate with Microsoft Fabric. You need to enable OneLake Security by opening the Item in the Fabric UI and clicking on "Manage OneLake data access".
The Dataverse connector does not require Microsoft Graph API permissions (User.Read.All) on the Service Principal. Dataverse provides Azure AD object IDs for users and teams directly, so principal resolution happens without Graph lookups in most cases.
However, the Service Principal needs the following:
If you haven't already created a Service Principal (see General Prerequisites):
- Go to Azure Portal > Entra ID > App registrations > New registration
- Note the Client ID and Tenant ID
- Under Certificates & secrets, create a client secret
This is the most critical step and the one most commonly missed:
- Go to the Power Platform Admin Center → select your environment
- Navigate to Settings > Users + permissions > Application users
- Click + New app user
- Select the app registration you created in step 1
- Assign a Business Unit
- Assign one or more Security Roles (see step 3)
📌 Note: Unlike Microsoft Graph, Dataverse does not require API permissions in the Entra app registration. The Application User + Security Role in Dataverse is what controls access.
The Service Principal (Application User) needs Organization-level Read access on the following Dataverse tables:
| Table | Reason |
|---|---|
systemuser |
Fetch users and their Azure AD object IDs |
team |
Fetch teams and team memberships |
role |
Fetch security roles |
roleprivileges |
Fetch privileges per role (including depth) |
privilege |
Resolve privilege names and access rights |
systemuserroles |
User-to-role assignments |
teamroles |
Team-to-role assignments |
fieldsecurityprofile |
Field-level security profiles |
fieldpermission |
Field-level permissions per profile |
The built-in System Administrator role covers all of these. For least-privilege access, create a custom security role with Organization-level Read on each table listed above.
Make sure your environment_url in the config matches the actual Dataverse environment URL (e.g. https://org21208c7b.crm.dynamics.com). The OAuth token scope is derived from this URL.
Policy Weaver fetches the following security metadata from your Dataverse environment:
- Users: All active, non-disabled system users with Azure AD object IDs
- Teams: All teams (Owner, Access, AAD Security Group, AAD Office Group) with memberships
- Security Roles: All security roles and their read privileges per table
- Privilege Depth: The scope of each read privilege (Basic/User, Local/Business Unit, Deep/Parent BU, Global/Organization)
- Field Security Profiles: Column-level security profiles with read permissions and user/team assignments
role_based(recommended): Creates one Fabric role per Dataverse security role. Supports column-level security.table_based: Creates one Fabric role per table with all principals who have read access. Does not support column-level security.
Download the config.yaml file template and update it based on your environment.
In general, you should fill the config file as described here: Config File values.
For Dataverse specifically, you will need to provide:
- environment_url: Your Dataverse environment URL (e.g.
https://org21208c7b.crm.dynamics.com)
Set the type to DATAVERSE.
Here is a minimal example config:
keyvault:
use_key_vault: false
fabric:
mirror_id: 845-----45567
mirror_name: dataversemirror
workspace_id: 9d-----7c
tenant_id: 349-----e2885
fabric_role_suffix: PW
delete_default_reader_role: true
policy_mapping: role_based
constraints:
columns:
columnlevelsecurity: true
fallback: deny
rows:
rowlevelsecurity: false
fallback: deny
service_principal:
client_id: your-client-id
client_secret: your-client-secret
tenant_id: your-tenant-id
source:
name: your-dataverse-environment
type: DATAVERSE
dataverse:
environment_url: https://yourorg.crm.dynamics.comThis is all the code you need. Just make sure Policy Weaver can access your YAML configuration file.
#import the PolicyWeaver library
from policyweaver.weaver import WeaverAgent
from policyweaver.plugins.dataverse.model import DataverseSourceMap
#Load config
config = DataverseSourceMap.from_yaml("path_to_your_config.yaml")
#run the PolicyWeaver
await WeaverAgent.run(config)All done! You can now check your Microsoft Fabric Mirrored Dataverse database's new OneLake Security policies.
Here ´s how the config.yaml should be adjusted to your environment.
-
keyvault:
- use_key_vault: true/false (true if you want to use keyvault to store secrets, false if you want to store secrets directly in the config file)
- name: your keyvault name (only required if use_key_vault is true)
- authentication_method: azure_cli / fabric_notebook (only required if use_key_vault is true) :right_arrow: use fabric_notebook if you run it in a fabric notebook, otherwise use azure_cli and login with
az loginbefore running the weaver
-
fabric:
- mirror_id: the item id of the mirrored catalog/database/warehouse (you can find it in the URL when you open the workload item in the Fabric UI)
- mirror_name: the name of the item in Fabric
- workspace_id: your fabric workspace id (you can find it in the URL when you are in the Fabric workspace)
- tenant_id: your fabric tenant id (you can find it in the URL "help" -> "about Fabric" section of the Fabric UI)
- fabric_role_suffix: suffix for the fabric roles created by Policy Weaver (default: PW)
- delete_default_reader_role: true/false (if true, the DefaultReader role created by Fabric will be deleted, if false it will be kept, default: true)
- policy_mapping: role_based: create one role per role/group, default: role_based)
-
constraints:
- columns: (optional, if not set, no column level security will be applied, see below for details Column Level Security)
- columnlevelsecurity: true/false (if true, column level security will be applied at best effort. Default: false)
- fallback: grant/deny (if a not supported column mask is found, the fallback will be applied. Default: deny)
- rows: (optional, if not set, no row level security will be applied, see below for details Row Level Security)
- RLS is always applied for non-Global depths to maintain access parity
- fallback: grant/deny (if a not supported row mask is found, the fallback will be applied. Default: deny)
- columns: (optional, if not set, no column level security will be applied, see below for details Column Level Security)
-
service_principal:
- client_id: the client id of the service principal mentioned under general prerequisites OR the corresponding secret name in the keyvault if you use keyvault
- client_secret: the client secret of the service principal mentioned under general prerequisites OR the corresponding secret name in the keyvault if you use keyvault
- tenant_id: the tenant id of the service principal mentioned under general prerequisites OR the corresponding secret name in the keyvault if you use keyvault
-
source:
- name of the unity catalog or snowflake database
- schemas: list of schemas to include. If not set, all schemas are included. For each schema you can give a list of tables which should be included. If not set all tables are included (see examples below)
-
type: either 'UNITY_CATALOG' for databricks, 'SNOWFLAKE' for snowflake, or 'DATAVERSE' for dataverse
Here is an example config.yaml NOT using keyvault:
keyvault:
use_key_vault: false
name: notapplicable
authentication_method: notapplicable
fabric:
mirror_id: 845464654646adfasdf45567
mirror_name: salescatalog
workspace_id: 9d556498489465asdf7c
tenant_id: 3494545asdfs7e2885
fabric_role_suffix: PW
delete_default_reader_role: true
policy_mapping: role_based
constraints:
columns:
columnlevelsecurity: true
fallback: deny
rows:
rowlevelsecurity: true
fallback: deny
service_principal:
client_id: 89ac5a4sd894as9df4sad89f
client_secret: 1234556dsad4848129
tenant_id: 3494545asdfs7e2885
source:
name: dbxsalescatalog
schemas: <---- optional, if not provided all schemas will be scanned
- name: analystschema
tables: <---- optional, if not provided all tables will be scanned
- subsubanalysttable
type: UNITY_CATALOG
databricks:
workspace_url: https://adb-6a5s4df9sd4fasdf.0.azuredatabricks.net/
account_id: 085a54s65a4sfa6565asdff
account_api_token: 74adsf84ad8f4a8sd4f8asdf
snowflake:
account_name: KAIJOIWA-DUAK8207
user_name: POLICYWEAVER
private_key_file: rsa_key.p8
password: ODFJo12io1212
warehouse: COMPUTE_WHHere is an example config.yaml using keyvault.
📋 Note that in this case, the user running the weaver needs to have access to the keyvault and the secrets.
keyvault:
use_key_vault: true
name: policyweaver20250912
authentication_method: fabric_notebook
fabric:
mirror_id: 845464654646adfasdf45567
mirror_name: SFDEMODATA
workspace_id: 9d556498489465asdf7c
tenant_id: 3494545asdfs7e2885
fabric_role_suffix: PW
delete_default_reader_role: true
policy_mapping: role_based
constraints:
columns:
columnlevelsecurity: true
fallback: deny
rows:
rowlevelsecurity: true
fallback: deny
service_principal:
client_id: kv-service-principal-client-id
client_secret: kv-service-principal-client-secret
tenant_id: kv-service-principal-tenant-id
source:
name: SFDEMODATA
schemas: <---- optional, if not provided all schemas will be scanned
- name: analystschema
tables: <---- optional, if not provided all tables will be scanned
- subsubanalysttable
type: SNOWFLAKE
databricks:
workspace_url: https://adb-1441751476278720.0.azuredatabricks.net/
account_id: 085f281e-a7ef-4faa-9063-325e1db8e45f
account_api_token: kv-databricks-account-api-token
snowflake:
account_name: kv-sfaccountname
user_name: kv-sfusername
private_key_file: rsa_key.p8
password: kv-sfpassword
warehouse: COMPUTE_WHColumn level security is an optional feature that can be enabled in the config file. If enabled, Policy Weaver will try to apply column level security at best effort and fallback to the configured fallback if a not supported column mask is found.
policy_mapping is set to role_based. In the case of table_based mapping there would be an unforeseeable high number of roles created in Fabric. That´s why it can only be used in role_based mapping.
Databricks and Snowflake support column level security by column mask policies. Dataverse supports column level security via field security profiles. OneLake Security currently supports column level security by restricting visible columns in role constraints.
Given the biggest use case for column mask policies is to hide the whole column, this still aligns. However, if you have a column mask policy that is not supported by Policy Weaver, you can configure the fallback to either grant or deny access to this column by default.
Supported column mask policies:
- Databricks:
- Allow only a specific group to see the real value. I.e. the function looks like
CASE WHEN is_account_group_member('HumanResourceDept') THEN ssn ELSE '<arbitrary_value>' END - Allow everyone except a specific group to see the real value. I.e. the function looks like
CASE WHEN is_account_group_member('HumanResourceDept') THEN '<arbitrary_value>' ELSE ssn END
- Allow only a specific group to see the real value. I.e. the function looks like
- Snowflake:
- Allow only a specific role to see the real value. I.e. the function looks like
CASE WHEN CURRENT_ROLE() IN ('HR_ROLE') THEN ssn ELSE '<arbitrary_value>' END - Allow everyone except a specific role to see the real value. I.e. the function looks like
CASE WHEN CURRENT_ROLE() IN ('HR_ROLE') THEN '<arbitrary_value>' ELSE ssn END
- Allow only a specific role to see the real value. I.e. the function looks like
- Dataverse:
- Field-level read permissions from Dataverse field security profiles are mapped to Fabric column constraints for role-based policies.
If for a specific role all columns of a table are denied, the whole table is denied to this role and will not show up in the Fabric for this role. :warning: NOTE: Our recommendation is to set the fallback to deny to avoid unintentional data exposure. If you identify a scenaro where there is a data exposure risk, please give us feedback and we´ll try to fix it asap. Note though that this solution is provided as-is without any warranties.
Row level security is an optional feature that can be enabled in the config file. If enabled, Policy Weaver will try to apply row level security at best effort and fallback to the configured fallback if a not supported row access policy is found.
policy_mapping is set to role_based. In the case of table_based mapping there would be an unforeseeable high number of roles created in Fabric. That´s why it can only be used in role_based mapping.
Databricks and Snowflake support various row level security variations. Dataverse uses privilege depth and business unit hierarchy semantics. OneLake Security sets row level security filters not on a table scope but a "table + role"-scope. For many use cases we can map source-side semantics to this "table + role"-scope.
If you have a row access policy that is not supported by Policy Weaver, you can configure the fallback to either grant or deny access to the whole table by default. This also includes unsupported policies like aggregation, join or projection policies in Snowflake. :warning: NOTE: Our recommendation is to set the fallback to deny to avoid unintentional data exposure. If you identify a scenario where there is a data exposure risk, please give us feedback and we´ll try to fix it asap. Note though that this solution is provided as-is without any warranties.
Supported row access policies:
-
Databricks:
- Specify a filter for a specific group and a default for the rest in the form of:
IF(IS_ACCOUNT_GROUP_MEMBER('admin'), true, region='US'); - Based on group membership allow access to certain rows via a CASE statement in the form of:
CASE WHEN IS_ACCOUNT_GROUP_MEMBER('subanalystgroup') THEN true WHEN IS_ACCOUNT_GROUP_MEMBER('analystgroup') THEN Id = 'T001' ELSE false END
- Specify a filter for a specific group and a default for the rest in the form of:
-
Snowflake:
- Allow only specific groups to see the values in the form of:
current_role() in ('SENSITIVE') - Based on group membership allow access to certain rows via a CASE statement in the form of:
CASE WHEN current_role() in ('SENSITIVE') THEN true WHEN current_role() in ('COUNTRY') THEN Id = 'T001' ELSE false END
- Allow only specific groups to see the values in the form of:
-
Dataverse (role-based mapping):
Globaldepth: no row filter is applied.Deepdepth: rows are filtered to the role business unit and descendant business units.Localdepth: rows are filtered to the role business unit.Basicdepth: rows are filtered to records owned by principals in the role.
If you see demand for more (simple) row access policies which are not supported, please give us feedback and we´ll try to add them.
If for a specific role all columns of a table are denied, the whole table is denied to this role and will not show up in the Fabric for this role.
If you run into any issues, wish for new features or let us know that you like the accelerator, let us know via our feedback form https://aka.ms/pwfeedback
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
This project is licensed under the MIT License - see the LICENSE file for details.
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.
