Data governance and secure data sharing

Snowflake · Expert · 16 min

Regulated data needs fine-grained controls, and sharing without copies is one of Snowflake's defining features.

Beyond grants, Snowflake offers policy objects (Enterprise edition) that are attached to columns and tables and evaluated at query time for every user.

Masking policies transform a column's value depending on context: CREATE MASKING POLICY ... AS (val STRING) RETURNS STRING -> CASE WHEN IS_ROLE_IN_SESSION('PII_READER') THEN val ELSE '***' END. Attach one to any number of columns with ALTER TABLE ... MODIFY COLUMN ... SET MASKING POLICY. Prefer IS_ROLE_IN_SESSION over CURRENT_ROLE() so inherited and secondary roles count. Tag-based masking attaches a policy to a tag such as pii = 'email', so every tagged column is protected automatically.

Row access policies return a boolean per row, typically by looking up the user's role in a mapping table, so one table can serve many regions or tenants.

Secure data sharing grants other Snowflake accounts read access to live objects. The provider creates a share, grants database, schema and table or secure-view privileges to it, and adds consumer accounts. The consumer creates a read-only database from the share. No data is copied or moved: the consumer queries the provider's storage with the consumer's own warehouse, and sees updates immediately. Share subsets through secure views: shares accept secure objects by default, because an ordinary view's definition and optimizations could reveal data it is meant to hide. Cross-region and cross-cloud sharing relies on replication, which Marketplace listings can automate. Reader accounts let you share with organizations that don't have Snowflake.

Example

CREATE MASKING POLICY mask_email AS (val STRING) RETURNS STRING ->
  CASE WHEN IS_ROLE_IN_SESSION('PII_READER') THEN val
       ELSE REGEXP_REPLACE(val, '.+@', '***@') END;
ALTER TABLE customers MODIFY COLUMN email SET MASKING POLICY mask_email;
-- analyst sees ***@x.io, PII_READER sees ana@x.io

CREATE ROW ACCESS POLICY by_region AS (row_region STRING) RETURNS BOOLEAN ->
  EXISTS (SELECT 1 FROM sec.region_access m
          WHERE m.role_name = CURRENT_ROLE() AND m.region = row_region);
ALTER TABLE orders ADD ROW ACCESS POLICY by_region ON (region);

-- Provider: share a secure view with a partner account
CREATE SECURE VIEW shop.public.partner_orders AS
  SELECT id, amount, created_at FROM shop.raw.orders WHERE partner_id = 42;
CREATE SHARE partner_share;
GRANT USAGE ON DATABASE shop TO SHARE partner_share;
GRANT USAGE ON SCHEMA shop.public TO SHARE partner_share;
GRANT SELECT ON VIEW shop.public.partner_orders TO SHARE partner_share;
ALTER SHARE partner_share ADD ACCOUNTS = partner_org.partner_acct;
-- Consumer: CREATE DATABASE partner_data FROM SHARE provider_org.provider_acct.partner_share;

Remember

  • Masking policies change column values per role at query time
  • Row access policies filter rows per user or role
  • Shares give live, read-only access with no copies; consumers pay compute

Watch out for

  • Trying to share a plain view; create it as a SECURE VIEW so its definition and filtered rows stay protected.

Check yourself

  1. Who pays for the compute when a consumer queries data from a share?
  2. Why prefer IS_ROLE_IN_SESSION('PII_READER') over CURRENT_ROLE() = 'PII_READER' in a masking policy?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.snowflake.com/en/user-guide/security-column-ddm-intro