cancel
Showing results forย 
Search instead forย 
Did you mean:ย 
Data Engineering
Join discussions on data engineering best practices, architectures, and optimization strategies within the Databricks Community. Exchange insights and solutions with fellow data engineers.
cancel
Showing results forย 
Search instead forย 
Did you mean:ย 

Implementing Row Level Security using ABAC

Dolly0503
New Contributor III

I have to implement row level Security to single/multiple tables based on roles and we don't want to create separate copies for users this one how can i implement and what is the code i can use?

2 REPLIES 2

balajij8
Contributor III

Hi Rupa,

You can define row filters centrally and apply them dynamically to single or multiple tables based on governed tags - no duplication is required.

It has three main components. 

  • Governed Tags - Apply the account level tags to tables/columns to classify them  (sensitivity:high, department:finance etc)
  • Row Filter UDFs - SQL functions that return BOOLEAN (if true - user can see the row)
  • Policies - Its created at catalog, schema or table level that bind UDFs to users/groups based on tag conditions

You can follow below

  • Create a governed tag
-- Create account-level governed tag with allowed values
CREATE TAG sensitivity VALUES ('high', 'medium', 'low');
CREATE TAG department VALUES ('finance', 'hr', 'sales', 'engineering');
CREATE TAG region VALUES ('US', 'EU', 'APAC');โ€‹
  • Apply Tags to Tables
-- Tag entire tables
ALTER TABLE main.default.sensitive_transactions 
SET TAGS ('sensitivity' = 'high', 'region' = 'US');

ALTER TABLE main.default.employee_data 
SET TAGS ('sensitivity' = 'high', 'department' = 'hr');โ€‹
  • Create Row Filter UDF that contain filtering code
-- Filter by user's department
CREATE OR REPLACE FUNCTION main.default.filter_by_department(dept_column STRING)
RETURNS BOOLEAN
RETURN 
  CASE
    WHEN is_account_group_member('HR admins') THEN TRUE  -- HR sees all
    WHEN is_account_group_member('Finance admins') AND dept_column = 'finance' THEN TRUE
    WHEN is_account_group_member('Sales team') AND dept_column = 'sales' THEN TRUE
    ELSE FALSE
  ENDโ€‹
  • Create ABAC Policies
CREATE POLICY dept_filter_on_employees
ON TABLE main.default.employee_data
COMMENT 'Users see rows from their department'
ROW FILTER main.default.filter_by_department
TO `All Users` EXCEPT `Data admins`
FOR TABLES
MATCH COLUMNS has_tag('department') AS dept
USING COLUMNS (dept);โ€‹

You can create policies at catalog/schema level to apply it to all tables under it as below

CREATE POLICY catalog_wide_filter
ON CATALOG main
COMMENT 'Restrict sensitive data across entire catalog by department'
ROW FILTER main.default.filter_by_department
TO `All Users` EXCEPT `C-suite`, `Data admins`
FOR TABLES
WHEN has_tag_value('sensitivity', 'high')
MATCH COLUMNS has_tag('department') AS dept
USING COLUMNS (dept);

You can use the TO and EXCEPT clauses to control which users/groups the policy applies to.

  • TO - Policy applies to these users/groups
  • EXCEPT - users/groups bypass the filter (see all rows)

You can create policies once & apply it automatically. Unity Catalog evaluates policies at query time and provides the relevant data.

Louis_Frolio
Databricks Employee
Databricks Employee

Hi @Dolly0503 ,

Yes, you can do row-level security across one table or many in Unity Catalog without copying data per role. @balajij8  pointed you in the right architectural direction (ABAC with governed tags, a reusable row-filter function, and centrally managed policies). Let me add the official requirements, a couple of corrections, and a simpler option if you only have a few tables.

The first thing to decide is your path, and it comes down to scale.

Path A is the classic row filter. It's the simplest approach and it's the best fit when you only have a few tables. No tags or policies needed. You attach a UDF directly to each table.

-- Function returns TRUE for rows the caller may see
CREATE OR REPLACE FUNCTION main.default.dept_filter(dept STRING)
RETURN is_account_group_member('data_admins')   -- admins see all
  OR is_account_group_member(dept);              -- else only your dept's rows

-- Attach to each table
ALTER TABLE main.default.employee_data
  SET ROW FILTER main.default.dept_filter ON (department);

is_account_group_member() is the role hook. It evaluates the querying user's group membership at runtime. You repeat the ALTER TABLE for each table you want filtered.

Path B is ABAC policies. This is the better fit when you want one definition to govern many tables, including ones that don't exist yet. There are four steps.

  1. Define governed tags at the account level (Catalog Explorer under tag policies, or the REST API/Terraform) for the attributes that drive access, things like department, region, and sensitivity. One correction here: governed tags are not created with an inline CREATE TAG ... VALUES(...) statement. They're managed at the account level, then assigned to objects.
  2. Assign tags to the relevant tables and columns.
ALTER TABLE main.default.employee_data SET TAGS ('department' = 'hr');
  1. Create the row-filter UDF. It returns a BOOLEAN, same as Path A.
  2. Create a policy on the catalog, schema, or table, bound to the tagged objects.
CREATE POLICY dept_rls
ON SCHEMA main.default
COMMENT 'Users see only their department''s rows'
ROW FILTER main.default.dept_filter
TO `account users` EXCEPT `data_admins`
FOR TABLES
MATCH COLUMNS has_tag('department') AS dept
USING COLUMNS (dept);

The payoff with Path B is that you define it once on the schema or catalog, and any current or future table carrying the department tag gets filtered automatically. No per-table wiring.

A few guardrails to get right before you go to production:

  1. ABAC adds restrictions on top of access, it doesn't grant it. You still need the normal object-level GRANT on the table. ABAC only filters what's visible after access is granted.
  2. Requirements and privileges: supported compute (serverless or DBR 16.4+), MANAGE on the securable, and EXECUTE on the UDF. ABAC policies are GA as of mid-2026.
  3. Only one distinct row filter can resolve per user and table at runtime. If multiple different filters apply to the same user and table, Databricks errors out. So consolidate your role logic into one reusable function, or make sure your policies are mutually exclusive.
  4. Add an admin or service-account escape hatch in the UDF (the data_admins check above), otherwise pipelines and owners can lock themselves out.
  5. For complex role to data mappings, have the UDF run an EXISTS query against a lookup table (e.g. role_access_map) instead of chaining is_account_group_member() CASE branches. It's much easier to maintain as your roles grow.
  6. If you also need to hide columns or values (masking an SSN, for example), use the sibling column mask feature. Same ABAC machinery, just COLUMN MASK instead of ROW FILTER.

The short version: for RLS across many tables without duplicating data, use Unity Catalog ABAC with governed tags and a single reusable row-filter UDF. If you only have a few tables, a direct SET ROW FILTER gets you there in two statements.

Docs:

  1. ABAC in Unity Catalog: https://docs.databricks.com/aws/en/data-governance/unity-catalog/abac/
  2. Create and manage row filter and column mask policies: https://docs.databricks.com/aws/en/data-governance/unity-catalog/abac/policies
  3. Row filters and column masks: https://docs.databricks.com/aws/en/data-governance/unity-catalog/filters-and-masks
  4. Requirements, quotas, and limitations: https://docs.databricks.com/aws/en/data-governance/unity-catalog/abac/requirements

Cheers, Louis.