ABAC policies in Unity Catalog

kept in this browsersaved to your account

ABAC policies apply row filters and column masks at the catalog or schema level based on governed tags. A rule written once with CREATE POLICY covers every tagged table, present and future.

What it is

ABAC (attribute-based access control) is how Unity Catalog applies access rules based on the attributes of objects rather than their names. The attribute is a governed tag (pii = email, sensitivity = high); the rule is a policy attached to a catalog, a schema, or a table that says: “for members of these groups, on columns with this tag, apply this mask” or “on every table with this tag, apply this row filter.”

Why it exists

Per-table row filters and column masks scale poorly: a hundred tables with an email column means a hundred ALTER TABLE statements, and the hundred-and-first table created tomorrow is born unprotected. With ABAC the rule is written once by the catalog owner, and every table that receives the tag inherits it automatically, with no way for the table owner to remove it.

How it works

Governed tags

Regular tags are free-form labels. Governed tags are defined at the account level (by an account admin or metastore admin) with a list of allowed values and permissions on who may assign them. In the UI they show a padlock. Only governed tags can be used in policy conditions. There are also predefined system tags (for example system.certification_status and the class.* family) that cannot be modified.

There are two condition functions:

  • has_tag('name'): the object or column has the tag, with any value;
  • has_tag_value('name', 'value'): the tag has exactly that value.

Anatomy of a policy

CREATE [ OR REPLACE ] POLICY policy_name
ON { CATALOG c | SCHEMA c.s | TABLE c.s.t }
[ COMMENT '...' ]
{ ROW FILTER function | COLUMN MASK function }
TO principal [, ...]
[ EXCEPT principal [, ...] ]
FOR TABLES
[ WHEN condition ]                                   -- on the object
[ MATCH COLUMNS condition AS alias [, ...] ]         -- on the columns
[ ON COLUMN alias ]                                  -- column mask only
[ USING COLUMNS ( alias | constant [, ...] ) ]
ClauseMeaning
ONwhere the policy is attached: it applies to everything below
TO / EXCEPTwho it applies to and who is exempt (often account users with EXCEPT for admins)
FOR TABLESthe target object type (tables, materialized views, streaming tables)
WHENcondition on the object, for example has_tag_value('sensitivity', 'high')
MATCH COLUMNS ... AS aliasselects columns by tag and gives them a name usable later
ON COLUMNwhich column to mask
USING COLUMNSadditional arguments to the function

The function is a SQL UDF, the same kind used by per-table filters; whoever queries the table needs EXECUTE on it. Creating or modifying a policy requires MANAGE on the ON object, or ownership.

Column mask

CREATE FUNCTION prod.sec.ultime_cifre(valore STRING, n INT)
RETURN IF(is_account_group_member('hr'), valore, CONCAT('***', RIGHT(valore, n)));

CREATE POLICY mask_tax_id
ON CATALOG prod
COMMENT 'Tax ID visible in full only to HR'
COLUMN MASK prod.sec.ultime_cifre
TO `account users` EXCEPT `hr`
FOR TABLES
MATCH COLUMNS has_tag_value('pii', 'codice_fiscale') AS cf
ON COLUMN cf
USING COLUMNS (4);

From this point on, every column in prod tagged pii = codice_fiscale shows only its last four characters to anyone outside hr.

Row filter

CREATE FUNCTION prod.sec.solo_it(region STRING)
RETURN region = 'IT';

CREATE POLICY filter_non_domestic
ON SCHEMA prod.sales
ROW FILTER prod.sec.solo_it
TO `analysts`
FOR TABLES
WHEN has_tag_value('sensitivity', 'high')
MATCH COLUMNS has_tag('geo_region') AS region
USING COLUMNS (region);

From Python you run it with spark.sql(...), like any DDL.

Evaluation and conflicts

Policies are evaluated on every query: if the object carries the required tags, the policy applies. At runtime only one row filter and one mask per column can resolve for a given user: if two policies (or a policy and a per-table filter) apply the same function, the query proceeds; if they apply different functions, the query fails with an error. The comparison is on the functions, not on their results.

ABAC versus per-table filters

Per-table row filter / maskABAC policy
Scopeone tablecatalog, schema, or table and everything below it
Selectionby nameby governed tag
Who manages ittable ownercatalog/schema owner; the table owner cannot remove it
New tablesmust be configured by handcovered as soon as they are tagged
OpenSharing (Delta Sharing)noyes, if the share owner is exempt
When to usetable-specific logic, a few stable tablescross-cutting rules, a growing data estate

Requirements and limits

  • Compute: serverless, or Databricks Runtime 16.4 or later (Standard; Dedicated with fine-grained filtering). Older runtimes cannot read protected tables: use EXCEPT to exempt the principals that need them.
  • Policies do not apply to views; a view over a protected table is evaluated with the identity of whoever queries it.
  • Materialized views and streaming tables: the refresh runs with the identity of the pipeline owner; if the owner is subject to the policy, the data is materialized already masked. Exempt them.
  • Time travel and clone fail on protected tables.
  • Quotas: 100 policies per catalog or schema, 50 per table, 20 principals per policy, 3 conditions in MATCH COLUMNS.
  • There are also GRANT policies (granting privileges based on tags) and DENY policies (beta, denying MANAGE ACCESS CONTROL): not needed for the Associate exam, but they explain why DENY is not a statement in Unity Catalog (see Privileges: GRANT, REVOKE, and DENY).

Example

A data steward tags prod.crm.customers.email with pii = email and prod.marketing.lead.email with the same tag. A single policy on prod covers both, plus every future table:

CREATE FUNCTION prod.sec.mask_email(email STRING)
RETURN CONCAT('***@', SPLIT_PART(email, '@', 2));

CREATE POLICY mask_email
ON CATALOG prod
COLUMN MASK prod.sec.mask_email
TO `account users` EXCEPT `privacy-office`
FOR TABLES
MATCH COLUMNS has_tag_value('pii', 'email') AS e
ON COLUMN e;

Common mistakes

  • Using a regular tag in the condition: only governed tags work.
  • Forgetting EXCEPT for the pipeline owner: the materialized views end up masked for everyone.
  • Attaching a policy and a SET MASK with different functions to the same column: the querying user gets an error, not double masking.
  • Assuming the table owner can remove the policy: only whoever created it at the higher level can.

Where this sits

Resources

6All resources
Report a problem with this page
What kind of problem?

Reports about "ABAC policies in Unity Catalog" go to the maintainer, not to a public thread.

Suggest a resource
What kind?

Nothing appears on the site automatically. A person reads every suggestion, checks the link and writes the note that goes with it.