Skip to main content

13. Data Access Control

You'll learn how to protect sensitive columns at schema scale using Unity Catalog ABAC policies in ~30 min (plus a 55-min hands-on lab).

Prereqs: 5. Data Governance Strategy, Unity Catalog foundations, 3. Infra Setup: Add Groups

Starter Journey Progress

CI/CD and DevOps
Data Access Control
Automation & Orchestration
Unified AnalyticsDA
Predictive AnalyticsML
AgentsAI
Query and Explore
Build the First Pipeline
Access Your Data
Data Governance Strategy
Cost Monitoring
Infra SetupDEVSTGPRD
Completed Current Pending

The right approach for column-level access control in Unity Catalog is ABAC (Attribute-Based Access Control). You define a masking policy once, attach it to a tag, and every column in every tagged table inherits the policy. No per-table wiring. No risk of a developer forgetting to add the mask when they create a new table.

Think of it like a building's badge system. You set the rule at the door, not at each desk. The right badge gets you in; everyone else is redirected at the entrance, regardless of how many new desks get added.

How ABAC works

Three building blocks:

  1. Tag: a label applied to a column that identifies what the policy should match
  2. Masking UDF: a SQL function that returns a substitute value
  3. Policy: one statement that binds the UDF to any column carrying that tag, anywhere in a schema or catalog
-- 1. Tag: label the column so a policy can find it
ALTER TABLE main.hr.employees ALTER COLUMN salary SET TAGS ('sensitivity' = 'high');

-- 2. UDF: returns a substitute value; who sees the real value is decided by the policy, not the function
CREATE OR REPLACE FUNCTION main.security.mask_salary(salary DOUBLE)
RETURNS DOUBLE
RETURN NULL;

-- 3. Policy: created once, bound to the schema, masks every matching column except hr_admin and finance
CREATE OR REPLACE POLICY salary_policy
ON SCHEMA main.hr
COLUMN MASK main.security.mask_salary
TO `account users` EXCEPT hr_admin, finance
FOR TABLES
MATCH COLUMNS has_tag_value('sensitivity', 'high') AS salary
ON COLUMN salary;

Any column tagged sensitivity = high in main.hr, on any table, picks up the mask automatically. No new policy, no per-table wiring. Tagging the column is the only step.

warning

ABAC security policies require DBR 16.4+ or serverless compute. Clusters on older runtimes will fail with a permission error when the policy is evaluated.


Tags

Tags are key-value metadata attached to securable objects: catalogs, schemas, tables, and columns. Unity Catalog has three types, and they serve different purposes.

TypeWho sets itSafe for ABAC triggersUse for
CustomData owners, stewardsNo — anyone with ALTER can change or remove itDiscovery, search, catalog navigation, team labeling
GovernedAuthorized users (values policy-controlled)YesCompliance classification, ABAC policy triggers
SystemDatabricks automaticallyYesPII detection output, automatic classification results

Custom tags

Free-form. Any user with ALTER privilege on an object can set them. Good for labeling by owner, team, or domain.

ALTER TABLE main.hr.employees
SET TAGS ('owner' = 'hr-team', 'domain' = 'hr', 'env' = 'prod');

Custom tags can technically appear in a FILTER TAG binding on a security policy, but you should not use them there. Because anyone with ALTER on the schema can change or remove the tag, the policy becomes bypassable:

-- Any data engineer with ALTER on the schema can do this, silently removing the masking policy
ALTER SCHEMA main.hr UNSET TAGS ('sensitivity');

Use custom tags for discovery and documentation only. For anything a security policy depends on, use a governed tag.

Governed tags

A governed tag has an admin-defined policy that controls which values are allowed and who can set them. This makes the tag a trusted, controlled signal. Only users the admin has authorized can apply or remove the value, so a security policy that filters on it cannot be quietly bypassed.

It also ensures consistency: every team uses sensitivity = 'pii', not pii, PII, or Personally Identifiable. These are the tags referenced in ABAC FILTER TAG bindings.

To create a tag policy: go to Data Explorer, select the catalog, open the Tags tab, and create a new tag policy. Define the key and the allowed values, then assign which groups can apply it.

-- Setting a governed tag value (only valid if 'pii' is an approved value for 'sensitivity',
-- and only possible if the caller is in a group authorized to apply this tag)
ALTER SCHEMA main.hr SET TAGS ('sensitivity' = 'pii');

System tags and automatic data classification

Unity Catalog can scan your tables and apply PII tags without any manual work. When enabled, Databricks samples column data, detects common PII types, and writes system-managed tags to each detected column.

To enable: open Data Explorer, select a catalog or schema, open the Data Classification tab, and turn on scanning.

After the scan, columns receive a databricks:columnPiiTypes tag with values like EMAIL_ADDRESS, PHONE_NUMBER, US_SOCIAL_SECURITY_NUMBER, CREDIT_CARD_NUMBER, and PERSON_NAME. You can write ABAC policies that filter on these system tags, so new tables are covered the moment they are scanned, with no manual tagging step.

This is the most scalable setup: classification runs on a schedule, finds new PII columns, and the policy covers them automatically.

tip

System tags are read-only. You cannot set or remove a databricks: tag manually. They are updated when classification reruns.

Reference: Automatic data classification


Common PII patterns

Most organizations protect the same handful of column types. Two strategies exist for what non-privileged users see.

Redaction returns NULL or a fixed placeholder. No information survives. Use this when analysts have no reason to correlate on the value (salary, diagnosis, full SSN).

Hashing (pseudonymization) returns SHA2(value, 256). The hash is unreadable but deterministic: the same input always produces the same output. Data scientists can join user_events to customers on the hash token without ever seeing real addresses. Use this when cross-dataset joins matter and full anonymization is not required.

ColumnWho sees itOthers seeCommon in
salary DOUBLEhr_admin, financeNULLAll industries
ssn STRINGhr_admin, compliance'XXX-XX-XXXX' or SHA-256HR, Insurance, Finance
email STRINGdata_owners, it_supportSHA-256 or '***@domain.com'Retail, SaaS, Marketing
card_number STRINGfraud_team'****-****-****-1234'Retail, Financial Services
phone STRINGsupport_ops'+X-XXX-XXX-1234'Telecom, Retail, Healthcare
dob DATEmedical_staff, actuariesNULLHealthcare, Insurance
diagnosis STRINGclinical_staffNULLHealthcare (HIPAA)
ip_address STRINGsecurity_ops'XXX.XXX.X.X'Tech, Security

See the hands-on lab for the full UDF and policy implementation for each column type, including both redaction and SHA-256 variants.


Row filters

A column mask controls what a visible row shows. A row filter controls which rows are visible at all. Same mechanics, different target: instead of COLUMN MASK, a policy uses ROW FILTER with a function that returns BOOLEAN, and instead of ON COLUMN, it uses USING COLUMNS to pass in the tagged column the filter checks.

Use a row filter when different groups should see different slices of the same table: a regional manager sees their region, a support rep sees their queue, a business unit sees its own cost center. Use a column mask when the row is fine to see but a specific field on it isn't.

CREATE OR REPLACE FUNCTION main.security.filter_region(region STRING)
RETURNS BOOLEAN
RETURN is_account_group_member('region_emea') AND region = 'EMEA';

CREATE OR REPLACE POLICY region_policy
ON SCHEMA main.hr
ROW FILTER main.security.filter_region
TO `account users` EXCEPT hr_admin
FOR TABLES
MATCH COLUMNS has_tag_value('sensitivity', 'region') AS region
USING COLUMNS (region);

Everyone in region_emea sees only EMEA rows on any tagged table in main.hr. hr_admin is exempt and sees everything. See the hands-on lab for a full worked example with three regions.


Unity Catalog: Data Access Control

In this section

  • Hands-on lab: apply ABAC tag-driven policies to an employees table with salary, SSN, email, and credit card columns, including SHA-256 hashing and row-level filtering by region (~55 min)

Next